Living life and Make it Better2024-01-26T10:34:10+07:00https://software.endy.muhardin.comEndy Muhardinendy.muhardin@gmail.comEnkripsi Data dalam Aplikasi2022-09-08T08:14:00+07:00https://software.endy.muhardin.com/java/enkripsi-aes-data-aplikasi<p>Tahun lalu, kita sudah membahas cara <a href="/java/enkripsi-data-di-aplikasi/">enkripsi data di aplikasi menggunakan Vault</a>. Enkripsi data menggunakan Vault lebih aman, dan saat ini bahkan sudah diterima untuk mengajukan sertifikasi PCI-DSS. Walaupun demikian, untuk aplikasi kecil, yang dijalankan oleh perusahaan yang tidak memiliki personel IT operation yang memadai, agak sulit untuk mengelola instalasi Vault server.</p>
<p>Oleh karena itu, pada artikel ini, kita akan membahas metode enkripsi data yang lebih mudah dikelola di production. Akan tetapi, perlu saya tekankan sebelumnya bahwa keamanan enkripsi terutama terletak pada kerahasiaan kunci enkripsi. Dengan Vault, kunci enkripsi disimpan dengan aman. Dengan metode ini, kunci enkripsi disimpan tertulis secara plaintext di server aplikasi. Walaupun kita set permission agar cuma bisa diakses oleh user <code class="language-plaintext highlighter-rouge">root</code>, tetap ini adalah resiko security yang besar. Anyway, setidaknya data sensitif user aplikasi tidak tersimpan dalam format plain text.</p>
<p>Kita akan menggunakan algoritma AES untuk mengenkripsi data dan file milik user. Konsep dasar dan contoh kode programnya sudah pernah kita bahas di <a href="/java/symmetric-encryption-dengan-java/">artikel terdahulu</a>. Di artikel ini, kita akan mengaplikasikan artikel tersebut ke dalam aplikasi Spring Boot.</p>
<!--more-->
<p>Karena sebelumnya kita sudah menggunakan <a href="https://www.youtube.com/watch?v=Ncf0BZKwbHY&list=PL9oC_cq7OYbyxsdDPDSpuURfWRTFQSXDl&index=13">Strategy Pattern</a> untuk memilih strategi penyimpanan data, yaitu Plain dan Vault, maka sekarang kita tinggal membuat satu implementasi lagi, yaitu menggunakan algoritma AES.</p>
<p>Ada dua data yang kita gunakan sebagai contoh di sini:</p>
<ul>
<li>data NIK (Nomor KTP) yang akan kita simpan di tabel database</li>
<li>scan KTP yang akan kita simpan berupa file dalam folder di server</li>
</ul>
<p>Dua bentuk data ini mewakili mayoritas aplikasi bisnis yang biasa kita buat. Ada data yang disimpan berupa kolom di database, dan ada data yang disimpan berupa file. Kita akan mengenkripsi kedua data ini pada waktu disimpan, kemudian melakukan dekripsi pada waktu data tersebut akan ditampilkan di aplikasi. Selain melalui aplikasi kita, data ini tidak bisa langsung dibaca di lokasi penyimpanannya.</p>
<p>Berikut adalah <code class="language-plaintext highlighter-rouge">interface</code> yang mendefinisikan fitur penyimpanan data di aplikasi</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">MemberInputService</span> <span class="o">{</span>
<span class="kt">void</span> <span class="nf">save</span><span class="o">(</span><span class="nc">Member</span> <span class="n">member</span><span class="o">,</span> <span class="nc">MultipartFile</span> <span class="n">fileKtp</span><span class="o">);</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="nf">getFileKtp</span><span class="o">(</span><span class="nc">Member</span> <span class="n">member</span><span class="o">);</span>
<span class="nc">Iterable</span><span class="o"><</span><span class="nc">Member</span><span class="o">></span> <span class="nf">findAllMembers</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Bila kita simpan secara apa adanya tanpa enkripsi, implementasinya sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Profile</span><span class="o">(</span><span class="s">"default"</span><span class="o">)</span>
<span class="nd">@Service</span> <span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PlainMemberService</span> <span class="kd">implements</span> <span class="nc">MemberInputService</span> <span class="o">{</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${file.upload.folder}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">fileUploadFolder</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">MemberDao</span> <span class="n">memberDao</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">MimetypesFileTypeMap</span> <span class="n">fileTypeMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MimetypesFileTypeMap</span><span class="o">();;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">save</span><span class="o">(</span><span class="nc">Member</span> <span class="n">member</span><span class="o">,</span> <span class="nc">MultipartFile</span> <span class="n">fileKtp</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">createDirectories</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">fileUploadFolder</span><span class="o">));</span>
<span class="n">member</span><span class="o">.</span><span class="na">setFileKtpMimeType</span><span class="o">(</span><span class="n">fileTypeMap</span><span class="o">.</span><span class="na">getContentType</span><span class="o">(</span><span class="n">fileKtp</span><span class="o">.</span><span class="na">getOriginalFilename</span><span class="o">()));</span>
<span class="n">memberDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">destinationFilename</span> <span class="o">=</span> <span class="n">fileUploadFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Upload file to {}"</span><span class="o">,</span> <span class="n">destinationFilename</span><span class="o">);</span>
<span class="n">fileKtp</span><span class="o">.</span><span class="na">transferTo</span><span class="o">(</span><span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">destinationFilename</span><span class="o">));</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">getFileKtp</span><span class="o">(</span><span class="nc">Member</span> <span class="n">member</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">fileUploadFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
<span class="k">return</span> <span class="nc">FileUtils</span><span class="o">.</span><span class="na">readFileToByteArray</span><span class="o">(</span><span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">filename</span><span class="o">));</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Iterable</span><span class="o"><</span><span class="nc">Member</span><span class="o">></span> <span class="nf">findAllMembers</span><span class="o">(){</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Member</span><span class="o">></span> <span class="n">memberList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="n">memberDao</span><span class="o">.</span><span class="na">findAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">member</span> <span class="o">-></span> <span class="o">{</span>
<span class="n">member</span><span class="o">.</span><span class="na">setNoKtpPlain</span><span class="o">(</span><span class="n">member</span><span class="o">.</span><span class="na">getNoKtp</span><span class="o">());</span>
<span class="n">memberList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
<span class="o">});</span>
<span class="k">return</span> <span class="n">memberList</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada kode program di atas, kita menulis datanya langsung ke database dan ke folder penyimpanan tanpa enkripsi. Kita bisa langsung melakukan query ke database, atau membuka File Explorer, dan melihat nomor NIK dan scan KTP secara langsung.</p>
<p>Untuk melakukan enkripsi, kita akan membuat satu class helper untuk melakukan fungsi enkripsi. Mayoritas kode program di class ini diambil dari <a href="/java/symmetric-encryption-dengan-java/">artikel Symmetric Encryption dengan Java</a>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CryptoHelper</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">ALGORITHM_KEY</span> <span class="o">=</span> <span class="s">"AES"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">ALGORITHM_ENCRYPTION</span> <span class="o">=</span> <span class="s">"AES/GCM/NoPadding"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">TAG_LENGTH_BIT</span> <span class="o">=</span> <span class="mi">128</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">IV_LENGTH_BYTE</span> <span class="o">=</span> <span class="mi">12</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">AES_KEY_LENGTH</span> <span class="o">=</span> <span class="mi">256</span><span class="o">;</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${aes.encryption.key}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">aesKeyString</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">SecretKey</span> <span class="n">secretKey</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">generateKey</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">NoSuchAlgorithmException</span> <span class="o">{</span>
<span class="nc">KeyGenerator</span> <span class="n">keygen</span> <span class="o">=</span> <span class="nc">KeyGenerator</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="no">ALGORITHM_KEY</span><span class="o">);</span>
<span class="n">keygen</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="no">AES_KEY_LENGTH</span><span class="o">);</span>
<span class="nc">SecretKey</span> <span class="n">key</span> <span class="o">=</span> <span class="n">keygen</span><span class="o">.</span><span class="na">generateKey</span><span class="o">();</span>
<span class="k">return</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">getEncoder</span><span class="o">().</span><span class="na">encodeToString</span><span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">getEncoded</span><span class="o">());</span>
<span class="o">}</span>
<span class="nd">@PostConstruct</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">initialize</span><span class="o">(){</span>
<span class="n">secretKey</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SecretKeySpec</span><span class="o">(</span><span class="nc">Base64</span><span class="o">.</span><span class="na">getDecoder</span><span class="o">().</span><span class="na">decode</span><span class="o">(</span><span class="n">aesKeyString</span><span class="o">),</span> <span class="no">ALGORITHM_KEY</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">encrypt</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">plainContent</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InvalidKeyException</span><span class="o">,</span> <span class="nc">InvalidAlgorithmParameterException</span><span class="o">,</span> <span class="nc">NoSuchAlgorithmException</span><span class="o">,</span> <span class="nc">NoSuchPaddingException</span><span class="o">,</span> <span class="nc">IllegalBlockSizeException</span><span class="o">,</span> <span class="nc">BadPaddingException</span> <span class="o">{</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">iv</span> <span class="o">=</span> <span class="n">generateIV</span><span class="o">();</span>
<span class="nc">Cipher</span> <span class="n">cipher</span> <span class="o">=</span> <span class="nc">Cipher</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="no">ALGORITHM_ENCRYPTION</span><span class="o">);</span>
<span class="n">cipher</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="nc">Cipher</span><span class="o">.</span><span class="na">ENCRYPT_MODE</span><span class="o">,</span> <span class="n">secretKey</span><span class="o">,</span> <span class="k">new</span> <span class="nc">GCMParameterSpec</span><span class="o">(</span><span class="no">TAG_LENGTH_BIT</span><span class="o">,</span> <span class="n">iv</span><span class="o">));</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">cipherText</span> <span class="o">=</span> <span class="n">cipher</span><span class="o">.</span><span class="na">doFinal</span><span class="o">(</span><span class="n">plainContent</span><span class="o">);</span>
<span class="k">return</span> <span class="nc">ByteBuffer</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="n">iv</span><span class="o">.</span><span class="na">length</span> <span class="o">+</span> <span class="n">cipherText</span><span class="o">.</span><span class="na">length</span><span class="o">)</span>
<span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">iv</span><span class="o">)</span>
<span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">cipherText</span><span class="o">)</span>
<span class="o">.</span><span class="na">array</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">decrypt</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">encryptedContent</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchAlgorithmException</span><span class="o">,</span> <span class="nc">NoSuchPaddingException</span><span class="o">,</span> <span class="nc">InvalidKeyException</span><span class="o">,</span> <span class="nc">InvalidAlgorithmParameterException</span><span class="o">,</span> <span class="nc">IllegalBlockSizeException</span><span class="o">,</span> <span class="nc">BadPaddingException</span><span class="o">{</span>
<span class="nc">ByteBuffer</span> <span class="n">bb</span> <span class="o">=</span> <span class="nc">ByteBuffer</span><span class="o">.</span><span class="na">wrap</span><span class="o">(</span><span class="n">encryptedContent</span><span class="o">);</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">iv</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="no">IV_LENGTH_BYTE</span><span class="o">];</span>
<span class="n">bb</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">iv</span><span class="o">);</span>
<span class="nc">Cipher</span> <span class="n">cipher</span> <span class="o">=</span> <span class="nc">Cipher</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="no">ALGORITHM_ENCRYPTION</span><span class="o">);</span>
<span class="n">cipher</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="nc">Cipher</span><span class="o">.</span><span class="na">DECRYPT_MODE</span><span class="o">,</span> <span class="n">secretKey</span><span class="o">,</span> <span class="k">new</span> <span class="nc">GCMParameterSpec</span><span class="o">(</span><span class="no">TAG_LENGTH_BIT</span><span class="o">,</span> <span class="n">iv</span><span class="o">));</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">cipherText</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="n">bb</span><span class="o">.</span><span class="na">remaining</span><span class="o">()];</span>
<span class="n">bb</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">cipherText</span><span class="o">);</span>
<span class="k">return</span> <span class="n">cipher</span><span class="o">.</span><span class="na">doFinal</span><span class="o">(</span><span class="n">cipherText</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">generateIV</span><span class="o">(){</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">nonce</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="no">IV_LENGTH_BYTE</span><span class="o">];</span>
<span class="k">new</span> <span class="nf">SecureRandom</span><span class="o">().</span><span class="na">nextBytes</span><span class="o">(</span><span class="n">nonce</span><span class="o">);</span>
<span class="k">return</span> <span class="n">nonce</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Jangan lupa menambahkan encryption key di konfigurasi. Kita bisa meletakkannya di <code class="language-plaintext highlighter-rouge">application.properties</code> atau dipasang sebagai environment variable di sistem operasi. Berikut konfigurasinya bila kita taruh di <code class="language-plaintext highlighter-rouge">application.properties</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aes.encryption.key=lwSqNtjsMWZfKxRLU6yi08l71TL7G5Ksii1rOoraL7M=
</code></pre></div></div>
<p>Bila ingin dipasang sebagai environment variable, biasanya kita pasang di konfigurasi <code class="language-plaintext highlighter-rouge">systemd</code>, seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Aplikasi KTP
After=syslog.target
[Service]
User=aplikasi
Environment=SPRING_PROFILES_ACTIVE=aeslocal
Environment=AES_ENCRYPTION_KEY=lwSqNtjsMWZfKxRLU6yi08l71TL7G5Ksii1rOoraL7M=
ExecStart=/var/lib/belajar-enkripsi-data/belajar-enkripsi-data.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
</code></pre></div></div>
<p>Selanjutnya, kita tinggal mengimplementasikan interface <code class="language-plaintext highlighter-rouge">MemberInputService</code> sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Profile</span><span class="o">(</span><span class="s">"aeslocal"</span><span class="o">)</span>
<span class="nd">@Service</span> <span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">EncryptedLocalFileMemberService</span> <span class="kd">implements</span> <span class="nc">MemberInputService</span> <span class="o">{</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${file.upload.folder}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">fileUploadFolder</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">CryptoHelper</span> <span class="n">cryptoHelper</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">MemberDao</span> <span class="n">memberDao</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">MimetypesFileTypeMap</span> <span class="n">fileTypeMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MimetypesFileTypeMap</span><span class="o">();</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">save</span><span class="o">(</span><span class="nc">Member</span> <span class="n">member</span><span class="o">,</span> <span class="nc">MultipartFile</span> <span class="n">fileKtp</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">member</span><span class="o">.</span><span class="na">setFileKtpMimeType</span><span class="o">(</span><span class="n">fileTypeMap</span><span class="o">.</span><span class="na">getContentType</span><span class="o">(</span><span class="n">fileKtp</span><span class="o">.</span><span class="na">getOriginalFilename</span><span class="o">()));</span>
<span class="n">member</span><span class="o">.</span><span class="na">setNoKtp</span><span class="o">(</span><span class="nc">Base64</span><span class="o">.</span><span class="na">getEncoder</span><span class="o">().</span><span class="na">encodeToString</span><span class="o">(</span>
<span class="n">cryptoHelper</span><span class="o">.</span><span class="na">encrypt</span><span class="o">(</span><span class="n">member</span><span class="o">.</span><span class="na">getNoKtp</span><span class="o">().</span><span class="na">getBytes</span><span class="o">())));</span>
<span class="n">memberDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
<span class="nc">Path</span> <span class="n">destination</span> <span class="o">=</span> <span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">fileUploadFolder</span><span class="o">,</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Storing {} at {}"</span><span class="o">,</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">destination</span><span class="o">.</span><span class="na">toAbsolutePath</span><span class="o">());</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">createDirectories</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">fileUploadFolder</span><span class="o">));</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">destination</span><span class="o">,</span> <span class="n">cryptoHelper</span><span class="o">.</span><span class="na">encrypt</span><span class="o">(</span><span class="n">fileKtp</span><span class="o">.</span><span class="na">getBytes</span><span class="o">()));</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="o">|</span> <span class="nc">InvalidKeyException</span> <span class="o">|</span> <span class="nc">InvalidAlgorithmParameterException</span> <span class="o">|</span> <span class="nc">NoSuchAlgorithmException</span> <span class="o">|</span> <span class="nc">NoSuchPaddingException</span> <span class="o">|</span> <span class="nc">IllegalBlockSizeException</span> <span class="o">|</span> <span class="nc">BadPaddingException</span> <span class="n">err</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">err</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">err</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">getFileKtp</span><span class="o">(</span><span class="nc">Member</span> <span class="n">member</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">fileUploadFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
<span class="k">return</span> <span class="n">cryptoHelper</span><span class="o">.</span><span class="na">decrypt</span><span class="o">(</span><span class="nc">FileUtils</span><span class="o">.</span><span class="na">readFileToByteArray</span><span class="o">(</span><span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">filename</span><span class="o">)));</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="o">|</span> <span class="nc">InvalidKeyException</span> <span class="o">|</span> <span class="nc">NoSuchAlgorithmException</span> <span class="o">|</span> <span class="nc">NoSuchPaddingException</span> <span class="o">|</span> <span class="nc">InvalidAlgorithmParameterException</span> <span class="o">|</span> <span class="nc">IllegalBlockSizeException</span> <span class="o">|</span> <span class="nc">BadPaddingException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Iterable</span><span class="o"><</span><span class="nc">Member</span><span class="o">></span> <span class="nf">findAllMembers</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Member</span><span class="o">></span> <span class="n">memberList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="n">memberDao</span><span class="o">.</span><span class="na">findAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">member</span> <span class="o">-></span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">member</span><span class="o">.</span><span class="na">setNoKtpPlain</span><span class="o">(</span><span class="k">new</span> <span class="nc">String</span><span class="o">(</span>
<span class="n">cryptoHelper</span><span class="o">.</span><span class="na">decrypt</span><span class="o">(</span>
<span class="nc">Base64</span><span class="o">.</span><span class="na">getDecoder</span><span class="o">().</span><span class="na">decode</span><span class="o">(</span><span class="n">member</span><span class="o">.</span><span class="na">getNoKtp</span><span class="o">()))));</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvalidKeyException</span> <span class="o">|</span> <span class="nc">NoSuchAlgorithmException</span> <span class="o">|</span> <span class="nc">NoSuchPaddingException</span>
<span class="o">|</span> <span class="nc">InvalidAlgorithmParameterException</span> <span class="o">|</span> <span class="nc">IllegalBlockSizeException</span> <span class="o">|</span> <span class="nc">BadPaddingException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span><span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">memberList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
<span class="o">});</span>
<span class="k">return</span> <span class="n">memberList</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kalau diperhatikan, perbedaannya dengan yang versi plaintext cuma sedikit. Contohnya, pada waktu menyimpan data, berikut kode program tanpa enkripsi</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">memberDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">destinationFilename</span> <span class="o">=</span> <span class="n">fileUploadFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
<span class="n">fileKtp</span><span class="o">.</span><span class="na">transferTo</span><span class="o">(</span><span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">destinationFilename</span><span class="o">));</span>
</code></pre></div></div>
<p>Versi enkripsinya sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">member</span><span class="o">.</span><span class="na">setNoKtp</span><span class="o">(</span><span class="nc">Base64</span><span class="o">.</span><span class="na">getEncoder</span><span class="o">().</span><span class="na">encodeToString</span><span class="o">(</span>
<span class="n">cryptoHelper</span><span class="o">.</span><span class="na">encrypt</span><span class="o">(</span><span class="n">member</span><span class="o">.</span><span class="na">getNoKtp</span><span class="o">().</span><span class="na">getBytes</span><span class="o">())));</span>
<span class="n">memberDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
<span class="nc">Path</span> <span class="n">destination</span> <span class="o">=</span> <span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">fileUploadFolder</span><span class="o">,</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">destination</span><span class="o">,</span> <span class="n">cryptoHelper</span><span class="o">.</span><span class="na">encrypt</span><span class="o">(</span><span class="n">fileKtp</span><span class="o">.</span><span class="na">getBytes</span><span class="o">()));</span>
</code></pre></div></div>
<p>Pengambilan file tanpa enkripsi, sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">fileUploadFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
<span class="k">return</span> <span class="nc">FileUtils</span><span class="o">.</span><span class="na">readFileToByteArray</span><span class="o">(</span><span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">filename</span><span class="o">));</span>
</code></pre></div></div>
<p>Berikut pengambilan file yang terenkripsi</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">fileUploadFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="n">member</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
<span class="k">return</span> <span class="n">cryptoHelper</span><span class="o">.</span><span class="na">decrypt</span><span class="o">(</span><span class="nc">FileUtils</span><span class="o">.</span><span class="na">readFileToByteArray</span><span class="o">(</span><span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">filename</span><span class="o">)));</span>
</code></pre></div></div>
<p>Untuk membaca data dari database, tanpa enkripsi bisa langsung diambil</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o"><</span><span class="nc">Member</span><span class="o">></span> <span class="n">memberList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="n">memberDao</span><span class="o">.</span><span class="na">findAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">member</span> <span class="o">-></span> <span class="o">{</span>
<span class="n">member</span><span class="o">.</span><span class="na">setNoKtpPlain</span><span class="o">(</span><span class="n">member</span><span class="o">.</span><span class="na">getNoKtp</span><span class="o">());</span>
<span class="n">memberList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
<span class="o">});</span>
<span class="k">return</span> <span class="n">memberList</span><span class="o">;</span>
</code></pre></div></div>
<p>Kalau datanya terenkripsi, maka kita dekripsi dulu</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o"><</span><span class="nc">Member</span><span class="o">></span> <span class="n">memberList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="n">memberDao</span><span class="o">.</span><span class="na">findAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">member</span> <span class="o">-></span> <span class="o">{</span>
<span class="n">member</span><span class="o">.</span><span class="na">setNoKtpPlain</span><span class="o">(</span><span class="k">new</span> <span class="nc">String</span><span class="o">(</span>
<span class="n">cryptoHelper</span><span class="o">.</span><span class="na">decrypt</span><span class="o">(</span>
<span class="nc">Base64</span><span class="o">.</span><span class="na">getDecoder</span><span class="o">().</span><span class="na">decode</span><span class="o">(</span><span class="n">member</span><span class="o">.</span><span class="na">getNoKtp</span><span class="o">()))));</span>
<span class="n">memberList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
<span class="o">});</span>
<span class="k">return</span> <span class="n">memberList</span><span class="o">;</span>
</code></pre></div></div>
<p>Kalau kita lihat isi database, berikut isinya bila kita tidak lakukan enkripsi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/01-query-tanpa-enkripsi.png"><img src="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/01-query-tanpa-enkripsi.png" alt="Isi database tanpa enkripsi" /></a></p>
<p>Dan ini hasil querynya bila nomor KTP kita enkripsi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/02-query-database-dengan-enkripsi.png"><img src="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/02-query-database-dengan-enkripsi.png" alt="Isi database dengan enkripsi" /></a></p>
<p>Sedangkan untuk file yang diupload, tanpa enkripsi kita bisa langsung buka di file explorer</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/03-file-upload-tanpa-enkripsi.png"><img src="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/03-file-upload-tanpa-enkripsi.png" alt="File upload tanpa enkripsi" /></a></p>
<p>Tapi kalau dienkripsi, tidak bisa dilihat langsung tanpa melalui aplikasi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/04-file-upload-dengan-enkripsi.png"><img src="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/04-file-upload-dengan-enkripsi.png" alt="File upload dengan enkripsi" /></a></p>
<p>Lewat aplikasi, kita bisa lihat datanya, baik nomor KTP maupun file yang diupload</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/05-tampilan-aplikasi.png"><img src="https://software.endy.muhardin.com/images/uploads/2022/enkripsi-aes-data-aplikasi/05-tampilan-aplikasi.png" alt="Tampilan Aplikasi" /></a></p>
<p>Demikian cara mengamankan data user yang diamanahkan kepada kita. Namanya kena hack itu musibah, sudah takdir Allah. Tugas kita sebagai programmer, berusaha semampunya untuk mengamankan data.</p>
<p>Kode program lengkap bisa <a href="https://github.com/endymuhardin/belajar-enkripsi-data">diambil di Github</a>. Semoga bermanfaat</p>
External Services dengan Docker Compose2022-04-06T08:05:00+07:00https://software.endy.muhardin.com/aplikasi/docker-compose-external-services<p>Di jaman sekarang, aplikasi yang saya buat umumnya sudah mengadopsi arsitektur <code class="language-plaintext highlighter-rouge">Cloud Native</code>, yaitu aplikasi yang bisa berjalan dengan baik di environment cloud. Penjelasan detail tentang aplikasi <code class="language-plaintext highlighter-rouge">Cloud Native</code> ini sudah pernah saya jelaskan di Youtube.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/nTq7o18ij-M" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Singkatnya, aplikasi cloud native biasanya menggunakan beberapa external services, misalnya:</p>
<ul>
<li>Database relasional (MySQL, PostgreSQL, dsb)</li>
<li>Message Broker (Kafka, dsb)</li>
<li>Database non-relasional / NoSQL (Redis, Elasticsearch, dsb)</li>
</ul>
<p>External service ini harus bisa diganti dengan implementasi lain, misalnya dari local ke cloud, dari cloud ke on-premise, dan berbagai skenario lainnya.</p>
<p>Agar kita bisa develop aplikasi dengan external service tersebut, maka development environment kita (misalnya PC atau laptop) harus menyediakan service yang dibutuhkan. Kebutuhan ini berbeda-beda antar project. Misal di project A saya menggunakan MySQL. Project B menggunakan PostgreSQL. Project C menggunakan PostgreSQL dan Kafka. Project D menggunakan MySQL dan Redis. Project E menggunakan ElasticSearch dan MongoDB. Dan seterusnya.</p>
<p>Apabila kita harus menginstal semua service tersebut, dan jalan pada waktu booting, waduh berapa RAM yang harus kita sediakan. Belum lagi nanti kita harus membuatkan database instance untuk masing-masing aplikasi. Bisa-bisa kita instal MySQL yang isinya belasan database sesuai dengan project yang pernah kita tangani.</p>
<p>Ini juga menjadi lebih sulit buat para team leader atau arsitek yang harus melakukan supervisi ke banyak project sekaligus. Oleh karena itu, kita perlu membuat sistem kerja yang baik agar tidak kusut.</p>
<p>Solusi yang biasa saya gunakan, terdiri dari 3 aspek:</p>
<ol>
<li>Project harus portable</li>
<li>Project harus self-contained</li>
<li>External service yang dibutuhkan, harus dideklarasikan dengan konsep Infrastructure as a Code</li>
</ol>
<p>Mari kita elaborasi …</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#project-portability" id="markdown-toc-project-portability">Project Portability</a></li>
<li><a href="#project-self-containment" id="markdown-toc-project-self-containment">Project Self Containment</a></li>
<li><a href="#infrastructure-as-a-code" id="markdown-toc-infrastructure-as-a-code">Infrastructure as a Code</a></li>
</ul>
<h2 id="project-portability">Project Portability</h2>
<p>Project yang kita kerjakan, harus bisa langsung dibuka dan dibuild di semua environment (Windows, Linux, Mac) sejak kita melakukan <code class="language-plaintext highlighter-rouge">git clone</code>. Semua library yang dibutuhkan harus bisa didapatkan dari repository, baik yang publik di internet, ataupun di server internal kita. Di Java, ini bisa dilakukan dengan menggunakan Maven atau Gradle. Apa itu Maven, bisa dibaca <a href="% post_url 2015-02-24-apache-maven %">di artikel ini</a> atau ditonton <a href="https://www.youtube.com/embed/4bMPYQKHlfc">di video ini</a>.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/4bMPYQKHlfc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Dengan menggunakan Maven atau Gradle, maka project kita akan bisa dibuka di segala editor, misalnya Visual Studio Code, Intellij IDEA, Netbeans, Eclipse, dan sebagainya.</p>
<h2 id="project-self-containment">Project Self Containment</h2>
<p>Project kita harus bisa dijalankan (run) di semua environment, hanya dengan bermodalkan hasil <code class="language-plaintext highlighter-rouge">git clone</code>. Ini artinya yang kita simpan di source repository bukan cuma source code. Tapi juga file lain seperti:</p>
<ul>
<li>konfigurasi koneksi database</li>
<li>script untuk membuat database</li>
<li>data-data awal agar aplikasi kita bisa dijalankan (misalnya data kota/kabupaten/kecamatan, data tingkat pendidikan, dsb)</li>
<li>file pendukung lain seperti image untuk icon, API key, credential file, dan sebagainya</li>
</ul>
<p>Semua file-file di atas harus ikut disimpan di source repository, biasanya jaman now orang pakai Git. Kemudian, script pembuatan database dan pengisian data awal juga harus otomatis dijalankan pada waktu aplikasi di run. Tidak boleh ada kegiatan manual orang login ke database dan menjalankan perintah-perintah. Karena kalau ada kegiatan manual, akan ada orang yang lupa, langkah yang ketinggalan, salah ketik, dan human error lain yang mengakibatkan aplikasi gagal jalan.</p>
<p>Di Java biasanya kita menggunakan migration tools seperti <a href="https://flywaydb.org/">FlywayDB</a> atau <a href="https://liquibase.org/">Liquibase</a>.</p>
<h2 id="infrastructure-as-a-code">Infrastructure as a Code</h2>
<p>Berikutnya kita bahas tentang external services. Jaman dulu, saya menginstal MySQL dan PostgreSQL di laptop, walaupun diset tidak start pada waktu boot. Akan tetapi, ini cukup merepotkan, karena untuk tiap project yang akan dijalankan, saya harus create user dan create database dulu. Nama database, username/password database harus disesuaikan dengan konfigurasi masing-masing project. Demikian juga versinya. MySQL versi 5 tidak kompatibel dengan versi 8. Padahal belum tentu semua project bisa dinaikkan ke MySQL 8. Jadinya kita harus mencatat aplikasi mana pakai MySQL versi berapa.</p>
<p>Ada cara yang lebih efektif daripada membuat catatan tersebut, yaitu langsung saja kita tuliskan dalam bentuk file <code class="language-plaintext highlighter-rouge">docker-compose</code>. Dengan demikian, file ini bisa langsung kita jalankan. Docker akan mengunduh versi yang sesuai, kemudian membuatkan database dengan nama, username, dan password yang kita tentukan.</p>
<p>Berikut adalah contoh file <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> untuk database MySQL</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
<span class="na">db-timezone</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">mysql</span>
<span class="na">platform</span><span class="pi">:</span> <span class="s">linux/x86_64</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">MYSQL_RANDOM_ROOT_PASSWORD=yes</span>
<span class="pi">-</span> <span class="s">MYSQL_DATABASE=timezonedb</span>
<span class="pi">-</span> <span class="s">MYSQL_USER=timezone</span>
<span class="pi">-</span> <span class="s">MYSQL_PASSWORD=timezone123</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">3306:3306</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./db-timezone:/var/lib/mysql</span>
</code></pre></div></div>
<p>Ini untuk database PostgreSQL</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
<span class="na">db-authserver</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">postgres</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">POSTGRES_DB=authserver-db</span>
<span class="pi">-</span> <span class="s">POSTGRES_USER=authserver</span>
<span class="pi">-</span> <span class="s">POSTGRES_PASSWORD=authserver123</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">5432:5432</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./db-authserver:/var/lib/postgresql/data</span>
</code></pre></div></div>
<p>Bila aplikasi kita butuh PostgreSQL dan Kafka sekaligus, tinggal kita pasang keduanya seperti ini</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
<span class="na">db-muamalat-va</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">postgres:14</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">POSTGRES_DB=muamalat-va-db</span>
<span class="pi">-</span> <span class="s">POSTGRES_USER=muamalat</span>
<span class="pi">-</span> <span class="s">POSTGRES_PASSWORD=muamalat123</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">5432:5432</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./db-muamalat-va:/var/lib/postgresql/data</span>
<span class="na">zookeeper</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">confluentinc/cp-zookeeper</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">zookeeper</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">ZOOKEEPER_CLIENT_PORT</span><span class="pi">:</span> <span class="m">2181</span>
<span class="na">ZOOKEEPER_TICK_TIME</span><span class="pi">:</span> <span class="m">2000</span>
<span class="na">kafka</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">confluentinc/cp-kafka</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">kafka-broker</span>
<span class="na">depends_on</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">zookeeper</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">9092:9092</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">KAFKA_BROKER_ID</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">KAFKA_ZOOKEEPER_CONNECT</span><span class="pi">:</span> <span class="s">zookeeper:2181</span>
<span class="na">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP</span><span class="pi">:</span> <span class="s">PLAINTEXT:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT</span>
<span class="na">KAFKA_ADVERTISED_LISTENERS</span><span class="pi">:</span> <span class="s">PLAINTEXT://localhost:9092,PLAINTEXT_INTERNAL://broker:29092</span>
<span class="na">KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">KAFKA_TRANSACTION_STATE_LOG_MIN_ISR</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR</span><span class="pi">:</span> <span class="m">1</span>
</code></pre></div></div>
<p>Dengan adanya <code class="language-plaintext highlighter-rouge">docker-compose</code> ini, kita tinggal menjalankan perintah <code class="language-plaintext highlighter-rouge">docker-compose up</code> di masing-masing folder project. Dia akan langsung mengunduh versi yang sesuai, membuatkan database, username/password, dan kita tinggal akses di port yang ditentukan. Setelah selesai, tekan <code class="language-plaintext highlighter-rouge">Ctrl-C</code>, kemudian <code class="language-plaintext highlighter-rouge">docker-compose down</code>, dan hapus file databasenya.</p>
<p>Jangan lupa daftarkan folder yang berisi data testing tadi (<code class="language-plaintext highlighter-rouge">db-timezone</code>, <code class="language-plaintext highlighter-rouge">db-authserver</code>) ke <code class="language-plaintext highlighter-rouge">.gitignore</code> supaya tidak ikut tersimpan di git.</p>
<p>Demikian tips untuk menyiapkan development environment agar kita mudah menangani banyak project sekaligus. Selamat mencoba, semoga bermanfaat …</p>
Instalasi Netbeans di MacOS2021-11-25T08:05:00+07:00https://software.endy.muhardin.com/java/instalasi-netbeans-macos<p>Sebetulnya di tahun 2021 ini, saya sudah lama tidak menggunakan Netbeans. Terakhir pakai di versi 8, dan sekarang sudah versi 11. Tapi ya apa boleh buat, ada client yang pengen pakai ini dan tanya bagaimana caranya. Dengan semangat <code class="language-plaintext highlighter-rouge">maju tak gentar membela yang bayar</code>, baiklah mari kita install.</p>
<p>Biasanya, instalasi Netbeans tinggal next-next saja sampai finish. Tapi karena Java di laptop saya diinstal menggunakan <a href="https://sdkman.io">SDKMAN</a>, maka installernya Netbeans macet dengan pesan error berikut ini</p>
<!--more-->
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/01-netbeans-install-error.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/01-netbeans-install-error.png" alt="Instalasi Netbeans Error" /></a></p>
<p>Error di atas disebabkan karena lokasi instalasi Java dihardcode di dalam installer. Untuk mengatasinya, kita perlu mengedit satu file dalam installer. Pertama, kita extract dulu installernya dengan perintah berikut ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pkgutil --expand Apache\ NetBeans\ 12.5.pkg pkg-extract
</code></pre></div></div>
<p>Setelah itu, kita cari file Distribution di folder yang sudah diextract</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/03-hasil-extract.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/03-hasil-extract.png" alt="Extract Installer" /></a></p>
<p>Cari baris yang melakukan pengecekan versi Java seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function checkSystem() {
var java_result = checkJavaVersion();
if (!java_result) {
return java_result;
}
var mem_result = checkMemsize();
return mem_result;
}
</code></pre></div></div>
<p>Langsung saja kita hardcode menjadi <code class="language-plaintext highlighter-rouge">true</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function checkSystem() {
var java_result = true;
if (!java_result) {
return java_result;
}
var mem_result = checkMemsize();
return mem_result;
}
</code></pre></div></div>
<p>Setelah itu kita bungkus kembali menjadi installer.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pkgutil --flatten pkg-extract netbeans-installer-fixed.pkg
</code></pre></div></div>
<p>Hasilnya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/04-repackage-installer.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/04-repackage-installer.png" alt="Extract Installer" /></a></p>
<p>Kita bisa coba install kembali dengan paket installer yang sudah kita perbaiki. Bisa dengan cara dobel-klik, atau bisa juga menggunakan command line sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/bin/sudo -E -- /usr/bin/env LOGNAME=endymuhardin USER=endymuhardin USERNAME=endymuhardin /usr/sbin/installer -pkg netbeans-installer-fixed.pkg -target /
</code></pre></div></div>
<p>Pada saat dijalankan, hasilnya masih error, seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/05-install-masih-error.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/05-install-masih-error.png" alt="Installer masih error" /></a></p>
<p>Walaupun gagal, akan tetapi filenya sudah berhasil ter-extract di folder <code class="language-plaintext highlighter-rouge">/Applications</code>. Kita bisa buka isi aplikasinya dengan klik kanan, kemudian pilih <code class="language-plaintext highlighter-rouge">Show Package Contents</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/06-show-package-content.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/06-show-package-content.png" alt="Show Package Content" /></a></p>
<p>Ada satu file lagi yang perlu kita edit, yaitu file <code class="language-plaintext highlighter-rouge">netbeans.conf</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/07-edit-netbeans-conf.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/netbeans-install-macos/07-edit-netbeans-conf.png" alt="Edit netbeans.conf" /></a></p>
<p>File ini tidak bisa kita edit secara langsung, karena lokasinya di folder terproteksi. Oleh karena itu, copy dulu ke lokasi lain, baru diedit.</p>
<p>Atau bisa juga langsung diedit menggunakan <code class="language-plaintext highlighter-rouge">vim</code> kalau mau lebih cepat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo vim /Applications/NetBeans/Apache\ NetBeans\ 12.5.app/Contents/Resources/NetBeans/netbeans/etc/netbeans.conf
</code></pre></div></div>
<p>Ubah isi <code class="language-plaintext highlighter-rouge">netbeans_jdkhome</code> menjadi sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>netbeans_jdkhome="/Users/endymuhardin/.sdkman/candidates/java/current"
</code></pre></div></div>
<p>Jangan lupa menghapus karakter <code class="language-plaintext highlighter-rouge">#</code> di awal baris. Setelah diedit, copy balik filenya ke lokasi semula. Bila menggunakan <code class="language-plaintext highlighter-rouge">vim</code>, bisa langsung ketik <code class="language-plaintext highlighter-rouge">Esc :wq</code> untuk save dan quit.</p>
<p>Selanjutnya, kita bisa menjalankan aplikasi Netbeans seperti biasa. Kita juga bisa mengganti versi Java SDK ke versi 11 dengan SDKMAN. Lihat dulu versi java yang tersedia</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sdk list java
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>================================================================================
Available Java Versions
================================================================================
Vendor | Use | Version | Dist | Status | Identifier
--------------------------------------------------------------------------------
Corretto | | 17.0.1.12.1 | amzn | | 17.0.1.12.1-amzn
| | 17.0.0.35.2 | amzn | | 17.0.0.35.2-amzn
Java.net | | 18.ea.24 | open | | 18.ea.24-open
| | 18.ea.5.lm | open | | 18.ea.5.lm-open
| | 17 | open | | 17-open
| | 17.0.1 | open | | 17.0.1-open
Liberica | | 17.0.1.fx | librca | | 17.0.1.fx-librca
| | 17.0.1 | librca | | 17.0.1-librca
| | 17.0.0.fx | librca | | 17.0.0.fx-librca
| | 17.0.0 | librca | | 17.0.0-librca
| | 16.0.2 | librca | | 16.0.2-librca
| | 11.0.13 | librca | | 11.0.13-librca
| | 11.0.12 | librca | | 11.0.12-librca
| | 8.0.312 | librca | | 8.0.312-librca
| | 8.0.302 | librca | | 8.0.302-librca
Microsoft | | 17.0.1 | ms | | 17.0.1-ms
| | 17.0.0 | ms | | 17.0.0-ms
| | 16.0.2.7.1 | ms | | 16.0.2.7.1-ms
Oracle | | 17.0.1 | oracle | | 17.0.1-oracle
| | 17.0.0 | oracle | | 17.0.0-oracle
SapMachine | | 17 | sapmchn | | 17-sapmchn
| | 17.0.1 | sapmchn | | 17.0.1-sapmchn
Temurin | | 17.0.1 | tem | | 17.0.1-tem
| | 17.0.0 | tem | | 17.0.0-tem
Zulu | | 17.0.1 | zulu | | 17.0.1-zulu
| | 17.0.1.fx | zulu | | 17.0.1.fx-zulu
| | 17.0.0 | zulu | | 17.0.0-zulu
| | 17.0.0.fx | zulu | | 17.0.0.fx-zulu
| | 16.0.2 | zulu | | 16.0.2-zulu
| | 11.0.13 | zulu | | 11.0.13-zulu
| | 11.0.12 | zulu | installed | 11.0.12-zulu
| | 11.0.11 | zulu | local only | 11.0.11-zulu
| >>> | 8.0.312 | zulu | installed | 8.0.312-zulu
| | 8.0.302 | zulu | | 8.0.302-zulu
| | 8.0.282 | zulu | local only | 8.0.282-zulu
================================================================================
Omit Identifier to install default version 17.0.1-tem:
$ sdk install java
Use TAB completion to discover available versions
$ sdk install java [TAB]
Or install a specific version by Identifier:
$ sdk install java 17.0.1-tem
Hit Q to exit this list view
================================================================================
</code></pre></div></div>
<p>Kita bisa pindah ke Java 11 dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sdk default java 11.0.12-zulu
</code></pre></div></div>
<p>Outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Default java version set to 11.0.12-zulu
</code></pre></div></div>
<p>Kemudian kita bisa restart Netbeans agar menggunakan Java 11.</p>
<p>Demikianlah sekilas info tentang cara instalasi Netbeans. Mudah-mudahan developer Netbeans segera insyaf dan bertobat atas kesalahan kode program installer ini.</p>
Internet Ngebut dengan WAN Bonding2021-11-04T08:05:00+07:00https://software.endy.muhardin.com/networking/internet-ngebut-wan-bonding<p>Sebagai orang IT, kita selalu dipanggil apabila ada hal-hal yang berkaitan dengan komputer. Baik itu printer, proyektor, apalagi internet. Ditambah lagi di jaman ini, meeting online sudah menjadi kebutuhan dasar di semua institusi. Apabila terjadi gangguan internet, maka kita orang IT adalah yang paling pertama dipanggil.</p>
<p>Sejak sebelum jaman Zoom/GMeet/dsb, saya sebetulnya sudah sering juga menangani live streaming event. Salah satu artikel di blog ini <a href="% post_url 2018-11-12-live-stream-nginx-rtmp %">membahas tentang cara melakukan live streaming berbarengan ke banyak platform sekaligus</a>. Ada videonya juga, yang bisa ditonton di sini</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Rgs67PA6EsQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Salah satu masalah yang kita alami pada waktu melakukan live streaming, apalagi di lokasi event, adalah ketersediaan internet. Kita belum tentu dapat akses internet gedung yang biasanya menggunakan fiber optik dan kabel, stabil dan kencang. Biasanya kita mengandalkan koneksi modem wifi (mifi) yang menggunakan koneksi seluler. Beberapa rekan bahkan membeli wajan mikrotik untuk memperkuat sinyal.</p>
<p>Walaupun demikian, kita tetap mengalami kepanikan apabila provider seluler yang kita gunakan ternyata sedang gangguan. Mau pakai wajan selebar apapun, kalau providernya sedang bermasalah, ya tentu tidak akan menolong.</p>
<p>Selain itu, kondisi sinyal di lokasi kita tidak bisa pastikan. Ada lokasi yang Telkomsel berjaya, XL tiarap. Tapi di lokasi lain, justru XL ngebut, Indosat ngesot. Jadi kita harus gonta-ganti simcard sesuai yang sinyal yang bagus.</p>
<p>Jadi, akhirnya saya mencari cara untuk menggabungkan beberapa provider seluler sehingga tidak terlalu manual gonta-ganti modem dan simcard.</p>
<p>Setelah menjelajah dunia maya bertahun-tahun – sebetulnya saya mulai kepikiran masalah ini sejak awal live streaming di tahun 2019 dulu – akhirnya saya menemukan solusi yang sesuai dengan keinginan saya.</p>
<video loop="" autoplay="" muted="">
<source src="/images/uploads/2021/wan-bonding/speedtest.mp4" type="video/mp4" />
</video>
<!--more-->
<hr />
<h2 id="berbagai-metode-multi-wan">Berbagai Metode Multi WAN</h2>
<p>Yang akan kita buat ini disebut dengan istilah Multi WAN, artinya multiple wide area network. Yaitu menggunakan beberapa koneksi internet (WAN) sekaligus. Misalnya kita gunakan 3 provider : Telkomsel, Indosat, XL. Ada beberapa cara atau skema untuk menggabungkan 3 provider ini, yaitu :</p>
<ul>
<li>Failover</li>
<li>Load Balancing</li>
<li>Bonding</li>
</ul>
<p>Dengan mekanisme failover, kita memilih salah satu koneksi yang paling bagus kualitasnya (bandwidth dan latency) dan itulah yang kita gunakan untuk berinternet. Dua koneksi lain standby tidak digunakan selama koneksi utama lancar jaya. Ketika koneksi utama bermasalah, maka perangkat router akan otomatis pindah ke koneksi cadangan. Tanpa kita harus gonta ganti simcard, cabut colok modem, ataupun sekedar ganti WiFi.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-failover.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-failover.png" alt="Skema Failover" /></a></p>
<p>Mekanisme failover ini memang bisa untuk mengatasi koneksi yang bermasalah di tengah acara. Akan tetapi relatif mubazir, karena ada 2 koneksi lain yang hanya nganggur standby saja. Oleh karenanya, orang mencari alternatif yang lebih baik, yaitu load balancing.</p>
<p>Dengan load balancing, router kita lebih pintar. Dia akan melihat setiap alamat website yang dituju, kemudian memilihkan provider yang sedikit bebannya. Keterbatasan dari load balancing ini adalah, sekali dia sudah mengarahkan koneksi melalui provider tertentu, maka semua request ke tujuan tersebut akan selalu diarahkan melalui provider tersebut. Misalnya, kita live streaming ke Youtube. Pada waktu koneksi pertama, router akan memilihkan provider untuk menuju Youtube, misalnya dia memilihkan XL. Maka semua request ke Youtube dari komputer kita, akan selalu dilewatkan melalui XL. Bila kita buka website lain, misalnya Instagram, barulah dipilihkan jalur lain, misalnya Indosat.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-load-balance.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-load-balance.png" alt="Skema Load Balancing" /></a></p>
<p>Oleh karena itu, metode ini walaupun cocok buat rumahan atau kantoran, dimana banyak orang mengakses ke banyak website, tapi kurang cocok buat live streaming. Pada live streaming, kita hanya kirim data ke sedikit tujuan, misal ke Youtube saja, atau Youtube dan Facebook. Biasanya maksimal tiga tujuan : Youtube Live, Facebook Live, dan Instagram Live. Jadi koneksinya tidak bisa pindah-pindah provider. Misal di tengah jalan tiba-tiba koneksi Telkomsel drop, dia tidak bisa langsung pindah ke Indosat.</p>
<p>Sebetulnya tujuannya bagus, supaya si website tujuan tidak heran melihat satu user pindah-pindah asal requestnya. Terutama di website yang memeriksa asal usul request seperti internet banking. Bila request login datang dari Telkomsel, request cek saldo datang dari Indosat, request transfer datang dari XL, maka dia akan menganggap ini adalah request mencurigakan. Bisa-bisa akun kita diblokir untuk mencegah fraud. Supaya website tujuan tidak bingung, maka router mencatat tujuan setiap request dilewatkan provider mana. Dan request berikutnya akan dilewatkan melalui provider yang sama.</p>
<p>Berikut beberapa referensi untuk melakukan load balancing:</p>
<ul>
<li><a href="https://blog.kopijahe.my.id/posts/openwrt-mwan3/">Tutorial Load Balancing di OpenWRT</a></li>
<li><a href="https://www.youtube.com/watch?v=WABqrwXULlk">LoadBalance 4 ISP di TPLink MR3420</a></li>
</ul>
<p>Selanjutnya, kita akan membahas metode ketiga, yaitu bonding. Mekanisme bonding ini memungkinkan kita menggunakan semua provider secara merata, tanpa membingungkan website tujuan karena gonta-ganti provider. Cara kerjanya adalah kita memasang satu gateway di internet untuk berfungsi sebagai pintu keluar. Skemanya bisa digambarkan seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-bonding.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-bonding.png" alt="Skema Bonding" /></a></p>
<p>Pada gambar di atas, kita memiliki satu VPS yang kita sewa di cloud untuk menggabungkan semua paket data dari semua provider. Setelah digabungkan, barulah request diteruskan ke website tujuan. Dengan demikian, website tujuan melihat semua request datang dari asal yang sama, yaitu dari VPS. Setupnya memang lebih sulit daripada metode failover dan load balancing, karena kita harus setup dua titik, yaitu router dan VPS. Dengan metode failover dan load balancing, kita cukup setup router saja. Dengan bonding ini, kita harus setup router untuk mendistribusikan ke semua provider, kemudian harus setup VPS untuk menggabungkan lagi paket data dari semua provider tersebut.</p>
<p>Ada beberapa metode untuk membuat bonding, yaitu:</p>
<ul>
<li>VPN</li>
<li>Multipath TCP</li>
</ul>
<p>Untuk metode VPN, bisa dicari tutorialnya dengan keyword <code class="language-plaintext highlighter-rouge">openvpn bonding</code>. Banyak tutorial di internet mengenai topik ini, baik berupa tulisan maupun video. Salah satunya tutorial OneMarcFifty ini</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/I08A4-PWawk" title="OpenVPN Bonding" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Kita tidak membahas metode VPN ini, karena setupnya terlalu sulit. Saya mencari sesuatu yang bisa dirakit dan dibongkar dalam waktu cepat. Maksimal 2 jam untuk setup.</p>
<p>Nah, kemudian saya menemukan video tutorial OneMarcFifty yang membahas OpenMPTCPRouter. Berikut videonya</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/mYYoIDCWszo" title="Setup OpenMPTCPRouter" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Dan <a href="https://milankragujevic.com/openmptcprouter-true-bonding-of-2-wan-connections-for-cheap">artikel yang membahas langkah-langkah implementasinya</a>.</p>
<h3 id="komponen-wan-bonding-dengan-multipath-tcp">Komponen WAN Bonding dengan Multipath TCP</h3>
<p>Kunci sukses dari metode bonding ini adalah pemilihan VPS yang menjadi pintu keluar semua request. Ada beberapa kriteria untuk memilih VPS:</p>
<ul>
<li>kompatibel dengan kernel multipath tcp</li>
<li>koneksi ke VPS tersebut bagus dari lokasi kita</li>
<li>koneksi dari VPS ke internet bagus</li>
</ul>
<p>Dengan pertimbangan tersebut, saya tidak menggunakan DigitalOcean yang biasanya saya pakai. VPS DO hanya ada di Singapura. Dan koneksi ke sana konon sering dihambat oleh provider lokal. Akhirnya saya pakai Google Cloud Platform (GCP) saja, yang sudah ada cabang Jakarta. VPS terkecil yang tersedia, tarifnya $0.01 per jam. Kita bisa setup VPSnya beberapa jam sebelum dipakai, kemudian dihapus lagi setelah selesai.</p>
<p>Selain GCP, kita bisa coba juga provider VPS yang lain seperti Exabytes, IDCloudHost, dan sebagainya. Tapi pada artikel ini, kita contohkan menggunakan GCP. Provider lain harusnya sama saja.</p>
<p>Selain VPS, kita juga harus menyiapkan :</p>
<ul>
<li>Perangkat router yang sudah dilengkapi gigabit ethernet port. Saya pakai Raspberry Pi 4. Sebetulnya pakai versi 3 juga bisa, tapi harus ditambah USB Lan yang sudah gigabit.</li>
<li>Beberapa modem dengan berbagai provider. Pakai provider yang sama bisa juga, tapi resikonya kalau provider tersebut pingsan, maka koneksi kita langsung pingsan semua. Saya pakai 2 modem orbit max dan 1 modem orbit pro. Modem orbit ini bisa diisi kartu sim dari provider lain, tidak hanya telkomsel.</li>
<li>Gigabit Switch. Saya menggunakan TP-Link LS108G.</li>
</ul>
<p>Selanjutnya, kita akan mulai setup dari kiri ke kanan, sesuai skema berikut.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-openmptcprouter.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-openmptcprouter.png" alt="Skema OpenMPTCPRouter" /></a></p>
<p>Tampilan fisiknya setelah dirakit seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/physical-setup.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/physical-setup.jpg" alt="Physical Setup" /></a></p>
<p>Keren kan? Mirip Triple Rashomon-nya Orochimaru 😅</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/orochimaru-sanju-rashomon.gif"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/orochimaru-sanju-rashomon.gif" alt="Orochimaru Rashomon" /></a></p>
<blockquote>
<p>Warning : bagian berikutnya sangat teknis.
Butuh skill Linux Network Administration untuk memahami dan menjalankannya.</p>
</blockquote>
<h2 id="setup-vps">Setup VPS</h2>
<p>Berikut adalah langkah-langkah untuk mempersiapkan VPS di GCP:</p>
<ol>
<li>Membuat Project</li>
<li>Membuat VPS</li>
<li>Setting Firewall</li>
<li>Install paket-paket OpenMPTCP</li>
<li>Mendapatkan informasi konfigurasi</li>
</ol>
<h3 id="membuat-project-google-cloud-platform">Membuat Project Google Cloud Platform</h3>
<p>Project GCP bisa dibuat di <a href="https://console.cloud.google.com">Google Cloud Console</a>. Kita klik tombol Create Project, dan kita isikan nama projectnya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-01-create-project.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-01-create-project.png" alt="Create Project" /></a></p>
<p>Klik Create Project, dan kemudian kita akan mendapatkan notifikasi bahwa projectnya sudah dibuat.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-02-project-created.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-02-project-created.png" alt="Create Project" /></a></p>
<p>Selanjutnya, kita klik Compute Engine > VM Instances untuk melihat daftar VPS dalam project.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-03-create-instance.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-03-create-instance.png" alt="Create Instance" /></a></p>
<p>Di project baru, biasanya Compute Engine belum aktif, sehingga harus kita <code class="language-plaintext highlighter-rouge">Enable</code> dulu.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-04-enable-compute.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-04-enable-compute.png" alt="Enable Compute Engine" /></a></p>
<p>Di sini kita harus mengatur metode pembayaran dulu bila belum ada. Kebetulan saya sudah pernah memasukkan informasi kartu kredit, sehingga di tahap ini bisa langsung lanjut.</p>
<p>Kita akan melihat daftar yang masih kosong, karena memang kita belum membuat VPS apa-apa.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-05-instance-list.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-05-instance-list.png" alt="Daftar VPS" /></a></p>
<h3 id="membuat-vps">Membuat VPS</h3>
<p>Kita klik Create Instance, dan kita akan disajikan halaman input informasi VPS yang akan dibuat. Masukkan nama VPS, dan jangan lupa untuk memilih lokasi data center <code class="language-plaintext highlighter-rouge">Jakarta</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-06-new-instance.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-06-new-instance.png" alt="New VPS" /></a></p>
<p>Untuk ukuran VPS, kita bisa pilih yang paling kecil saja, yaitu <code class="language-plaintext highlighter-rouge">e2-micro</code>. Spesifikasinya sudah memadai untuk sekedar menjadi VPS aggregator. Untuk VPS dengan spesifikasi ini, kita harus membayar $0.01 per jam.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-08-vps-price.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-08-vps-price.png" alt="VPS Price" /></a></p>
<p>Selanjutnya gulung ke bawah, dan pilih Ubuntu versi 20.04 LTS</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-07-select-image.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-07-select-image.png" alt="Ubuntu Image" /></a></p>
<p>Setelah itu, kita bisa langsung saja Create Instance. Dan hasilnya akan terlihat di Instance List</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-09-instance-created.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-09-instance-created.png" alt="Instance Created" /></a></p>
<p>Buat kaum yang tidak pakai GUI, berikut perintah command line untuk membuat VPS dengan konfigurasi di atas.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances create vps-mptcp --project=mptcp-gateway --zone=asia-southeast2-a --machine-type=e2-micro --network-interface=network-tier=PREMIUM,subnet=default --maintenance-policy=MIGRATE --service-account=724546327782-compute@developer.gserviceaccount.com --scopes=https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/trace.append --create-disk=auto-delete=yes,boot=yes,device-name=vps-mptcp,image=projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20211102,mode=rw,size=10,type=projects/mptcp-gateway/zones/asia-southeast2-a/diskTypes/pd-balanced --no-shielded-secure-boot --shielded-vtpm --shielded-integrity-monitoring --reservation-affinity=any
</code></pre></div></div>
<h3 id="setting-firewall">Setting Firewall</h3>
<p>Secara bawaannya (default), GCP menutup semua port. Kita harus membuka port-port yang kita butuhkan satu persatu. Untuk menyederhanakan artikel ini, kita akan buka semua port. Silahkan nanti diulik lagi sendiri bila ingin lebih teliti buka satu persatu yang dibutuhkan saja.</p>
<p>Masuk ke menu Firewall. Kita akan diperlihatkan daftar rule yang berlaku.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-10-firewall-rules.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-10-firewall-rules.png" alt="Firewall Rule List" /></a></p>
<p>Berikut adalah nilai-nilai yang kita isikan. Buat yang sudah paham jaringan, harusnya tidak asing dengan isian ini.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-11-firewall-create-rule-1.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-11-firewall-create-rule-1.png" alt="Firewall Rule Input" /></a></p>
<p>Intinya adalah kita akan membuka semua port untuk protokol TCP dan UDP.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-12-firewall-create-rule-2.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-12-firewall-create-rule-2.png" alt="Firewall Rule Input" /></a></p>
<p>Berikut perintah command line untuk yang tidak suka klik klik</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute --project=mptcp-gateway firewall-rules create allow-all-tcp-udp --description="Semua koneksi ke semua port dengan protokol TCP dan UDP diijinkan" --direction=INGRESS --priority=1000 --network=default --action=ALLOW --rules=all --source-ranges=0.0.0.0/0
</code></pre></div></div>
<p>Hasilnya, kita dapat melihat rule yang barusan dibuat berada di paling atas.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-13-rule-created.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/gcp-13-rule-created.png" alt="Firewall Rule Input" /></a></p>
<h3 id="mengakses-vps-melalui-ssh">Mengakses VPS melalui SSH</h3>
<p>Untuk mengakses VPS, terlebih dulu kita harus masuk ke projectnya. Tampilkan dulu daftar project kita di GCP melalui command line dengan perintah <code class="language-plaintext highlighter-rouge">gcloud</code>. Bila belum menginstallnya, petunjuk setup bisa dibaca di <a href="https://cloud.google.com/sdk/docs/install">website resminya</a></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud projects list
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROJECT_ID NAME PROJECT_NUMBER
belajar-gmail-api belajar-gmail-api 514337769807
belajar-sso-endy belajar-sso 266648357609
mptcp-gateway mptcp-gateway 724546327782
</code></pre></div></div>
<p>Kemudian kita set project kita ke <code class="language-plaintext highlighter-rouge">mptcp-gateway</code> sesuai yang telah kita buat pada langkah sebelumnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud config set project mptcp-gateway
</code></pre></div></div>
<p>Outputnya begini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Updated property [core/project].
</code></pre></div></div>
<p>Sekarang kita bisa lihat instances yang ada di project ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances list
</code></pre></div></div>
<p>VPS yang sudah kita buat tadi akan terlihat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
vps-mptcp asia-southeast2-a e2-micro 10.184.0.2 34.101.134.130 RUNNING
</code></pre></div></div>
<p>Kemudian kita bisa mencoba login SSH menggunakan <code class="language-plaintext highlighter-rouge">gcloud</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute ssh vps-mptcp
</code></pre></div></div>
<p>Kita akan diminta untuk membuat SSH keypair baru, tidak menggunakan SSH key yang sudah ada. Kalau mau menggunakan yang sudah ada bisa juga, silahkan baca dokumentasinya <a href="https://cloud.google.com/compute/docs/connect/add-ssh-keys">di sini</a></p>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WARNING: The private SSH key file for gcloud does not exist.
WARNING: The public SSH key file for gcloud does not exist.
WARNING: You do not have an SSH key for gcloud.
WARNING: SSH keygen will be executed to generate a key.
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/endymuhardin/.ssh/google_compute_engine
Your public key has been saved in /Users/endymuhardin/.ssh/google_compute_engine.pub
The key fingerprint is:
SHA256:nMpVUz8OPWO+QcjpzLY1Cn2vzp89I7juSBz19n1kldg endymuhardin@Endys-MacBook-Pro.local
The key's randomart image is:
+---[RSA 3072]----+
| . |
| o =o .|
| o.=.OE.|
| . o.*.* +.|
| S.. *oB o|
| . o. .o.=.B.|
| o o + ..+|
| . .. o ++|
| .o+..=o=|
+----[SHA256]-----+
Updating project ssh metadata...⠶Updated [https://www.googleapis.com/compute/v1/projects/mptcp-gateway].
Updating project ssh metadata...done.
Waiting for SSH key to propagate.
Warning: Permanently added 'compute.6453105502643430895' (ED25519) to the list of known hosts.
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.11.0-1021-gcp x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu Nov 4 09:02:51 UTC 2021
System load: 0.0 Processes: 104
Usage of /: 17.2% of 9.52GB Users logged in: 0
Memory usage: 20% IPv4 address for ens4: 10.184.0.2
Swap usage: 0%
1 update can be applied immediately.
To see these additional updates run: apt list --upgradable
endymuhardin@vps-mptcp:~$
</code></pre></div></div>
<p>Nah kita sudah berhasil login ke dalam VPS tersebut. Selanjutnya seperti prosedur standar instalasi VPS, kita update dulu paket-paket yang terinstal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt update && sudo apt upgrade -y
</code></pre></div></div>
<p>Outputnya tidak saya tampilkan karena banyak sekali.</p>
<h3 id="install-paket-paket-openmptcp">Install paket-paket OpenMPTCP</h3>
<p>Instalasi paket OpenMPTCP memakan waktu cukup lama, karena banyak aplikasi yang dia setup. Oleh karena itu, kita jalankan perintahnya di dalam <code class="language-plaintext highlighter-rouge">tmux</code> atau <code class="language-plaintext highlighter-rouge">screen</code> sehingga kita bisa logout dan tidak perlu menunggu selesai. Script instalasi berjalan dengan user <code class="language-plaintext highlighter-rouge">root</code>, jadi kita perlu untuk menjalankan <code class="language-plaintext highlighter-rouge">sudo -i</code> dulu. Setelah menjadi root, jalankan perintah berikut bila menggunakan Ubuntu :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -O - https://www.openmptcprouter.com/server/ubuntu20.04-x86_64.sh | sh
</code></pre></div></div>
<p>Atau perintah berikut bila menggunakan Debian</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -O - https://www.openmptcprouter.com/server/debian10-x86_64.sh | sh
</code></pre></div></div>
<p>Saya juga mencoba instalasi paket OpenMPTCPRouter di <a href="https://idcloudhost.com">IDCloudHost</a>, dan ternyata ada langkah tambahan yang perlu kita lakukan sebelum menjalankan perintah di atas, yaitu:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl unmask systemd-networkd.service
systemctl unmask systemd-networkd.socket
systemctl unmask systemd-networkd-wait-online.service
systemctl enable systemd-networkd.socket
systemctl enable systemd-networkd.service
systemctl enable systemd-networkd-wait-online.service
systemctl start systemd-networkd.service
</code></pre></div></div>
<p>Perkiraan waktu instalasi antara 15 - 30 menit. Tidak perlu ditunggu. Kita bisa keluar dari <code class="language-plaintext highlighter-rouge">tmux</code> dengan menekan <code class="language-plaintext highlighter-rouge">Ctrl-b d</code> untuk detach. Tampilan setelah detach seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>endymuhardin@vps-mptcp:~$ tmux
[detached (from session 0)]
</code></pre></div></div>
<p>Kemudian kita bisa logout dari VPS dengan mengetik perintah <code class="language-plaintext highlighter-rouge">exit</code>. Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>logout
Connection to 34.101.134.130 closed.
</code></pre></div></div>
<p>Bila ingin mengecek hasilnya, kita bisa ssh lagi ke VPS tersebut dengan perintah yang sama, yaitu</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute ssh vps-mptcp
</code></pre></div></div>
<p>Bila kita terlalu lama meninggalkan VPS, bisa jadi proses instalasi sudah selesai dan port SSH sudah pindah ke port <code class="language-plaintext highlighter-rouge">65222</code>. Kalau terjadi seperti ini, kita bisa login langsung tanpa menggunakan <code class="language-plaintext highlighter-rouge">gcloud</code> dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh -p 65222 -i ~/.ssh/google_compute_engine 34.101.134.130
</code></pre></div></div>
<p>Setelah masuk, kita masuk lagi ke sesi <code class="language-plaintext highlighter-rouge">tmux</code> dengan perintah <code class="language-plaintext highlighter-rouge">tmux attach</code>.</p>
<h3 id="mendapatkan-informasi-konfigurasi">Mendapatkan informasi konfigurasi</h3>
<p>Bila proses instalasi berjalan dengan sukses, maka akan ada file <code class="language-plaintext highlighter-rouge">/root/openmptcprouter_config.txt</code>. Isinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SSH port: 65222 (instead of port 22)
Shadowsocks port: 65101
Shadowsocks encryption: chacha20
Your shadowsocks key: nxl0i6Q/o8j8TCyRERs5vKY0NjRXAmSkp1P9bQEdYPk=
Glorytun port: 65001
Glorytun encryption: chacha20
Your glorytun key: 45B05B2C4F12A1547D6F073E7DF45A6CD8853D714C2C1D58A4AB86072FABA735
A Dead Simple VPN port: 65401
A Dead Simple VPN key: CD0FA520746E641A8FE5A898AF7F587B999774CC5396E4F5936106DE75917B65
MLVPN first port: 65201'
Your MLVPN password: e9xnMWBYwxduaFxHEPx/iteR1xT0HsnIMzQXBZGT3/Q=
Your OpenMPTCProuter ADMIN API Server key (only for configuration via API access, you don't need it): 988C52062A2C4FEB327F18C0FD39CF8A1577231178FFD0FAF4B5D03FBC87A5EB
Your OpenMPTCProuter Server key: AE5EC8866B17B9D2CEB7C6CC516EB53702E85AF1827B398A13499DC511D09A93
Your OpenMPTCProuter Server username: openmptcprouter
</code></pre></div></div>
<p>Yang kita perlukan hanya dua hal, yaitu baris ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Your OpenMPTCProuter Server key: AE5EC8866B17B9D2CEB7C6CC516EB53702E85AF1827B398A13499DC511D09A93
</code></pre></div></div>
<p>dan alamat IP VPSnya, di contoh ini adalah <code class="language-plaintext highlighter-rouge">34.101.134.130</code></p>
<p>Server key dan alamat IP ini akan kita pasang di router Raspberry Pi kita.</p>
<p>Terakhir, kita restart VPSnya supaya aplikasi-aplikasi yang diinstal script tadi menjadi aktif.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo reboot
</code></pre></div></div>
<h3 id="menghapus-vps">Menghapus VPS</h3>
<p>Karena VPS kita dihitung tagihannya tiap jam, setelah event kita perlu segera menghapus VPS tersebut supaya tagihannya tidak jalan terus. Berikut adalah perintah untuk menghapus VPS di Google Cloud</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances delete vps-mptcp
</code></pre></div></div>
<p>Google akan mengkonfirmasi penghapusan VPS</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The following instances will be deleted. Any attached disks configured to be auto-deleted will be deleted unless they are attached to any other instances or
the `--keep-disks` flag is given and specifies them for keeping. Deleting a disk is irreversible and any data on the disk will be lost.
- [vps-mptcp] in [asia-southeast2-a]
Do you want to continue (Y/n)?
</code></pre></div></div>
<p>Jawab saja <code class="language-plaintext highlighter-rouge">y</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Deleted [https://www.googleapis.com/compute/v1/projects/mptcp-gateway/zones/asia-southeast2-a/instances/vps-mptcp].
</code></pre></div></div>
<h2 id="setup-modem">Setup Modem</h2>
<p>Ada dua hal yang perlu kita setting di modem ataupun koneksi internet lain dari provider, yaitu:</p>
<ul>
<li>DHCP server</li>
<li>network address</li>
</ul>
<p>Secara logika, sebetulnya koneksi antara modem dan provider dengan router modelnya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-bonding.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-bonding.png" alt="Skema Bonding" /></a></p>
<p>Tapi secara fisik, Ada dua pilihan koneksi antara ISP kita dengan router. Yang pertama gambarnya seperti ini:</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-openmptcprouter1.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-openmptcprouter1.png" alt="1 ethernet per ISP" /></a></p>
<p>Tiap koneksi provider kita sediakan ethernet card masing-masing di router. Bila kita pakai Raspberry Pi yang cuma punya satu ethernet card, kita perlu menambah dua lagi menggunakan USB Ethernet Card seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/tplink-UE300.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/tplink-UE300.jpg" alt="TP-Link UE300" /></a></p>
<p>Gambarnya diambil dari <a href="https://www.tp-link.com/id/home-networking/computer-accessory/ue300/">website resmi TP-Link</a></p>
<p>Harganya 200 ribuan satu.</p>
<p>Tapi bila kita mau irit, kita bisa menggunakan port ethernet bawaan Raspberry Pi yang cuma satu. Di Linux, satu ethernet card bisa dipasang banyak alamat IP. Gambarnya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-openmptcprouter.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/skema-openmptcprouter.png" alt="1 ethernet semua ISP" /></a></p>
<p>Syaratnya, kita harus mematikan fungsi DHCP di semua modem/provider. Sebabnya, semua koneksi akan terhubung ke satu switch yang sama, sehingga broadcast addressnya sama. Bila masing-masing modem/provider mengaktifkan DHCP server, maka akan tabrakan dan menjadi tidak jelas nanti alamatnya apa. Apalagi bila PC/Laptop yang ingin berinternet juga terhubung ke switch yang sama. Bisa-bisa PC/Laptop tersebut langsung terkoneksi ke salah satu modem, tidak melalui router.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/modem-disable-dhcp.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/modem-disable-dhcp.png" alt="Disable DHCP Modem" /></a></p>
<p>Setelah kita matikan fungsi DHCP server di modem, kita juga harus membedakan segmen alamat IP di tiap modem. Karena saya menggunakan modem orbit semua, maka settingan pabrikannya juga sama. Alamat IPnya adalah <code class="language-plaintext highlighter-rouge">192.168.8.1</code> di semua modem. Jadi kita perlu ganti menjadi :</p>
<ul>
<li>Modem A : <code class="language-plaintext highlighter-rouge">192.168.11.1</code></li>
<li>Modem B : <code class="language-plaintext highlighter-rouge">192.168.12.1</code></li>
<li>Modem C : <code class="language-plaintext highlighter-rouge">192.168.13.1</code></li>
</ul>
<p>Dalam artikel ini, saya akan menggunakan skema 1 ethernet card untuk semua ISP.
Sehingga ethernet card Raspberry Pi kita berikan 3 alamat tambahan sebagai berikut:</p>
<ul>
<li>eth0:1 : <code class="language-plaintext highlighter-rouge">192.168.11.10</code></li>
<li>eth0:2 : <code class="language-plaintext highlighter-rouge">192.168.12.10</code></li>
<li>eth0:3 : <code class="language-plaintext highlighter-rouge">192.168.13.10</code></li>
</ul>
<h2 id="setup-router">Setup Router</h2>
<p>Ada banyak pilihan perangkat router yang bisa digunakan. Kita tinggal pilih yang sudah disediakan <a href="https://www.openmptcprouter.com/download">di website OpenMPTCPRouter</a>. Bisa pakai Raspberry Pi, bisa juga pakai PC kecil seperti Intel NUC. Saya pakai Raspberry Pi 4 dan 3.</p>
<p>Setelah kita unduh filenya, kita bisa tulis ke memory card yang akan kita pasang di RasPi. Bisa menggunakan <a href="https://www.balena.io/etcher/">aplikasi Balena Etcher</a> atau bisa melalui command line. Saya lebih suka command line, karena lebih cepat. Berikut perintahnya di MacOS atau Linux</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo dd if=openmptcprouter-v0.58.5-r0+16336-b36068d35d-bcm27xx-bcm2711-rpi-4-ext4-factory of=/dev/rdisk4 bs=4m
</code></pre></div></div>
<p>Perhatikan isian <code class="language-plaintext highlighter-rouge">of=/dev/rdisk4</code> dengan teliti. Awas jangan salah nomor disk, bisa-bisa partisi data Anda yang terformat.</p>
<p>Setelah selesai, pasang memori card di RasPi, kemudian nyalakan. Hubungkan ke laptop dengan kabel jaringan. Kemudian kita bisa browse ke <code class="language-plaintext highlighter-rouge">http://192.168.100.1</code> untuk mengakses routernya.</p>
<p>Masuk ke pengaturan System > OpenMPTCPRouter kemudian masukkan isian berikut:</p>
<ul>
<li>
<p>Setting VPS</p>
<ul>
<li>Server Address : alamat VPS kita. <code class="language-plaintext highlighter-rouge">34.101.134.130</code></li>
<li>Server Key : key dari konfigurasi server. <code class="language-plaintext highlighter-rouge">AE5EC8866B17B9D2CEB7C6CC516EB53702E85AF1827B398A13499DC511D09A93</code></li>
</ul>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/router-vps.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/router-vps.png" alt="Router VPS Setting" /></a></p>
</li>
<li>
<p>WAN1</p>
<ul>
<li>Perangkat yang digunakan : <code class="language-plaintext highlighter-rouge">eth0</code></li>
<li>Type : <code class="language-plaintext highlighter-rouge">MacVLAN</code> (karena satu <code class="language-plaintext highlighter-rouge">eth0</code> digunakan banyak alamat IP)</li>
<li>IPv4 address : <code class="language-plaintext highlighter-rouge">192.168.11.10</code></li>
<li>IPv4 gateway : <code class="language-plaintext highlighter-rouge">192.168.11.1</code></li>
<li>Setting lainnya dibiarkan saja seperti defaultnya</li>
</ul>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/router-wan1.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/router-wan1.png" alt="Router WAN1 Setting" /></a></p>
</li>
<li>
<p>WAN2</p>
<ul>
<li>Perangkat yang digunakan : <code class="language-plaintext highlighter-rouge">eth0</code></li>
<li>Type : <code class="language-plaintext highlighter-rouge">MacVLAN</code></li>
<li>IPv4 address : <code class="language-plaintext highlighter-rouge">192.168.12.10</code></li>
<li>IPv4 gateway : <code class="language-plaintext highlighter-rouge">192.168.12.1</code></li>
<li>Setting lainnya dibiarkan saja seperti defaultnya</li>
</ul>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/router-wan2.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/router-wan2.png" alt="Router WAN2 Setting" /></a></p>
</li>
<li>
<p>WiFi Rumah/Kantor (bila ada)</p>
<ul>
<li>Perangkat yang digunakan : <code class="language-plaintext highlighter-rouge">wlan0</code></li>
<li>Tipe device : <code class="language-plaintext highlighter-rouge">Normal</code> (karena satu <code class="language-plaintext highlighter-rouge">wlan0</code> hanya digunakan untuk satu alamat IP)</li>
<li>Protocol : <code class="language-plaintext highlighter-rouge">DHCP</code> bila alamat IP diberikan otomatis, <code class="language-plaintext highlighter-rouge">Manual</code> bila mau setting sendiri</li>
</ul>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/router-wifi.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/router-wifi.png" alt="Router WiFi Setting" /></a></p>
</li>
</ul>
<p>Dari 3 WAN yang kita gunakan, pilih satu yang paling stabil, dan jadikan dia <code class="language-plaintext highlighter-rouge">Master</code>. Sisanya kita bisa set menjadi <code class="language-plaintext highlighter-rouge">Enabled</code>.</p>
<p>Jangan lupa untuk mengatur password router, karena by default passwordnya kosong.</p>
<p>Setelah semua selesai, kita bisa cek status koneksinya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/openmptcp-2-modem-only.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/openmptcp-2-modem-only.png" alt="2 Modem Aktif" /></a></p>
<p>Modem yang tidak aktif akan tampil dengan warna merah.</p>
<p>Hasil pengetesan saya, dengan VPS di GCP harusnya semua koneksi bisa hijau.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/openmptcp-all-green.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/openmptcp-all-green.png" alt="All Green" /></a></p>
<p>Kalau ada yang kuning, dengan pesan error MPTCP tidak didukung ISP, kita bisa coba restart modemnya.</p>
<p>Biasanya setelah reconnect, dia akan hijau.</p>
<h2 id="test-koneksi-per-modem">Test Koneksi per Modem</h2>
<p>Agar kita ada perbandingan before dan after, kita bisa tes kecepatan masing-masing modem. Caranya mudah. Cukup ganti alamat IP laptop kita menjadi satu network dengan salah satu modem. Misalnya kita mau mengetes modem A yang alamatnya <code class="language-plaintext highlighter-rouge">192.168.11.1</code>. Ganti alamat IP laptop kita menjadi <code class="language-plaintext highlighter-rouge">192.168.11.100</code> dan arahkan gatewaynya ke <code class="language-plaintext highlighter-rouge">192.168.11.1</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/setting-ip-manual.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/setting-ip-manual.png" alt="Setting IP Manual" /></a></p>
<p>Kemudian jalankan speed test.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/01-speedtest-xl-orbit-max.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/01-speedtest-xl-orbit-max.png" alt="Speed Test 1 Provider" /></a></p>
<p>Kita bisa lakukan ini secara bergantian untuk masing-masing modem. Dengan demikian kita ada gambaran mengenai kecepatan masing-masing modem.</p>
<h2 id="test-koneksi-gabungan">Test Koneksi Gabungan</h2>
<p>Setelah tahu kecepatan masing-masing provider, kita bisa tes kecepatan gabungan. Kembalikan setting IP menjadi otomatis, sehingga laptop kita mendapatkan IP dari router.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/setting-ip-otomatis.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/setting-ip-otomatis.png" alt="Setting IP Otomatis" /></a></p>
<p>Kemudian jalankan lagi speedtest.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/04-speedtest-openmptcp.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/wan-bonding/04-speedtest-openmptcp.png" alt="Speed Test 1 Provider" /></a></p>
<p>Video speed test bisa kita lihat di sini</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/YdH-1l8CB_Q" title="Speed Test" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Demikianlah sedikit sharing tentang cara bonding WAN. Mudah-mudahan bermanfaat.</p>
Upgrade Major Version Gitlab2021-09-22T07:14:00+07:00https://software.endy.muhardin.com/devops/upgrade-gitlab-major<p>Buat yang ingin menginstal repository Git di server sendiri, <a href="https://about.gitlab.com">Gitlab</a> adalah aplikasi yang populer dan banyak digunakan karena open source, gratis, dan lengkap fiturnya. Gitlab juga sampai sekarang sangat aktif dikembangkan oleh para maintainernya, sehingga fitur-fitur baru terus bermunculan.</p>
<p>Hal ini tentu menggembirakan bagi para penggunanya, tapi juga relatif merepotkan bagi yang bertugas mengurus servernya. Ya apa lagi kalau bukan urusan upgrade versi. Begitu kita lupa upgrade beberapa bulan saja, maka kita akan ketinggalan rilis major version, sehingga urusan upgrade tidak lagi sesederhana <code class="language-plaintext highlighter-rouge">apt update && apt upgrade -y</code>.</p>
<p>Nah pada artikel ini saya akan mencatat langkah-langkah yang perlu dilakukan untuk mengupgrade Gitlab agar bisa kembali menyusul versi terbaru.</p>
<!--more-->
<p>Pertama yang harus kita lakukan adalah mengunjungi <a href="https://docs.gitlab.com/ee/update/index.html#upgrade-paths">halaman dokumentasi panduan upgrade</a>. Kita perlu melihat jalur upgrade yang harus ditempuh apabila kita sudah ketinggalan beberapa major version. Cari bagian upgrade paths yang bentuknya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/gitlab-upgrade/01-gitlab-upgrade-path.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/gitlab-upgrade/01-gitlab-upgrade-path.png" alt="Upgrade Path" /></a></p>
<p>Selanjutnya, kita perlu tahu versi Gitlab yang sekarang terinstal di server kita. Caranya dengan login ke tampilan web Gitlab, kemudian klik menu Help di kanan atas.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/gitlab-upgrade/02-menu-help.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/gitlab-upgrade/02-menu-help.png" alt="Menu Help" /></a></p>
<p>Kita akan melihat halaman Help yang berisi versi Gitlab yang sedang berjalan.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/gitlab-upgrade/03-current-version.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/gitlab-upgrade/03-current-version.png" alt="Versi Gitlab" /></a></p>
<p>Sesuaikan versi kita dengan upgrade path di dokumentasi resmi.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/gitlab-upgrade/04-our-upgrade-path.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/gitlab-upgrade/04-our-upgrade-path.png" alt="Upgrade Path Kita" /></a></p>
<p>Kemudian, kita perlu mencari nama lengkap dari rilis yang mau kita instal. Karena kita sekarang berada di versi <code class="language-plaintext highlighter-rouge">13.12.9</code>, maka saya perlu naik dulu ke <code class="language-plaintext highlighter-rouge">14.0.7</code>. Perintahnya adalah sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-cache madison gitlab-ce | grep 14.0
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gitlab-ce | 14.0.10-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.9-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.8-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.7-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.6-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.5-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.4-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.3-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.2-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.1-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
gitlab-ce | 14.0.0-ce.0 | https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 Packages
</code></pre></div></div>
<p>Versi yang kita inginkan adalah <code class="language-plaintext highlighter-rouge">14.0.7-ce.0</code>. Instal dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install gitlab-ce=14.0.7-ce.0
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be upgraded:
gitlab-ce
1 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 926 MB of archives.
After this operation, 62.6 MB of additional disk space will be used.
Get:1 https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu focal/main amd64 gitlab-ce amd64 14.0.7-ce.0 [926 MB]
Fetched 926 MB in 37s (24.9 MB/s)
(Reading database ... 380734 files and directories currently installed.)
Preparing to unpack .../gitlab-ce_14.0.7-ce.0_amd64.deb ...
gitlab preinstall: Checking for unmigrated data on legacy storage
gitlab preinstall: Automatically backing up only the GitLab SQL database (excluding everything else!)
Deleting tmp directories ... done
done
Deleting old backups ... done. (0 removed)
Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data
and are not included in this backup. You will need these files to restore a backup.
Please back them up manually.
Backup task is done.
gitlab preinstall: Automatically backing up /etc/gitlab
Running configuration backup
Creating configuration backup archive: gitlab_config_1632301670_2021_09_22.tar
Configuration backup archive complete: /etc/gitlab/config_backup/gitlab_config_1632301670_2021_09_22.tar
WARNING: In GitLab 14.0 we will begin removing all configuration backups older than yourgitlab_rails['backup_keep_time'] setting (currently set to: 172800)
Keeping all older configuration backups
Unpacking gitlab-ce (14.0.7-ce.0) over (13.12.9-ce.0) ...
Setting up gitlab-ce (14.0.7-ce.0) ...
Upgrade complete! If your GitLab server is misbehaving try running
sudo gitlab-ctl restart
before anything else.
If you need to roll back to the previous version you can use the database
backup made during the upgrade (scroll up for the filename).
</code></pre></div></div>
<p>Setelah <code class="language-plaintext highlighter-rouge">14.0.7-ce.0</code> terinstal, berarti kita sudah selangkah di belakang rilis terbaru. Langsung saja upgrade lagi dengan cara biasa</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt upgrade -y
</code></pre></div></div>
<p>Kadangkala kita mengalami error pada waktu ingin menjalankan versi terbaru. Ini hal yang biasa, mengingat kita upgrade beberapa versi sekaligus. Gitlab menambahkan fitur background migration di versi <code class="language-plaintext highlighter-rouge">14.2</code>, sehingga kadangkala masih ada proses migrasi database yang berjalan walaupun status upgrade sudah selesai.</p>
<p>Ada beberapa solusi untuk problem ini, diantaranya:</p>
<ul>
<li>Jangan upgrade langsung berturut-turut. Berikan jeda beberapa jam atau beberapa hari (tergantung ukuran repository dan database), supaya background jobs selesai dieksekusi</li>
<li>Restart setiap selesai upgrade antar versi major, sesuai anjuran mbak-mbak Indihome.</li>
</ul>
<p>Nah, kalau semua berjalan lancar, kita sudah menjalankan versi terbaru dari Gitlab.</p>
<p>Selamat mencoba … semoga bermanfaat …</p>
Persiapan Coding Flutter2021-09-02T07:14:00+07:00https://software.endy.muhardin.com/flutter/persiapan-coding-flutter<p>Flutter adalah framework pengembangan aplikasi untuk membuat aplikasi Android dan iOS sekaligus. Jadi dengan coding satu kali saja, kita bisa menghasilkan dua versi aplikasi, Android dan iOS. Sebetulnya framework multiplatform seperti ini bukanlah hal yang baru. Di jaman dahulu, kita mengenal PhoneGap (yang sekarang disebut Apache Cordova) dan Sencha Touch. Kemudian yang agak modern ada Ionic dan ReactNative.</p>
<p>Yang membuat Flutter relatif cepat naik daun adalah karena disponsori Google. Iming-iming Google sebagai sponsor utama Android membuat banyak developer yakin dengan adopsi Flutter, minimal di platform Android. Walaupun demikian, Google memiliki <em>track record</em> yang kurang meyakinkan dalam hal framework. Kita sudah melihat apa yang terjadi dengan AngularJS. Setelah banyak yang mengadopsi, kemudian ditulis ulang. Sehingga semua aplikasi yang sudah dibuat dengan AngularJS, harus dibuat ulang dari nol dengan Angular.</p>
<p>Walaupun demikian, sepertinya Flutter ini cukup populer dan masif adopsinya. Sehingga perlu kita explore lebih jauh. Untuk itu, pada artikel ini kita akan membahas tentang persiapan coding Flutter.</p>
<!--more-->
<p>Secara garis besar, kita harus menginstal beberapa kelengkapan di komputer kita untuk bisa membuat aplikasi Flutter, yaitu:</p>
<ul>
<li>Flutter SDK</li>
<li>Editor, dalam artikel ini saya akan pakai Visual Studio Code.</li>
<li>Java SDK</li>
<li>Android SDK</li>
<li>XCode</li>
</ul>
<h2 id="instalasi-flutter-sdk">Instalasi Flutter SDK</h2>
<p>Flutter SDK mencakup library flutter dan bahasa pemrograman Dart. Petunjuk instalasinya bisa dibaca <a href="https://flutter.dev/docs/get-started/install">di websitenya</a>.</p>
<p>Untuk MacOS, saya lebih suka menggunakan <code class="language-plaintext highlighter-rouge">brew</code>. Berikut perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install --cask flutter
</code></pre></div></div>
<p>Di Ubuntu, berikut adalah perintah untuk instalasi Flutter SDK</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo snap install flutter --classic
</code></pre></div></div>
<p>Instalasi dengan <code class="language-plaintext highlighter-rouge">brew</code> atau <code class="language-plaintext highlighter-rouge">snap</code> seharusnya akan menambahkan variabel di <code class="language-plaintext highlighter-rouge">PATH</code> sehingga kita bisa menjalankan perintah <code class="language-plaintext highlighter-rouge">flutter doctor</code> di command line. Kalau tidak bisa, coba restart dulu aplikasi Terminalnya. Kalau masih tidak bisa, kita harus setting <code class="language-plaintext highlighter-rouge">PATH</code> sendiri.</p>
<p>Untuk melakukan setting <code class="language-plaintext highlighter-rouge">PATH</code>, pengguna MacOS bisa mengedit file <code class="language-plaintext highlighter-rouge">.zshrc</code> dan pengguna Ubuntu bisa mengedit file <code class="language-plaintext highlighter-rouge">.bashrc</code> yang ada di <code class="language-plaintext highlighter-rouge">$HOME</code> masing-masing. Tambahkan baris berikut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export PATH="$PATH:[lokasi-instalasi-flutter]/bin"
</code></pre></div></div>
<p>Setelah Flutter SDK terinstal, jalankan <code class="language-plaintext highlighter-rouge">flutter doctor</code> untuk mengecek apakah kelengkapan lainnya sudah terinstal atau belum.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter doctor
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.2.3, on macOS 11.5.2 20G95 darwin-arm, locale en-ID)
[!] Android toolchain - develop for Android devices
✗ Unable to locate Android SDK.
Install Android Studio from: https://developer.android.com/studio/index.html
On first launch it will assist you in installing the Android SDK components.
(or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions).
If the Android SDK has been installed to a custom location, please use
`flutter config --android-sdk` to update to that location.
✗ No valid Android SDK platforms found in /Users/endymuhardin/Applications/android-sdk/platforms. Directory was empty.
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[!] Android Studio (not installed)
[✓] IntelliJ IDEA Ultimate Edition (version 2021.2)
[✓] VS Code (version 1.59.1)
[✓] Connected device (1 available)
! Doctor found issues in 2 categories.
</code></pre></div></div>
<p>Output di atas menunjukkan bahwa kita belum menginstal Android SDK.</p>
<h2 id="instalasi-android-sdk">Instalasi Android SDK</h2>
<p>Biasanya, kalau kita mau membuat aplikasi Android, kita perlu menginstal Android Studio. Ukurannya lumayan besar, installernya saja bisa 2GB sendiri. Nanti setelah diinstal, dia akan mengunduh lagi teman-temannya yang ukurannya bisa bergiga-giga. Untuk laptop saya yang cuma memiliki storage 256GB, ini kurang efisien. Lagipula karena kita akan coding Flutter, kita tidak membutuhkan Android Studio secara lengkap. Cukup Android SDK saja.</p>
<p>Untuk menginstal SDKnya saja, kita buka <a href="https://developer.android.com/studio#downloads">halaman Download Android Studio</a>, kemudian scroll ke bawah sampai menemukan <code class="language-plaintext highlighter-rouge">Command line tools only</code> seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/01-android-sdk-command-line-only.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/01-android-sdk-command-line-only.png" alt="Command line only" /></a></p>
<p>Ukurannya <em>hanya</em> 100MB saja, bandingkan dengan Android Studio yang bisa mencapai 1GB.</p>
<p>Setelah diunduh, extract filenya ke folder mana saja yang kita sukai. Saya sendiri biasa menaruhnya di <code class="language-plaintext highlighter-rouge">$HOME/Applications/android-sdk/cmdline-tools</code> seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/02-hasil-donlod-command-line-only.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/02-hasil-donlod-command-line-only.png" alt="Hasil download Command line only" /></a></p>
<p>Yang barusan kita unduh hanya <code class="language-plaintext highlighter-rouge">Command Line Tools</code> saja. Untuk melengkapinya, kita perlu menginstal <code class="language-plaintext highlighter-rouge">platform-tools</code> dan teman-temannya, sesuai dengan versi Android API yang kita akan gunakan. Misalnya kita memakai API level 29. Maka perintahnya seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sdkmanager "platform-tools" "platforms;android-29" "build-tools;29.0.2" "emulator"
</code></pre></div></div>
<p>Kita juga perlu menginstal <code class="language-plaintext highlighter-rouge">system-image</code>, yaitu template sistem operasi untuk menjalankan emulator. Pilih yang sesuai dengan laptop yang digunakan. Untuk laptop berprosesor Intel 64 bit, perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sdkmanager "system-images;android-29;google_apis_playstore;x86"
</code></pre></div></div>
<p>Untuk laptop Macbook Pro M1, perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sdkmanager "system-images;android-29;google_apis_playstore;arm64-v8a"
</code></pre></div></div>
<p>Android SDK Manager akan mengunduh kelengkapan tersebut, hasilnya setelah unduhan selesai menjadi seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/03-hasil-donlod-android-sdk.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/03-hasil-donlod-android-sdk.png" alt="Hasil download Android SDK" /></a></p>
<p>Setelah semua terinstal, kita set dulu pathnya. Edit file <code class="language-plaintext highlighter-rouge">.zshrc</code> (MacOS) atau <code class="language-plaintext highlighter-rouge">.bashrc</code> (Ubuntu)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export ANDROID_HOME=$HOME/Applications/android-sdk
export PATH=$ANDROID_HOME/cmdline-tools/tools/bin:$PATH
export PATH=$ANDROID_HOME/emulator:$PATH
export PATH=$ANDROID_HOME/platform-tools:$PATH
</code></pre></div></div>
<p>Jangan lupa sesuaikan lokasi <code class="language-plaintext highlighter-rouge">ANDROID_HOME</code> dengan lokasi instalasi di komputer masing-masing.</p>
<p>Kita coba lagi mengecek status kelengkapan Flutter dengan perintah <code class="language-plaintext highlighter-rouge">flutter doctor</code>. Sekarang hasilnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.2.3, on macOS 11.5.2 20G95 darwin-arm, locale en-ID)
[!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
✗ Android license status unknown.
Run `flutter doctor --android-licenses` to accept the SDK licenses.
See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[!] Android Studio (not installed)
[✓] IntelliJ IDEA Ultimate Edition (version 2021.2)
[✓] VS Code (version 1.59.1)
[✓] Connected device (1 available)
! Doctor found issues in 2 categories.
</code></pre></div></div>
<p>Dia bilang kita perlu melakukan <code class="language-plaintext highlighter-rouge">Accept</code> terhadap lisensi Android SDK. Akan tetapi bila kita jalankan perintah <code class="language-plaintext highlighter-rouge">flutter doctor --android-licenses</code>, kita akan menemui error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter doctor --android-licenses
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
at com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:156)
at com.android.repository.api.SchemaModule.<init>(SchemaModule.java:75)
at com.android.sdklib.repository.AndroidSdkHandler.<clinit>(AndroidSdkHandler.java:81)
at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:73)
at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:48)
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 5 more
</code></pre></div></div>
<p>Untuk mengatasinya, kita perlu menggunakan Java versi 8. Agar mudah, gunakan <a href="https://sdkman.io/">SDKMan</a> dan jalankan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sdk install java 8.0.282-zulu
sdk default java 8.0.282-zulu
</code></pre></div></div>
<p>Restart terminal, kemudian jalankan lagi <code class="language-plaintext highlighter-rouge">flutter doctor --android-licenses</code>. Flutter akan menginformasikan berapa jumlah lisensi yang harus kita <code class="language-plaintext highlighter-rouge">Accept</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>6 of 7 SDK package licenses not accepted. 100% Computing updates...
Review licenses that have not been accepted (y/N)? y
</code></pre></div></div>
<p>Kemudian kita akan disajikan dengan lisensi berbagai paket yang diinstal oleh Android SDK. Jawab saja <code class="language-plaintext highlighter-rouge">y</code> semua.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Accept? (y/N): y
All SDK package licenses accepted
</code></pre></div></div>
<p>Setelah itu, jalankan lagi <code class="language-plaintext highlighter-rouge">flutter doctor</code>. Hasilnya oke semua seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.2.3, on macOS 11.5.2 20G95 darwin-arm, locale en-ID)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[!] Android Studio (not installed)
[✓] IntelliJ IDEA Ultimate Edition (version 2021.2)
[✓] VS Code (version 1.59.1)
[✓] Connected device (1 available)
! Doctor found issues in 1 category.
</code></pre></div></div>
<p>Cuma ada satu warning karena kita belum instal Android Studio. Ini tidak masalah, karena kita akan coding menggunakan Visual Studio Code.</p>
<h2 id="instalasi-xcode">Instalasi XCode</h2>
<p>XCode adalah aplikasi yang disediakan Apple untuk membuat aplikasi iOS. Ini dibutuhkan bila kita ingin membuat aplikasi yang berjalan di iPhone dan iPad. Untuk menginstalnya, jalankan perintah berikut di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
</code></pre></div></div>
<p>Biasanya para programmer yang menggunakan laptop Macbook sudah menginstal XCode, bukan karena ingin membuat aplikasi iOS, tapi karena butuh <code class="language-plaintext highlighter-rouge">git</code>.</p>
<p>Setelah diinstal, kita tes menjalankan simulator dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>open -a Simulator
</code></pre></div></div>
<p>XCode akan menjalankan simulator dan menampilkan emulator iPhone.</p>
<p>Kita juga membutuhkan <code class="language-plaintext highlighter-rouge">cocoapods</code>, yaitu dependency management untuk mengurus library yang kita butuhkan dalam aplikasi. Semacam <code class="language-plaintext highlighter-rouge">maven</code> untuk iOS development. Berikut perintah untuk instalasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo gem install cocoapods
</code></pre></div></div>
<h2 id="setup-visual-studio-code">Setup Visual Studio Code</h2>
<p>Aplikasi Visual Studio Code bisa diunduh dan diinstal <a href="https://code.visualstudio.com">dari websitenya</a>. Kita perlu mengaktifkan extension Flutter.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/04-extension-vscode-flutter.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/04-extension-vscode-flutter.png" alt="VSCode Flutter Extension" /></a></p>
<p>Kemudian, kita coba membuat project Hello World dulu. Untuk memastikan semua kelengkapan sudah terinstal dengan sempurna. Kita generate project dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter create helloflutter
</code></pre></div></div>
<p>Selanjutnya, masuk ke folder tersebut, dan jalankan visual studio code di dalamnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd helloflutter
code .
</code></pre></div></div>
<p>Kita bisa lihat di kanan bawah ada daftar perangkat emulator yang tersedia. Bila belum ada, tulisannya <code class="language-plaintext highlighter-rouge">No Device</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/05-open-project-no-device.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/05-open-project-no-device.png" alt="Open Project VSCode" /></a></p>
<p>Setelah kita jalankan emulator, maka di pojok kanan bawah akan tampil nama device emulatornya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/06-simulator-iphone.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/06-simulator-iphone.png" alt="Simulator iPhone" /></a></p>
<p>Kita juga bisa menjalankan simulator Android.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/06-simulator-android.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/06-simulator-android.png" alt="Simulator Android" /></a></p>
<p>Aplikasi yang baru saja kita generate bisa langsung dijalankan di emulator. Klik menu Run > Start Debugging untuk menjalankan aplikasi.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/07-run-app.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/07-run-app.png" alt="Run App" /></a></p>
<p>VS Code akan menanyakan emulator mana yang akan digunakan untuk mengetes aplikasi. Pilih saja device yang kita inginkan.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/08-select-device.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/08-select-device.png" alt="Select Device" /></a></p>
<p>Selanjutnya, VSCode akan melakukan build, dan hasilnya akan dijalankan di emulator. Kita bisa lihat hasilnya di emulator iPhone sebagai berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/09-app-display-iphone.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/09-app-display-iphone.png" alt="App Display iPhone" /></a></p>
<p>Atau di emulator Android</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/09-app-display-android.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/09-app-display-android.png" alt="App Display Android" /></a></p>
<p>Nah, kita bisa melanjutkan coding untuk menambahkan fitur di aplikasi. VS Code juga menyediakan visual editor untuk memudahkan kita dalam mengatur layout komponen.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/10-visual-editor.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/persiapan-flutter/10-visual-editor.png" alt="Visual Editor" /></a></p>
<p>Demikianlah persiapan yang harus kita lakukan untuk mulai membuat aplikasi mobile dengan Flutter. Selanjutnya silahkan cari tutorial di Youtube tentang pengembangan aplikasi dengan Flutter.</p>
<p>Selamat mencoba … semoga bermanfaat …</p>
Setup Vault untuk Production2021-08-26T07:14:00+07:00https://software.endy.muhardin.com/devops/setup-vault-production<p>Pada <a href="/java/enkripsi-data-di-aplikasi/">artikel sebelumnya</a>, kita telah mengamankan aplikasi kita supaya data pribadi yang kita tampung di aplikasi dapat diamankan dengan baik. Solusinya adalah menggunakan <a href="https://vaultproject.io">Vault</a>.</p>
<p>Kali ini, kita akan membahas instalasi dan konfigurasi Vault untuk kita pasang di production server. Setup yang kita akan bahas adalah setup minimalis dengan satu node saja. Apabila terjadi error terhadap node tersebut, maka akan ada downtime sementara kita menyiapkan node pengganti dan melakukan restorasi data.</p>
<!--more-->
<h2 id="persiapan-vps">Persiapan VPS</h2>
<p>Kita akan menggunakan Ubuntu versi terbaru pada saat artikel ini ditulis, yaitu versi 20.04. Terlebih dulu kita update dan upgrade paket-paket yang sudah terinstal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt update && apt upgrade -y
</code></pre></div></div>
<p>Selanjutnya, kita konfigurasi nama domain dan arahkan ke alamat IP publik VPS kita. Pada contoh ini, nama domain kita tentukan <code class="language-plaintext highlighter-rouge">vault.artivisi.id</code> yang mengarah ke IP <code class="language-plaintext highlighter-rouge">111.222.121.212</code>.</p>
<h2 id="instalasi-aplikasi">Instalasi Aplikasi</h2>
<p>Berikut adalah daftar aplikasi yang perlu kita instal:</p>
<ul>
<li>letsencrypt dan certbot</li>
<li>MySQL / MariaDB / PostgreSQL apabila ingin menggunakan database storage</li>
</ul>
<p>Letsencrypt dan certbot diinstal menggunakan <code class="language-plaintext highlighter-rouge">snap</code>. Update dulu daftar paketnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo snap install core; sudo snap refresh core
</code></pre></div></div>
<p>Kemudian install <code class="language-plaintext highlighter-rouge">certbot</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo snap install --classic certbot
</code></pre></div></div>
<p>Untuk database server, diinstal menggunakan <code class="language-plaintext highlighter-rouge">apt</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install letsencrypt mariadb-server -y
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">mariadb-server</code> bisa diganti <code class="language-plaintext highlighter-rouge">mysql-server</code> atau <code class="language-plaintext highlighter-rouge">postgresql-server</code> sesuai selera.</p>
<h2 id="instalasi-vault">Instalasi Vault</h2>
<p>Vault memiliki repository sendiri untuk file installernya. Kita tambahkan dulu repository Vault ke Ubuntu.</p>
<p>Jalankan perintah berikut untuk mendaftarkan signature repository Vault ke Ubuntu.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
</code></pre></div></div>
<p>Lalu tambahkan repository</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
</code></pre></div></div>
<p>Update dan install</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get update && sudo apt-get install vault
</code></pre></div></div>
<p>Setelah instalasi selesai, harusnya akan terbentuk folder <code class="language-plaintext highlighter-rouge">/opt/vault</code> dan <code class="language-plaintext highlighter-rouge">/etc/vault.d</code>.</p>
<p>Kita akan melanjutkan konfigurasi Vault setelah kelengkapan yang lain kita instal.</p>
<h2 id="generate-sertifikat-ssl">Generate Sertifikat SSL</h2>
<p>Kita membutuhkan sertifikat SSL supaya komunikasi aplikasi kita dengan Vault terenkripsi. Dan juga apabila kita menggunakan browser untuk melihat isi Vault, kita juga akan menggunakan koneksi HTTPS.</p>
<p>Sebelum kita generate sertifikat, pastikan DNS sudah disetting dengan benar. Coba cek di layanan whois untuk memastikan <code class="language-plaintext highlighter-rouge">vault.artivisi.id</code> sudah mengarah ke <code class="language-plaintext highlighter-rouge">111.222.121.212</code>.</p>
<p>Selanjutnya, kita generate sertifikat dengan metode <code class="language-plaintext highlighter-rouge">standalone</code>. Certbot akan membuka port 80 untuk melakukan verifikasi. Pastikan di VPS yang digunakan tidak ada webserver lain yang sedang aktif.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certbot --standalone -d vault.artivisi.id
</code></pre></div></div>
<p>Bila prosesnya berjalan lancar, kita akan memiliki sertifikat SSL di folder <code class="language-plaintext highlighter-rouge">/etc/letsencrypt/live/vault.artivisi.id</code>. Ada 2 file yang kita butuhkan:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">fullchain.pem</code> : sertifikat SSL lengkap dengan rantai sampai ke CA LetsEncrypt</li>
<li><code class="language-plaintext highlighter-rouge">privkey.pem</code> : private key, pasangan dari sertifikat SSL</li>
</ul>
<p>Sebelumnya Vault telah membuatkan file self-signed certificate. Kita timpa dan gantikan dengan sertifikat yang digenerate oleh LetsEncrypt.</p>
<p>Copy file <code class="language-plaintext highlighter-rouge">fullchain.pem</code> dan <code class="language-plaintext highlighter-rouge">privkey.pem</code> ke folder Vault.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp /etc/letsencrypt/live/vault.artivisi.id/fullchain.pem /opt/vault/tls/tls.crt
cp /etc/letsencrypt/live/vault.artivisi.id/privkey.pem /opt/vault/tls/tls.key
</code></pre></div></div>
<p>Kemudian set kepemilikan dan ijin aksesnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod vault:vault /opt/vault/tls/*
chown 600 /opt/vault/tls/*
</code></pre></div></div>
<h2 id="setup-database-server">Setup Database Server</h2>
<p>Langkah ini sebetulnya opsional. Tidak wajib. Ini kita lakukan kalau mau data Vault tersimpan di database server. Instalasi default Vault di Ubuntu menyimpan data dalam file di folder <code class="language-plaintext highlighter-rouge">/opt/vault/data</code>. Kadangkala ada sysadmin yang lebih suka melakukan backup database daripada folder.</p>
<p>Jadi, silahkan lakukan langkah ini kalau dirasa perlu.</p>
<p>Pertama, kita siapkan username dan password untuk koneksi database. Kemudian buat databasenya.</p>
<ul>
<li>MySQL / MariaDB</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create user vault@localhost identified by 'vaultpassword123';
grant all on vaultdb.* to vault@localhost;
create database vaultdb;
</code></pre></div></div>
<ul>
<li>PostgreSQL</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createuser -P vault
createdb -Ovault vaultdb
</code></pre></div></div>
<p>Lalu, kita konfigurasi database storage di <code class="language-plaintext highlighter-rouge">/etc/vault.d/vault.hcl</code></p>
<p>Berikut konfigurasi untuk MySQL/MariaDB</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>storage "mysql"{
username = "vault"
password = "vaultpassword123"
database = "vaultdb"
plaintext_connection_allowed = true
}
</code></pre></div></div>
<p>PostgreSQL konfigurasinya relatif panjang. Lebih baik langsung merujuk ke <a href="https://www.vaultproject.io/docs/configuration/storage/postgresql">dokumentasi resmi</a>. Kalau saya tulis di sini khawatir akan berubah di kemudian hari.</p>
<p>Bila kita tidak menggunakan database, kita sudah disediakan konfigurasi oleh installer Vault seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>storage "file" {
path = "/opt/vault/data"
}
</code></pre></div></div>
<p>Biarkan saja apa adanya. Vault akan menyimpan data kita dalam kondisi terenkripsi di folder tersebut. Kita bisa melakukan backup terhadap folder tersebut. Untuk amannya, kita bisa melakukan backup pada waktu kondisi kegiatan sedang sepi, atau sekalian matikan Vault.</p>
<h2 id="inisialisasi-vault">Inisialisasi Vault</h2>
<p>Setelah konfigurasi SSL dan storage selesai, kita bisa menyalakan Vault. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start vault
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Started "HashiCorp Vault - A tool for managing secrets".
==> Vault server configuration:
Api Address: https://vault.artivisi.id:8200
Cgo: disabled
Cluster Address: https://vault.artivisi.id:8201
Go Version: go1.16.6
Listener 1: tcp (addr: "0.0.0.0:8200", cluster address: "0.0.0.0:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "enabled")
Log Level: info
Mlock: supported: true, enabled: true
Recovery Mode: false
Storage: file
Version: Vault v1.8.1
Version Sha: 4b0264f28defc05454c31277cfa6ff63695a458d
==> Vault server started! Log data will stream in below:
2021-08-26T06:12:32.424Z [INFO] proxy environment: http_proxy="" https_proxy="" no_proxy=""
</code></pre></div></div>
<p>Selanjutnya, kita bisa membuka <code class="language-plaintext highlighter-rouge">https://vault.artivisi.id:8200</code> di browser. Tampilannya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/01-set-num-key.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/01-set-num-key.png" alt="Inisialisasi Vault" /></a></p>
<p>Kita diminta menentukan berapa orang yang akan memegang <code class="language-plaintext highlighter-rouge">key</code> untuk menjalankan Vault dalam kondisi <code class="language-plaintext highlighter-rouge">unsealed</code>, yaitu kondisi operasional aktif dan siap menjalankan tugas. Ada dua hal yang harus ditentukan di sini, yaitu:</p>
<ul>
<li>berapa orang yang akan pegang <code class="language-plaintext highlighter-rouge">key</code></li>
<li>berapa minimal orang yang harus hadir untuk melakukan <code class="language-plaintext highlighter-rouge">unseal</code> / mengaktifkan Vault</li>
</ul>
<p>Inisialisasi <code class="language-plaintext highlighter-rouge">master key</code> ini disebut dengan istilah <a href="https://en.wikipedia.org/wiki/Key_ceremony">Key Management Ceremony</a>. Tata cara pemilihan pemegang key, prosedur pembuatan key, dan rincian lainnya bisa dibaca di <a href="https://www.iana.org/dnssec/procedures/ksk-operator/KSK_Key_Management_Procedure_v3.3.pdf">panduan ICANN</a>. Penjelasannya bisa ditonton di video berikut</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/XU5_Uv4EDV8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Master key ini nantinya akan digunakan untuk membuat key turunan, dan key turunan ini akan digunakan untuk mengenkripsi data. Jadi bila key ini sampai hilang, maka seluruh data kita yang terenkripsi tidak akan bisa dibuka. Oleh karena itu, kita harus mendistribusikan key ini ke sejumlah orang sehingga:</p>
<ul>
<li>bila salah satu atau lebih berhalangan (sakit, meninggal, bepergian, dan sebagainya), Vault tetap bisa diaktifkan</li>
<li>sulit untuk terjadi fraud, karena untuk melakukan fraud dibutuhkan kolaborasi dari sejumlah orang</li>
</ul>
<p>Idealnya, master key didistribusikan ke beberapa pejabat penting di departemen yang berbeda. Untuk artikel ini, kita contohkan distribusi master key ke 5 orang, dengan minimal 3 orang untuk melakukan <code class="language-plaintext highlighter-rouge">unseal</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/02-5-key-3-minimum.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/02-5-key-3-minimum.png" alt="Menentukan jumlah key dan minimum unseal" /></a></p>
<p>Klik <code class="language-plaintext highlighter-rouge">Initialize</code>, Vault akan membuat 5 key dan 1 root token. Key dan token ini akan tampil di halaman web atau command prompt, tergantung apakah kita menggunakan Web UI atau Command Line. Sebetulnya cara ini kurang secure, karena si operator yang melakukan inisialisasi bisa melihat (dan menyimpan) semua key.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/03-key-result.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/03-key-result.png" alt="Hasil generate 5 key dan 1 token" /></a></p>
<p>Cara yang lebih secure adalah menggunakan GPG. Untuk itu, kita harus memiliki public key dari masing-masing pemegang kunci. Cara membuat private dan public key dengan GPG bisa dibaca di <a href="/linux/menggunakan-gpg/">artikel ini</a> atau ditonton di video ini</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/IXmP8Oag3KM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Misalnya kita memiliki 5 user untuk memegang key, dengan GPG public key masing-masing adalah:</p>
<ul>
<li>endy.pub</li>
<li>anggi.pub</li>
<li>dadang.pub</li>
<li>ivans.pub</li>
<li>iqbal.pub</li>
</ul>
<p>Maka perintah untuk menggenerate key, mengenkripsi key tersebut, dan menampilkan hasilnya dalam bentuk terenkripsi sesuai public key masing-masing adalah sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vault operator init -key-shares=5 -key-threshold=3 \
-pgp-keys="endy.pub,anggi.pub,dadang.pub,ivans.pub,iqbal.pub"
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Key 1: wcBMA37rwGt6FS1VAQgAk1q8XQh6yc...
Key 2: wcBMA0wwnMXgRzYYAQgAavqbTCxZGD...
Key 3: wcFMA2DjqDb4YhTAARAAeTFyYxPmUd...
Key 4: wcFMAw0SPiCRMPCuAcxybCRqhF35Hf...
Key 5: wcBMAUS8ElQh9BLgzuq0nq/+hBYszJ...
</code></pre></div></div>
<p>Key yang dihasilkan tampil dalam bentuk terenkripsi. Untuk melihat isi key yang asli, perlu didekripsi dulu menggunakan private key dari masing-masing user yang disebutkan dalam perintah command line. Urutan key yang tampil di output sesuai dengan urutan penyebutan public key di command yang kita jalankan.</p>
<p>Selanjutnya, untuk mengaktifkan Vault, kita harus melakukan <code class="language-plaintext highlighter-rouge">Unseal</code>. Berikut tampilannya</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/04-unseal.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/04-unseal.png" alt="Prosedur Unseal" /></a></p>
<p>Bila <code class="language-plaintext highlighter-rouge">unseal key</code> ada dalam format terenkripsi (karena menggunakan GPG), maka kita bisa menampilkan key aslinya dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo "wcBMA37..." | base64 --decode | gpg -dq
</code></pre></div></div>
<p>Tentunya perintah <code class="language-plaintext highlighter-rouge">gpg -dq</code> tersebut hanya bisa dijalankan di komputer yang terpasang private key GPG.</p>
<p>Kita harus memasukkan key sejumlah yang kita tentukan pada <code class="language-plaintext highlighter-rouge">minimum threshold</code> di langkah sebelumnya. Setelah semua key dimasukkan, maka Vault akan berada dalam kondisi <code class="language-plaintext highlighter-rouge">Unsealed</code> dan siap digunakan. Ini bisa dilihat dari indikator di kanan atas yang berwarna hijau.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/05-root-login.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/05-root-login.png" alt="Login" /></a></p>
<p>Kita bisa login dengan <code class="language-plaintext highlighter-rouge">Root Token</code> yang ikut digenerate bersama perintah <code class="language-plaintext highlighter-rouge">Initialize</code> tadi. Setelah login, maka kita bisa melihat isi database Vault.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/06-login-success.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/setup-vault-production/06-login-success.png" alt="Isi Vault" /></a></p>
<p>Setelah login, maka kita bisa:</p>
<ul>
<li>mendaftarkan aplikasi yang dibolehkan mengakses Vault</li>
<li>memasukkan data rahasia ke dalam vault, misalnya : credential untuk mengakses database, API key, dan sebagainya</li>
</ul>
<p>Demikianlah cara instalasi dan konfigurasi Vault secara minimalis. Tentunya untuk skala production yang besar, kita harus menjalankan Vault dengan konfigurasi High Availability dan pengaturan Policy yang komplit. Tapi untuk keperluan aplikasi sederhana, yang seperti ini sudah memadai.</p>
<p>Selamat mencoba. Semoga bermanfaat …</p>
Enkripsi Data dalam Aplikasi2021-08-23T07:14:00+07:00https://software.endy.muhardin.com/java/enkripsi-data-di-aplikasi<p>Dalam membuat aplikasi, kita seringkali butuh untuk menyimpan data user, misalnya:</p>
<ul>
<li>Nomor KTP</li>
<li>Nama ibu kandung</li>
<li>Upload foto KTP</li>
<li>Ijazah</li>
<li>dan berbagai informasi pribadi lainnya</li>
</ul>
<p>Sebagai programmer yang bertanggung jawab, sudah seharusnya kita melakukan usaha yang memadai untuk melindungi data-data pribadi user kita tersebut. Tidak perlu jumawa mengenai keamanan sistem kita, bahkan perusahaan besar sekelas Amazon, Facebook, Tokopedia saja sudah menerima kunjungan hacker. Apalagi kita-kita rakyat kecil begini :D</p>
<p>Jadi, dalam merancang aplikasi, kita harus berasumsi bahwa suatu saat cracker akan bisa masuk ke dalam server kita dan mengambil data aplikasi. Baik itu data dalam database server (MySQL, PostgreSQL, dsb) ataupun file-file yang diupload oleh user.</p>
<p>Kita harus mencegah usaha cracker tersebut untuk membaca data-data pribadi user. Sehingga walaupun dia punya filenya, tapi dia tidak bisa membaca informasi di dalamnya.</p>
<p>Dalam artikel berikut ini, kita akan membahas cara yang bisa kita lakukan untuk mengamankan data pribadi tersebut.</p>
<!--more-->
<h2 id="konsep-pengelolaan-data-rahasia">Konsep Pengelolaan Data Rahasia</h2>
<p>Secara garis besar, ada dua jenis data rahasia yang kita kelola dalam aplikasi, yaitu:</p>
<ol>
<li>Data yang tidak perlu diketahui informasi aslinya. Contohnya password. Kita tidak perlu tahu isi password yang asli. Karena kita cuma perlu membandingkan password yang diinput user dengan password yang tersimpan di database.</li>
<li>Data yang harus diketahui informasi aslinya. Contohnya nomor identitas (NIK), foto KTP, dan sebagainya. User perlu melihat file aslinya untuk melakukan.</li>
</ol>
<p>Untuk data kategori pertama, cara yang tepat untuk mengamankannya adalah dengan metode <code class="language-plaintext highlighter-rouge">hash</code>. Algoritma hash mengkonversi informasi asli menjadi informasi acak yang tidak bisa dikembalikan ke kondisi awal. Untuk penjelasan lebih detail bisa ditonton di video saya di Youtube</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/NOMGdmhHPsw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Kita harus menggunakan algoritma hash khusus untuk password, yaitu bcrypt, scrypt, atau pbkdf2. Kalau kita menggunakan Spring Security, caranya tidak sulit. Berikut contohnya menggunakan BCrypt</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">PasswordEncoder</span> <span class="n">bcryptEncoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BCryptPasswordEncoder</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">hashedPassword</span> <span class="o">=</span> <span class="n">bcryptEncoder</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="n">plainPassword</span><span class="o">);</span>
</code></pre></div></div>
<p>Untuk kategori kedua, cara yang tepat adalah dengan metode enkripsi. Sehingga nilai asli yang sudah dienkripsi bisa dikembalikan ke nilai semula pada saat akan digunakan. Penjelasan detail mengenai enkripsi juga sudah ada di youtube</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/2e0kl1C-7F0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Dalam melakukan enkripsi, algoritma yang kita gunakan adalah algoritma yang sudah dikenal dan dinyatakan kuat dan aman secara ilmiah oleh para ahli kriptografi. Kita jangan mengarang-ngarang algoritma sendiri, kecuali kita adalah profesor di bidang kriptografi.</p>
<p>Keamanan suatu enkripsi bergantung pada keamanan penyimpanan keynya. Jadi walaupun semua orang di dunia mengetahui rumus/algoritma/cara kerja enkripsinya, kalau dia tidak punya <code class="language-plaintext highlighter-rouge">key</code>, maka dia tidak bisa melakukan dekripsi data dan melihat isi aslinya.</p>
<p>Untuk memudahkan saya menulis artikel, kita akan menggunakan beberapa istilah, yaitu:</p>
<ul>
<li>plaintext : informasi asli</li>
<li>ciphertext : informasi yang telah dienkripsi sehingga tidak terbaca</li>
</ul>
<h2 id="alternatif-solusi">Alternatif Solusi</h2>
<p>Untuk melakukan enkripsi, kita bisa membuat key sendiri dan mengenkripsi data dengan key tersebut. Caranya sudah pernah saya tulis <a href="/java/symmetric-encryption-dengan-java/">di artikel terdahulu</a>. Akan tetapi, kemudian kita akan menemui masalah key management, yaitu bagaimana kita akan :</p>
<ul>
<li>membuat key untuk masing-masing keperluan (key untuk NIK, key untuk file KTP, key untuk nomor kartu kredit, dsb)</li>
<li>menyimpan key tersebut secara aman</li>
<li>melakukan rotasi/penggantian key secara berkala</li>
<li>membuat mapping data mana menggunakan key yang mana</li>
</ul>
<p>Nah, ternyata cukup banyak juga yang harus kita kerjakan untuk urusan enkripsi ini. Apakah kita harus buat sendiri? Tidak perlu. Dengan keajaiban open source, kita tinggal cari aplikasi yang sesuai untuk urusan <code class="language-plaintext highlighter-rouge">key management</code> ini.</p>
<h2 id="key-management-system">Key Management System</h2>
<p>Tempat penyimpanan key yang paling aman adalah menggunakan Hardware Security Module (HSM). Ini adalah mesin khusus yang tugasnya menyimpan key. Dia memiliki berbagai perangkat pendeteksi aksi pembobolan. Bila dia merasa sedang dibobol, maka dia akan menghapus key yang dia simpan, sehingga orang tidak bisa membacanya.</p>
<p>Akan tetapi, HSM ini harganya relatif mahal. Bisa seharga mobil baru. Oleh karena itu, kita akan menggunakan penyimpanan key berupa aplikasi open source yang gratis, yaitu <a href="https://www.vaultproject.io">Vault</a>. Aplikasi Vault ini sudah diakui dan dipakai di berbagai skenario production.</p>
<h2 id="arsitektur-aplikasi-dengan-vault">Arsitektur Aplikasi dengan Vault</h2>
<p>Untuk memahami penggunaan Vault, berikut adalah aplikasi kita sebelum melakukan enkripsi data pribadi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/01-arsitektur-sebelum-enkripsi.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/01-arsitektur-sebelum-enkripsi.jpg" alt="Diagram sebelum pakai vault" /></a></p>
<p>Database kita terlihat seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/05-db-sebelum-enkripsi.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/05-db-sebelum-enkripsi.png" alt="Database sebelum pakai vault" /></a></p>
<p>Dan penyimpanan file kita seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/03-file-sebelum-enkripsi.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/03-file-sebelum-enkripsi.png" alt="Folder sebelum pakai vault" /></a></p>
<p>Dan berikut ini adalah arsitektur aplikasi kita setelah menggunakan Vault.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/02-arsitektur-setelah-enkripsi.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/02-arsitektur-setelah-enkripsi.jpg" alt="Diagram setelah pakai vault" /></a></p>
<p>Database yang sudah terenkripsi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/06-db-setelah-enkripsi.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/06-db-setelah-enkripsi.png" alt="Database setelah pakai vault" /></a></p>
<p>Dan penyimpanan file yang sudah terenkripsi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/04-file-setelah-enkripsi.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-data-pribadi/04-file-setelah-enkripsi.png" alt="Folder setelah pakai vault" /></a></p>
<p>Secara garis besar, berikut adalah langkah-langkah yang kita lakukan pada waktu user memasukkan data pribadi:</p>
<ol>
<li>Input di aplikasi</li>
<li>Kirim informasi tersebut ke Vault untuk dienkripsi</li>
<li>Terima ciphertext dari Vault</li>
<li>Insert ciphertext ke database/file</li>
</ol>
<p>Dan berikut adalah yang kita lakukan pada waktu user ingin melihat datanya:</p>
<ol>
<li>Baca ciphertext dari database/file</li>
<li>Kirim ke Vault untuk didekripsi</li>
<li>Terima plaintext dari Vault</li>
<li>Tampilkan datanya</li>
</ol>
<h2 id="koneksi-ke-vault">Koneksi ke Vault</h2>
<p>Kode program untuk membuat koneksi ke Vault seperti ini.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@PropertySource</span><span class="o">(</span><span class="s">"classpath:application.properties"</span><span class="o">)</span>
<span class="nd">@Import</span><span class="o">(</span><span class="nc">EnvironmentVaultConfiguration</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VaultConfig</span> <span class="o">{</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">VaultOperations</span> <span class="nf">vaultOperations</span><span class="o">(</span><span class="nc">EnvironmentVaultConfiguration</span> <span class="n">config</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">VaultTemplate</span><span class="o">(</span><span class="n">config</span><span class="o">.</span><span class="na">vaultEndpoint</span><span class="o">(),</span> <span class="n">config</span><span class="o">.</span><span class="na">clientAuthentication</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kode program di atas membaca file konfigurasi di <code class="language-plaintext highlighter-rouge">application.properties</code>. Untuk menyederhakanan contoh, kita menggunakan metode otentikasi <code class="language-plaintext highlighter-rouge">token</code> ke Vault. Nantinya pada waktu sudah naik production, kita akan menggunakan metode otentikasi lain yang lebih serius seperti AppRole atau Vault Agent. Berikut isi <code class="language-plaintext highlighter-rouge">application.properties</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vault.uri=http://localhost:8200
vault.token=s.i4cnIV0dNXhUbazeIFShZam8
</code></pre></div></div>
<h2 id="persiapan-vault">Persiapan Vault</h2>
<p>Kita jalankan dulu Vault server dengan mengetik perintah berikut di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vault server -dev
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>==> Vault server configuration:
Api Address: http://127.0.0.1:8200
Cgo: disabled
Cluster Address: https://127.0.0.1:8201
Go Version: go1.16.6
Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
Log Level: info
Mlock: supported: false, enabled: false
Recovery Mode: false
Storage: inmem
Version: Vault v1.8.1
Version Sha: 4b0264f28defc05454c31277cfa6ff63695a458d
==> Vault server started! Log data will stream in below:
2021-08-22T16:47:27.375+0700 [INFO] proxy environment: http_proxy="" https_proxy="" no_proxy=""
2021-08-22T16:47:27.375+0700 [WARN] no `api_addr` value specified in config or in VAULT_API_ADDR; falling back to detection if possible, but this value should be manually set
2021-08-22T16:47:27.376+0700 [INFO] core: security barrier not initialized
2021-08-22T16:47:27.376+0700 [INFO] core: security barrier initialized: stored=1 shares=1 threshold=1
2021-08-22T16:47:27.376+0700 [INFO] core: post-unseal setup starting
2021-08-22T16:47:27.380+0700 [INFO] core: loaded wrapping token key
2021-08-22T16:47:27.380+0700 [INFO] core: successfully setup plugin catalog: plugin-directory=""
2021-08-22T16:47:27.380+0700 [INFO] core: no mounts; adding default mount table
2021-08-22T16:47:27.381+0700 [INFO] core: successfully mounted backend: type=cubbyhole path=cubbyhole/
2021-08-22T16:47:27.381+0700 [INFO] core: successfully mounted backend: type=system path=sys/
2021-08-22T16:47:27.381+0700 [INFO] core: successfully mounted backend: type=identity path=identity/
2021-08-22T16:47:27.383+0700 [INFO] core: successfully enabled credential backend: type=token path=token/
2021-08-22T16:47:27.383+0700 [INFO] rollback: starting rollback manager
2021-08-22T16:47:27.383+0700 [INFO] core: restoring leases
2021-08-22T16:47:27.383+0700 [INFO] identity: entities restored
2021-08-22T16:47:27.383+0700 [INFO] identity: groups restored
2021-08-22T16:47:27.383+0700 [INFO] core: post-unseal setup complete
2021-08-22T16:47:27.384+0700 [INFO] expiration: lease restore complete
2021-08-22T16:47:27.384+0700 [INFO] core: root token generated
2021-08-22T16:47:27.384+0700 [INFO] core: pre-seal teardown starting
2021-08-22T16:47:27.384+0700 [INFO] rollback: stopping rollback manager
2021-08-22T16:47:27.384+0700 [INFO] core: pre-seal teardown complete
2021-08-22T16:47:27.384+0700 [INFO] core.cluster-listener.tcp: starting listener: listener_address=127.0.0.1:8201
2021-08-22T16:47:27.384+0700 [INFO] core.cluster-listener: serving cluster requests: cluster_listen_address=127.0.0.1:8201
2021-08-22T16:47:27.384+0700 [INFO] core: post-unseal setup starting
2021-08-22T16:47:27.384+0700 [INFO] core: loaded wrapping token key
2021-08-22T16:47:27.384+0700 [INFO] core: successfully setup plugin catalog: plugin-directory=""
2021-08-22T16:47:27.384+0700 [INFO] core: successfully mounted backend: type=system path=sys/
2021-08-22T16:47:27.384+0700 [INFO] core: successfully mounted backend: type=identity path=identity/
2021-08-22T16:47:27.384+0700 [INFO] core: successfully mounted backend: type=cubbyhole path=cubbyhole/
2021-08-22T16:47:27.385+0700 [INFO] core: successfully enabled credential backend: type=token path=token/
2021-08-22T16:47:27.385+0700 [INFO] rollback: starting rollback manager
2021-08-22T16:47:27.385+0700 [INFO] core: restoring leases
2021-08-22T16:47:27.385+0700 [INFO] expiration: lease restore complete
2021-08-22T16:47:27.385+0700 [INFO] identity: entities restored
2021-08-22T16:47:27.385+0700 [INFO] identity: groups restored
2021-08-22T16:47:27.385+0700 [INFO] core: post-unseal setup complete
2021-08-22T16:47:27.385+0700 [INFO] core: vault is unsealed
2021-08-22T16:47:27.386+0700 [INFO] core: successful mount: namespace="" path=secret/ type=kv
2021-08-22T16:47:27.397+0700 [INFO] secrets.kv.kv_54d0d613: collecting keys to upgrade
2021-08-22T16:47:27.397+0700 [INFO] secrets.kv.kv_54d0d613: done collecting keys: num_keys=1
2021-08-22T16:47:27.397+0700 [INFO] secrets.kv.kv_54d0d613: upgrading keys finished
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variable:
$ export VAULT_ADDR='http://127.0.0.1:8200'
The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.
Unseal Key: RjzDixgmnX3WVm8W+w/RXjeqq2BIfWGvcjozY/ry/UU=
Root Token: s.i4cnIV0dNXhUbazeIFShZam8
Development mode should NOT be used in production installations!
</code></pre></div></div>
<p>Cek status vault server. Buka terminal satu lagi, kemudian set <code class="language-plaintext highlighter-rouge">VAULT_ADDR</code> dan <code class="language-plaintext highlighter-rouge">VAULT_TOKEN</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='s.i4cnIV0dNXhUbazeIFShZam8'
</code></pre></div></div>
<p>Setelah itu, cek status</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vault status
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.8.1
Storage Type inmem
Cluster Name vault-cluster-cac24b22
Cluster ID 0cde6468-5a95-2d6c-282f-e2d2bd52cbd3
HA Enabled false
</code></pre></div></div>
<p>Untuk melakukan enkripsi/dekripsi ini, kita membutuhkan secret engine <code class="language-plaintext highlighter-rouge">transit</code>. Ini adalah fitur Vault untuk melakukan enkripsi/dekripsi tanpa menyimpan datanya. Jadi kita sendiri yang harus menyimpan data yang sudah dienkripsi. Berikut adalah perintah untuk mengaktifkan secret engine <code class="language-plaintext highlighter-rouge">transit</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vault secrets enable transit
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Success! Enabled the transit secrets engine at: transit/
</code></pre></div></div>
<p>Cek status</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vault secrets list
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_af98da45 per-token private secret storage
identity/ identity identity_a2a21ca7 identity store
secret/ kv kv_54d0d613 key/value secret storage
sys/ system system_20f0a31f system endpoints used for control, policy and debugging
transit/ transit transit_39891d6a n/a
</code></pre></div></div>
<h2 id="enkripsi-dan-dekripsi-string">Enkripsi dan Dekripsi String</h2>
<p>Sebelum mulai melakukan enkripsi, kita harus menyuruh Vault untuk membuat key dan menyimpannya. Key tidak kita simpan sendiri, karena lebih aman kalau disimpan dalam Vault. Memang itu tugasnya dia. Berikut kode program untuk inisialisasi key. Kita lakukan di constructor, karena cuma perlu dilakukan sekali saja.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span> <span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VaultService</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">KEY_TYPE</span> <span class="o">=</span> <span class="s">"aes128-gcm96"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">KEY_ENCRYPT_KTP</span> <span class="o">=</span> <span class="s">"KEY_ENCRYPT_KTP"</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">VaultTransitOperations</span> <span class="n">vaultTransit</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">VaultService</span><span class="o">(</span><span class="nc">VaultOperations</span> <span class="n">vaultOperations</span><span class="o">)</span> <span class="o">{</span>
<span class="n">vaultTransit</span> <span class="o">=</span> <span class="n">vaultOperations</span><span class="o">.</span><span class="na">opsForTransit</span><span class="o">();</span>
<span class="n">vaultTransit</span><span class="o">.</span><span class="na">createKey</span><span class="o">(</span><span class="no">KEY_ENCRYPT_KTP</span><span class="o">,</span>
<span class="nc">VaultTransitKeyCreationRequest</span><span class="o">.</span><span class="na">ofKeyType</span><span class="o">(</span><span class="no">KEY_TYPE</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Berikutnya, kita mengirim informasi yang ingin dienkripsi. Berikut kode programnya untuk mengenkripsi <code class="language-plaintext highlighter-rouge">String</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">String</span> <span class="nf">encrypt</span><span class="o">(</span><span class="nc">String</span> <span class="n">plaintext</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">vaultTransit</span><span class="o">.</span><span class="na">encrypt</span><span class="o">(</span><span class="no">KEY_ENCRYPT_KTP</span><span class="o">,</span> <span class="n">plaintext</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Method tersebut bisa kita panggil seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testEncryptString</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">nik</span> <span class="o">=</span> <span class="s">"123456789"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">encryptedNik</span> <span class="o">=</span> <span class="n">vaultService</span><span class="o">.</span><span class="na">encrypt</span><span class="o">(</span><span class="n">nik</span><span class="o">);</span>
<span class="nc">Assertions</span><span class="o">.</span><span class="na">assertNotNull</span><span class="o">(</span><span class="n">encryptedNik</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Encrypted : "</span> <span class="o">+</span> <span class="n">encryptedNik</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Outputnya seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Encrypted : vault:v1:iiAEK0ewEyOaNe41Z/4A/nnum1r5Kf+ixNGSTvQIMyGZV6vCwfaiQg==
</code></pre></div></div>
<p>Untuk mendapatkan nilai yang asli kembali, kita lakukan dekripsi. Kode programnya seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">String</span> <span class="nf">decrypt</span><span class="o">(</span><span class="nc">String</span> <span class="n">cipherText</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">vaultTransit</span><span class="o">.</span><span class="na">decrypt</span><span class="o">(</span><span class="no">KEY_ENCRYPT_KTP</span><span class="o">,</span> <span class="n">cipherText</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Menjalankannya seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testDecryptString</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">encryptedNik</span> <span class="o">=</span> <span class="s">"vault:v1:OZPO/nkobDFqMsCF5snQGgWoUtLdt2tp/lmr9zF/TAV4rRnH+A=="</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">decryptedNik</span> <span class="o">=</span> <span class="n">vaultService</span><span class="o">.</span><span class="na">decrypt</span><span class="o">(</span><span class="n">encryptedNik</span><span class="o">);</span>
<span class="nc">Assertions</span><span class="o">.</span><span class="na">assertNotNull</span><span class="o">(</span><span class="n">decryptedNik</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Decrypted : "</span> <span class="o">+</span> <span class="n">decryptedNik</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Bila dijalankan, hasilnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Decrypted : 123456789
</code></pre></div></div>
<h2 id="enkripsi-dan-dekripsi-file">Enkripsi dan Dekripsi File</h2>
<p>Untuk file sebetulnya sama saja. Vault sebetulnya hanya menerima jasa enkripsi untuk tipe data <code class="language-plaintext highlighter-rouge">String</code>. Jadi bila kita punya file gambar (jpg, png) atau dokumen (doc, xls, pdf), kita harus konversi dulu menjadi String dengan encoding Base64.</p>
<p>Berikut kode program untuk konversi Base64 dan melakukan enkripsi</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">File</span> <span class="nf">encrypt</span><span class="o">(</span><span class="nc">File</span> <span class="n">plainFile</span><span class="o">){</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">base64Encoded</span> <span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">getEncoder</span><span class="o">().</span><span class="na">encodeToString</span><span class="o">(</span><span class="nc">FileUtils</span><span class="o">.</span><span class="na">readFileToByteArray</span><span class="o">(</span><span class="n">plainFile</span><span class="o">));</span>
<span class="nc">String</span> <span class="n">base64Encrypted</span> <span class="o">=</span> <span class="n">encrypt</span><span class="o">(</span><span class="n">base64Encoded</span><span class="o">);</span>
<span class="nc">File</span> <span class="n">result</span> <span class="o">=</span> <span class="nc">File</span><span class="o">.</span><span class="na">createTempFile</span><span class="o">(</span><span class="n">plainFile</span><span class="o">.</span><span class="na">getName</span><span class="o">()+</span><span class="s">"%"</span><span class="o">,</span> <span class="s">"-enc.txt"</span><span class="o">);</span>
<span class="nc">FileUtils</span><span class="o">.</span><span class="na">writeStringToFile</span><span class="o">(</span>
<span class="n">result</span><span class="o">,</span>
<span class="n">base64Encrypted</span><span class="o">,</span> <span class="nc">StandardCharsets</span><span class="o">.</span><span class="na">UTF_8</span><span class="o">);</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Bila dijalankan, hasilnya seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Encrypted file : /var/folders/qx/dsj14n214d92nqfd0_mgd03c0000gn/T/ktp-plain.png%16902871742519479631-enc.txt
</code></pre></div></div>
<p>File yang didapatkan adalah berupa text file. Berikut sebagian isinya (dipotong karena terlalu panjang)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vault:v1:NDyul09w/otGzBHGepJmQVOV9imzM68SFERrq2Z3aux0KPdvLI/VgSFZt4urPCBjVaMZmZaLkdV6nc1M3pH5Xtc62pUjwSOhDch+FCk/ndyYPaSd2BFyRcxygT76gUEcNzS3UIDfZlFuq1IHWcX3xZ++bDf93xfbXoxv8RvsOP2Yx8ek7Xqedjn8oYP+syZK/uYnPOZAg8BomrmNEVotflylZuFN1oRuC0mUcd9mhbmKPGEzDz+974kOciVb9FFWp/wsm28INgmJl6a5/oZgTOSrVW3zjqB3txwj76o
</code></pre></div></div>
<p>Untuk mengembalikan file terenkripsi menjadi file asli, kita lakukan dekripsi seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">File</span> <span class="nf">decrypt</span><span class="o">(</span><span class="nc">File</span> <span class="n">cipherFile</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">cipherFileContent</span> <span class="o">=</span> <span class="nc">FileUtils</span><span class="o">.</span><span class="na">readFileToString</span><span class="o">(</span><span class="n">cipherFile</span><span class="o">,</span> <span class="nc">StandardCharsets</span><span class="o">.</span><span class="na">UTF_8</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">base64Encoded</span> <span class="o">=</span> <span class="n">decrypt</span><span class="o">(</span><span class="n">cipherFileContent</span><span class="o">);</span>
<span class="nc">File</span> <span class="n">result</span> <span class="o">=</span> <span class="nc">File</span><span class="o">.</span><span class="na">createTempFile</span><span class="o">(</span><span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">(),</span> <span class="s">".png"</span><span class="o">);</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">decryptedFileContent</span> <span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">getDecoder</span><span class="o">().</span><span class="na">decode</span><span class="o">(</span><span class="n">base64Encoded</span><span class="o">);</span>
<span class="nc">FileUtils</span><span class="o">.</span><span class="na">writeByteArrayToFile</span><span class="o">(</span><span class="n">result</span><span class="o">,</span> <span class="n">decryptedFileContent</span><span class="o">);</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Bila dijalankan, berikut adalah hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Decrypted file : /var/folders/qx/dsj14n214d92nqfd0_mgd03c0000gn/T/9156c7d7-d9f4-47d2-abaa-3217c0ce4dc94032189875688153456.png
</code></pre></div></div>
<p>Kita cek di folder yang disebutkan di output. Filenya harusnya sudah terlihat gambar aslinya.</p>
<h2 id="pengembangan-selanjutnya">Pengembangan Selanjutnya</h2>
<p>Pada contoh di atas, kita cuma membuat dan menggunakan satu key saja. Seharusnya nanti kita akan membuat key untuk masing-masing keperluan. Key untuk enkripsi nomor ktp berbeda dengan key untuk enkripsi file ktp. Demikian juga bila kita punya banyak data sensitif lain seperti nomor kartu kredit, foto selfie dengan ktp, nama ibu kandung, dan sebagainya.</p>
<p>Key tersebut juga perlu kita rotasi secara berkala, sehingga bila satu key bocor, tidak semua data bisa dibaca oleh si cracker.</p>
<p>Selamat mencoba. Mudah-mudahan dengan mengimplementasikan artikel ini, aplikasi yang kita buat akan menjadi lebih aman.</p>
<p>Kode program lengkapnya bisa dilihat <a href="https://github.com/endymuhardin/belajar-enkripsi-data">di Github</a></p>
<p>Semoga bermanfaat …</p>
Enkripsi Data in Use2021-01-23T07:14:00+07:00https://software.endy.muhardin.com/aplikasi/enkripsi-data-in-use<p>Di tahun 2021 ini, layanan cloud storage sudah sangat banyak tersedia dengan harga murah. Walaupun sebetulnya bukan hal yang baru – saya termasuk pengguna awal Yahoo! Briefcase sejak tahun 1999 – akan tetapi paket yang ditawarkan saat ini sudah sangat murah dan besar kapasitasnya. Saya juga termasuk pengguna awal Dropbox, kemudian banyak mendapatkan downline, sehingga sekarang punya kapasitas 8GB gratis.</p>
<p>Kemudahan dan otomasi aplikasi layanan cloud storage ini membuat penggunanya, termasuk saya, cenderung abai terhadap keamanan dan privasi data. Kita cenderung terlalu percaya kepada penyedia layanan untuk menjaga data pribadi kita dengan aman. Sehingga kita dengan tenang meletakkan banyak data-data rahasia seperti misalnya scan identitas, foto-foto anggota keluarga (dengan pakaian ‘rumahan’), dokumen perusahaan, dan berbagai file lain yang tentu kita tidak ingin terlihat orang lain.</p>
<p>Pada <a href="/linux/simplifikasi-prosedur-backup/">artikel sebelumnya</a>, kita sudah membahas solusi enkripsi untuk file arsip. File arsip (atau archive dalam bahasa Inggris), adalah file lama yang kita keluarkan dari penyimpanan utama dan disimpan di penyimpanan jangka panjang. Analoginya seperti menyimpan barang yang jarang dipakai dari ruang tamu ke gudang. Berbeda dengan file backup, yang merupakan data yang masih sering dipakai sehari-hari, yang kita buatkan copy-nya supaya ada cadangan. Foto-foto lama saya arsip, dipaketkan menjadi satu folder, dienkripsi, dan kemudian disimpan di penyimpanan jangka panjang seperti Amazon Deep Glacier. Data seperti ini dikenal dengan istilah <a href="https://en.wikipedia.org/wiki/Data_at_rest">data at rest</a>.</p>
<p>Nah kali ini kita akan membahas pengamanan data yang masih sering kita pakai sehari-hari seperti dokumen bisnis, foto-foto keluarga yang baru diambil, scan identitas, data penting client, dan file-file lain yang bersifat rahasia, tapi mobilitasnya tinggi. Data seperti ini dikenal dengan istilah <a href="https://en.wikipedia.org/wiki/Data_in_use">data in use</a>.</p>
<p>Ada satu jenis data lagi, yaitu data yang sedang berpindah dari satu komputer ke komputer lain. Ini dikenal dengan istilah <a href="https://en.wikipedia.org/wiki/Data_in_transit">data in transit</a>. Inipun sudah kita jelaskan cara pengamanannya di <a href="/devops/vpn-wireguard-01-intro/">seri VPN di artikel terdahulu</a> dan <a href="/devops/deployment-microservice-kere-hore-1/">berbagai</a> <a href="/devops/letsencrypt-manual-dns/">artikel</a> <a href="/aplikasi/memasang-sertifikat-ssl/">tentang</a> <a href="/aplikasi/apa-itu-ssl/">SSL</a>.</p>
<p><em>Data in use</em> ini biasanya ada di laptop, pc, smartphone, dan juga flashdisk atau external harddisk. Karena perangkat ini mobilitasnya tinggi, maka resiko berpindah tangan juga tinggi. Coba pikir, seberapa mudah kita meminjamkan harddisk eksternal atau flashdisk ke orang lain? Umumnya ya kita akan berikan ke orang lain tanpa pikir panjang. Bagaimana kalau file rahasia tersebut dicopy orang? Belum lagi kemungkinan laptop atau smartphone dicuri orang. Kalau datanya tidak terenkripsi, orang jahat akan dengan mudah membaca data tersebut.</p>
<!--more-->
<p>Ada banyak aplikasi untuk mengenkripsi data seperti ini. Akan tetapi, biasanya bisa dikelompokkan menjadi dua kategori utama:</p>
<ul>
<li>Full Disk Encryption (FDE)</li>
<li>File Based Encryption</li>
</ul>
<h2 id="full-disk-encryption">Full Disk Encryption</h2>
<p>Full disk encryption adalah solusi untuk mengenkripsi keseluruhan disk (harddisk ataupun flashdisk). Kita format dulu disk kosong dengan filesystem terenkripsi, kemudian kita <em>mount</em> disk tersebut agar terlihat dalam bentuk tidak terenkripsi. Pada waktu melakukan <em>mount</em>, kita akan diminta password untuk membuka enkripsi. Setelah dimount, kita bisa baca tulis file seperti biasa. Setelah selesai dipakai, kita <em>unmount</em> disk tersebut. Bila disk tersebut dipasang di komputer dan penggunanya tidak bisa memasukkan password yang sesuai, maka seluruh isi disk tidak akan terbaca.</p>
<p>Full disk encryption ini cocok digunakan untuk partisi utama di perangkat yang kita gunakan, seperti harddisk laptop, pc, ataupun smartphone. Partisi utama ini sekali dibuka, dipakai sepanjang perangkat hidup dan dipakai. Setelah perangkat dimatikan, otomatis dia unmount dan tidak akan terbaca. Jadi disk kita aman, walaupun perangkatnya dibuka dan disknya dipasang di komputer lain, datanya tidak akan bisa dibaca.</p>
<p>Beberapa aplikasi full disk encryption yang populer diantaranya:</p>
<ul>
<li>FileVault : aplikasi FDE bawaan MacOS</li>
<li>eCryptFS : dulu saya pakai ini waktu laptopnya masih Ubuntu</li>
<li>LUKS : opsi default FDE di Ubuntu saat ini (20.04)</li>
</ul>
<p>Buat yang pakai Windows, silahkan dicari sendiri ya …
Atau ya pindah Ubuntu atau MacOS ajalah :D</p>
<h2 id="file-based-encryption">File Based Encryption</h2>
<p>FDE walaupun sangat seamless (tidak terasa pemakaiannya) untuk partisi harddisk, dia kurang cocok untuk enkripsi folder per folder. Misalnya kita punya 10 folder yang tersinkronisasi ke Dropbox, 2 folder diantaranya berisi data rahasia. Kita tidak bisa menggunakan FDE untuk keperluan ini. Untuk itu, kita butuh solusi lain, yaitu FBE (file based encryption). FBE bisa mengenkripsi satu folder (berikut isinya) saja.</p>
<p>Aplikasi FBE bekerja di level file. Kalau dalam satu folder ada 15 file, maka di hasil enkripsi akan ada 15 file juga. Contohnya, ini folder dalam kondisi tidak terenkripsi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-fbe/folder-plain.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-fbe/folder-plain.png" alt="Folder tidak terenkripsi" /></a></p>
<p>dan ini hasil enkripsinya</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-fbe/folder-encrypted.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-fbe/folder-encrypted.png" alt="Folder terenkripsi" /></a></p>
<p>Aplikasi FDE ini cocok diterapkan untuk layanan cloud storage yang otomatis mensinkronisasi folder tertentu, seperti Dropbox. Kita bisa menaruh folder yang terenkripsi di folder Dropbox. Bila kita butuh filenya, kita bisa decrypt dan mount ke folder lain. Bila kita mengubah isi folder yang dimount, maka folder terenkripsinya akan langsung berubah juga. Tapi ingat, jangan mount-decrypt ke folder dalam Dropbox, supaya tidak disync sama dia ke cloud.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-fbe/proses-enkripsi.gif"><img src="https://software.endy.muhardin.com/images/uploads/2021/enkripsi-fbe/proses-enkripsi.gif" alt="Proses Enkripsi" /></a></p>
<p>Ada beberapa pilihan untuk aplikasi FBE, diantaranya:</p>
<ul>
<li>Cryptomator</li>
<li>GoCryptFS</li>
<li>EncFS</li>
<li>CryFS</li>
</ul>
<p>Beberapa artikel membahas perbandingan di antara alternatif tersebut, yaitu:</p>
<ul>
<li><a href="https://www.cryfs.org/comparison/">Perbandingan oleh pembuat CryFS</a></li>
<li><a href="https://nuetzlich.net/gocryptfs/comparison/">Perbandingan oleh pembuat GoCryptFS</a></li>
</ul>
<p>Tentu ada bias dalam artikel di atas, karena yang menulis adalah pembuat salah satu aplikasinya. Tapi lumayan untuk menambah informasi.</p>
<p>Silahkan dipilih tools mana yang kira-kira cocok dengan selera Anda. Saat ini saya sedang explore Cryptomator dan GoCryptFS. So far GoCryptFS terlihat paling menarik.</p>
<h2 id="cara-penggunaan-gocryptfs">Cara Penggunaan GoCryptFS</h2>
<p>Sebagai ilustrasi, untuk membuat folder enkripsi seperti pada animasi di atas, berikut perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir encrypted decrypted
gocryptfs -init encrypted
</code></pre></div></div>
<p>Dia akan menanyakan password untuk melakukan enkripsi. Password ini kemudian akan <em>dienhance</em> menggunakan algoritma <em>scrypt</em> menjadi <em>Key Encrypting Key (KEK)</em>. Setelah itu, <code class="language-plaintext highlighter-rouge">gocryptfs</code> akan membuat dua master key, satu untuk mengenkripsi nama file, satunya untuk mengenkripsi isi file. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Choose a password for protecting your files.
Password:
Repeat:
Your master key is:
1d6dd761-31c94976-b4070b98-f6625425-
c0d35bdd-a9b41578-9de318b0-28e073b9
If the gocryptfs.conf file becomes corrupted or you ever forget your password,
there is only one hope for recovery: The master key. Print it to a piece of
paper and store it in a drawer. This message is only printed once.
The gocryptfs filesystem has been created successfully.
You can now mount it using: gocryptfs encrypted MOUNTPOINT
</code></pre></div></div>
<p>Kemudian dia akan mengenkripsi kedua master key dengan KEK tadi, dan menulisnya di file <code class="language-plaintext highlighter-rouge">gocryptfs.conf</code>. Selain itu dia juga akan membuat file <em>Initialization Vector</em> yaitu <code class="language-plaintext highlighter-rouge">gocryptfs.diriv</code>.</p>
<p>Kita harus simpan kedua master key ini di tempat aman, supaya kalau file konfigurasinya hilang, kita tetap bisa mendekripsi file-file kita.</p>
<p>Selanjutnya, kita <em>mount</em> folder terenkripsi tadi supaya terlihat file dalam bentuk <em>plain</em>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gocryptfs encrypted decrypted
</code></pre></div></div>
<p>Dia akan meminta password untuk mendekripsi master key, kemudian dengan master key tersebut mendekripsi nama file dan isi file.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Password:
Decrypting master key
Filesystem mounted and ready.
</code></pre></div></div>
<p>Sekarang kita bisa mengisi/mengubah isi folder <code class="language-plaintext highlighter-rouge">decrypted</code>. Hasilnya akan langsung terlihat di folder <code class="language-plaintext highlighter-rouge">encrypted</code>.</p>
<p>Setelah selesai menggunakan, kita bisa <em>unmount</em> dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>umount decrypted
</code></pre></div></div>
<p>Sekarang folder <em>mount</em> sudah kosong kembali. Kita bisa hapus kalau mau.</p>
<p>Buat yang lebih suka versi GUI, bisa coba <a href="https://mhogomchungu.github.io/sirikali/">SiriKali</a> atau sekalian <a href="https://cryptomator.org/">Cryptomator</a> yang UInya lebih bagus.</p>
<p>Selamat mencoba, semoga bermanfaat …</p>
Simplifikasi Prosedur Backup2021-01-19T14:00:29+07:00https://software.endy.muhardin.com/linux/simplifikasi-prosedur-backup<p>Sebelumnya, saya sudah beberapa kali menulis artikel tentang backup, diantaranya:</p>
<ul>
<li><a href="https://software.endy.muhardin.com/linux/backup-home-folder/">Backup Home Folder</a></li>
<li><a href="https://software.endy.muhardin.com/linux/backup-duplicity/">Backup dengan Duplicity</a></li>
</ul>
<p>dan masih banyak lagi yang lebih spesifik tentang backup untuk aplikasi tertentu seperti MySQL, Subversion, dan lainnya.</p>
<p>Di awal 2021 ini, kondisi sudah banyak berubah, diantaranya :</p>
<ul>
<li>semua kode program sudah disimpan di repository git</li>
<li>internet sudah sangat tersedia, sehingga tidak perlu lagi mengumpulkan file-file installer</li>
<li>cloud storage service sudah banyak tersedia. Dropbox, Google Drive, iCloud, dan sebagainya. Sehingga dokumen-dokumen sudah langsung tersinkronisasi ke cloud. Bahkan sebagian besar dokumen saya (tulisan, spreadsheet, gambar) sudah dibuat di aplikasi berbasis cloud seperti Google Docs dan sejenisnya.</li>
</ul>
<p>Yang tersisa harus saya backup secara manual tinggal koleksi foto dan video pribadi saja. Sampai saat ini saya masih memotret dan merekam video menggunakan <em>kamera betulan</em>, yang ukuran filenya satu foto bisa mencapai belasan MB. Walaupun smartphone sudah bisa mengambil foto berkualitas lumayan, tapi tetap ergonomi dan user experience menggunakan kamera dslr/mirrorless belum terkalahkan. Belum lagi hasil fotonya, kalau kita buka di komputer, akan terlihat beda kualitasnya.</p>
<p>Selain itu, saya juga secara rutin memindahkan file foto dan video dari smartphone ke external disk. Sehingga tetap saja foto dan video dari smartphone tersebut harus dibackup juga.</p>
<p>Sebagai gambaran, berikut ukuran arsip foto/video saya dari tahun ke tahun</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/backup-2021/ukuran-folder.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/backup-2021/ukuran-folder.png" alt="Ukuran Foto/Video" /></a></p>
<p>Jadi pada artikel kali ini, kita akan membahas prosedur backup untuk foto dan video tersebut.</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#goals--non-goals" id="markdown-toc-goals--non-goals">Goals & Non-Goals</a></li>
<li><a href="#persiapan-backup" id="markdown-toc-persiapan-backup">Persiapan Backup</a></li>
<li><a href="#enkripsi" id="markdown-toc-enkripsi">Enkripsi</a></li>
<li><a href="#test-restore" id="markdown-toc-test-restore">Test Restore</a></li>
<li><a href="#copy-backup-ke-harddisk-lain" id="markdown-toc-copy-backup-ke-harddisk-lain">Copy backup ke harddisk lain</a></li>
<li><a href="#cloud-backup" id="markdown-toc-cloud-backup">Cloud Backup</a></li>
</ul>
<h2 id="goals--non-goals">Goals & Non-Goals</h2>
<p>Secara prinsip, backup tetap mengikuti kaidah 3-2-1, yaitu :</p>
<ul>
<li>Ada 3 copy dari data yang mau dibackup (satu data utama dan dua backup)</li>
<li>Terdiri dari minimal 2 jenis media (external disk, CD/DVD, cloud, dsb)</li>
<li>1 copy harus berada di lokasi yang terpisah sejauh minimal 30 km</li>
</ul>
<p>Saat ini saya sudah punya 2 copy, yaitu di laptop dan di external harddisk. Kurang satu copy lagi, di media berbeda, dan di lokasi yang berbeda. Jaman now ya tentu saja kita pakai cloud services.</p>
<p>Ada banyak layanan cloud services untuk menyimpan data. Harga dan kapasitasnya berbeda-beda, tergantung paket promo. Akan tetapi, karena ini backup, maka saya akan pilih provider yang bonafit dan meyakinkan. Saat ini ya Amazon dan Google.</p>
<p>Provider manapun yang dipilih, tetap saja kita harus mengenkripsi data tersebut. Apalagi ini koleksi foto dan video pribadi. Walaupun saya bukan artis, tapi tetap saja itu data rahasia yang harus diamankan. Mengikuti kaidah computer security, prosedur dan algoritma enkripsi tidak rahasia. Semakin umum dan terbuka prosedur/algoritma, maka akan semakin aman karena sudah direview banyak orang. Kita cukup mengamankan <code class="language-plaintext highlighter-rouge">key</code> enkripsinya saja.</p>
<p>Jadi, tujuan yang ingin kita capai dari prosedur backup ini adalah :</p>
<ol>
<li>Membuat backup untuk folder foto dan video</li>
<li>Backupnya harus terenkripsi</li>
<li>Hasil enkripsi tersimpan di 2 harddisk di lokasi berbeda dan 1 cloud provider</li>
</ol>
<p>Dan ada hal yang tidak akan dilakukan (non-goal) pada prosedur backup ini :</p>
<ul>
<li>Kita tidak melakukan backup incremental, cukup lakukan full backup sebulan sekali.</li>
</ul>
<h1 id="persiapan-backup">Persiapan Backup</h1>
<p>File foto dan video saya sudah tersusun secara kronologis dalam folder tahun, bulan, kemudian tanggal. Beberapa folder yang ukurannya kecil, saya gabungkan supaya hasil akhirnya lumayan besar. Ini dilakukan untuk mengefisienkan proses upload, dan juga karena produk Amazon Deep Glacier yang saya gunakan justru memasang tarif lebih mahal bila filenya kecil-kecil.</p>
<p>Folder yang berisi banyak file tersebut kemudian kita satukan menjadi satu file. Sebetulnya kita tidak perlu kompresi di sini, karena file foto dan video sudah dalam kondisi terkompres dari sananya. Tapi untuk alasan konsistensi dan familiaritas, saya gunakan command yang sama dengan kompresi backup pada umumnya. Berikut adalah perintah untuk menggabungkan dan mengkompres satu folder bernama <code class="language-plaintext highlighter-rouge">1994-2010</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tar cvzf ./upload-aws/1994-2010.tar.gz 1994-2010
</code></pre></div></div>
<p>Berikut hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -lh ./upload-aws/
-rwxr-xr-x 1 endymuhardin staff 11G Jan 18 12:54 1994-2010.tar.gz
</code></pre></div></div>
<h1 id="enkripsi">Enkripsi</h1>
<p>Selanjutnya, kita akan mengenkripsi file 11GB tersebut agar tidak bisa dibuka orang lain. Secara umum ada 2 metode enkripsi : asymmetric dan symmetric. Penjelasan detailnya bisa ditonton di <a href="https://www.youtube.com/watch?v=2e0kl1C-7F0&list=PL9oC_cq7OYbwClMMWLTgXr3zz9sQ_JW76&index=8">video saya di Youtube</a>.</p>
<p>Kita akan menggunakan metode hybrid, dimana enkripsi akan dilakukan dengan metode symmetric yang lebih ringan di CPU, tapi symmetric keynya akan digenerate secara otomatis dan dienkripsi lagi dengan asymmetric encryption yang lebih aman. Sesuai dengan <a href="https://serverfault.com/a/654468">anjuran netizen di Stack Overflow</a>.</p>
<p>Cara ini bisa dilakukan dengan mudah dengan menggunakan aplikasi GPG. Penjelasan detailnya sudah pernah saya tulis di <a href="https://software.endy.muhardin.com/linux/menggunakan-gpg/">artikel terdahulu</a>. Kita tidak akan bahas lagi rinciannya, langsung saja ke langkah-langkahnya.</p>
<ol>
<li>
<p>Kita generate dulu keypairnya kalau belum ada. Perintahnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gpg --full-generate-key
</code></pre></div> </div>
</li>
<li>
<p>Jangan lupa bikin revocation certificate dan backup keypair dalam format teks terenkripsi</p>
<ul>
<li>membuat revocation certificate</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gpg --gen-revoke -a endy.muhardin@gmail.com > endymuhardin-revoke.asc
</code></pre></div> </div>
<ul>
<li>membuat backup keypair</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gpg --export-secret-keys -a endy.muhardin@gmail.com | gpg --symmetric --cipher-algo AES256 -a > endymuhardin-encrypted.asc
</code></pre></div> </div>
</li>
<li>
<p>Enkripsi file hasil backup</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gpg --encrypt --recipient endy.muhardin@gmail.com 1994-2010.tar.gz
</code></pre></div> </div>
</li>
</ol>
<h1 id="test-restore">Test Restore</h1>
<p>Salah satu langkah krusial dalam membuat backup adalah <strong>test restore</strong>. Jangan sampai kita membuat backup, menyimpan dengan aman, lalu merasa tenang. Di kemudian hari, pada waktu kita butuh mengambil kembali backup tersebut, ternyata tidak bisa dibuka. Untuk mencegah hal tersebut, kita perlu mencoba membuka hasil backup yang sudah terenkripsi tersebut.</p>
<p>Prosedurnya adalah sebagai berikut. Lakukan prosedur ini di komputer lain untuk memastikan tidak ada dependensi sama sekali terhadap laptop yang sekarang kita gunakan untuk enkripsi. Kita bisa gunakan VPS, atau virtual box di laptop untuk memastikan prosedur ini berjalan dengan baik di komputer yang fresh.</p>
<ol>
<li>
<p>Import gpg key terenkripsi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gpg --decrypt -a --output - endymuhardin-encrypted.asc | gpg --batch --import
</code></pre></div> </div>
</li>
<li>
<p>Decrypt file backup</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gpg --decrypt-files *.gpg
</code></pre></div> </div>
</li>
<li>
<p>Buka kompresi tarball</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> tar xvzf 1994-2010.tar.gz
</code></pre></div> </div>
</li>
<li>
<p>Cek random isi file dan foldernya. Pastikan foldernya bisa diakses, dan filenya bisa dibuka.</p>
</li>
</ol>
<h1 id="copy-backup-ke-harddisk-lain">Copy backup ke harddisk lain</h1>
<p>Setelah file backup sudah dipastikan oke, kita bisa membuat checksum untuk verifikasi file. Untuk membuatnya, kita perlu menginstal aplikasi <code class="language-plaintext highlighter-rouge">sha256sum</code>. Di MacOS, cara installnya sebagai berikut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install coreutils
</code></pre></div></div>
<p>Setelah itu, kita pastikan versi yang terinstall</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sha256sum --version
sha256sum (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Ulrich Drepper, Scott Miller, and David Madore.
</code></pre></div></div>
<p>Kita bisa langsung membuatkan file checksum untuk semua file dalam folder</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sha256sum -b * > SHA256SUMS
</code></pre></div></div>
<p>Outputnya adalah file teks berisi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a601da25e7f4bdb8efa745a13cb6d400ab58cc98eacf2b69635aa8a2fdeeacea *1994-2010.tar.gz.gpg
</code></pre></div></div>
<p>Kemudian kita bisa mengcopy seluruh file backup berikut file checksum tersebut. Nanti di lokasi tujuan, kita bisa verifikasi dengan perintah ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sha256sum -c SHA256SUMS
</code></pre></div></div>
<p>Output yang benar harusnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1994-2010.tar.gz.gpg: OK
</code></pre></div></div>
<h1 id="cloud-backup">Cloud Backup</h1>
<p>Setelah file backup yang terenkripsi tadi dipastikan bisa dibuka dengan lancar dan baik, sekarang kita bisa melakukan upload ke Amazon Deep Glacier. Caranya masih sama seperti penjelasan di <a href="https://software.endy.muhardin.com/linux/backup-duplicity/#backup-ke-amazon-s3-dan-glacier">artikel sebelumnya</a>.</p>
<p>Kita cukup menjalankan perintah berikut di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3 cp 1994-2010.tar.gz.gpg s3://endy-backup/backup-foto
</code></pre></div></div>
<p>Lho katanya simplifikasi, kok masih rumit? Ya sebenarnya dibandingkan dengan versi sebelumnya ada banyak penyederhanaan, yaitu:</p>
<ul>
<li>tidak ada incremental backup</li>
<li>tidak menggunakan tools aneh-aneh seperti duplicity</li>
</ul>
<p>Dan lagipula, prosedurnya tidak terlalu rumit, yaitu:</p>
<ul>
<li>buat tarball</li>
<li>enkripsi</li>
<li>checksum</li>
<li>copy ke disk lain</li>
<li>upload ke cloud</li>
</ul>
<p>menggunakan tools yang umum dipakai administrator linux, yaitu:</p>
<ul>
<li>tar</li>
<li>gpg</li>
<li>sha256sum</li>
<li>rsync</li>
<li>aws cli</li>
</ul>
<p>Selamat mencoba. Semoga bermanfaat …</p>
Baca Tulis NTFS di MacOS2021-01-18T14:00:00+07:00https://software.endy.muhardin.com/mac/ntfs-write-macos<p>Beberapa bulan lalu, Apple merilis MacOS versi terbaru, yaitu Big Sur. Kebetulan laptop saya sudah lama tidak diformat, ada banyak sisa-sisa driver dan paket instalasi yang mengotori sistem. Oleh karena itu, saya segera format dan instal ulang, bukan upgrade. Ternyata, setelah diinstal ulang, external harddisk saya jadi tidak bisa ditulisi.</p>
<p>Sebenarnya ini bukan masalah baru, sistem operasi Mac memang <em>by default</em> tidak bisa menulis ke partisi NTFS. Biasanya para pengguna Mac membeli driver pihak ketiga. Saya biasanya pakai Paragon NTFS. Driver ini sangat dianjurkan bagi pengguna awam karena mudah dipakai. Cukup instal saja, restart, dan kemudian harddisk eksternal langsung bisa ditulis. Saya juga tadinya menggunakan Paragon NTFS ini sejak pertama pakai laptop Apple di tahun 2015. Harga lisensinya sekitar 300 ribu rupiah. Tidak terlalu mahal kalau dibagi durasi pemakaian selama 5 tahun.</p>
<p>Akan tetapi, ternyata lisensi saya tersebut tidak bisa dipakai lagi di MacOS versi Big Sur. Kita harus membeli lisensi baru. Nah, kalo begitu tiba saatnya saya mencari informasi lagi, apakah di tahun 2021 ini kita masih perlu membeli lisensi.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/ntfs-macos/google-ntfs-macos.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/ntfs-macos/google-ntfs-macos.png" alt="Google NTFS MacOS" /></a></p>
<p>Setelah googling, ada beberapa alternatif yang bisa dilakukan (selain beli Paragon), yaitu:</p>
<ul>
<li>Menggunakan driver <code class="language-plaintext highlighter-rouge">osxfuse</code> dan <code class="language-plaintext highlighter-rouge">ntfs-3g</code></li>
<li>Melakukan mount secara manual melalui command line</li>
<li>Membuat konfigurasi di <code class="language-plaintext highlighter-rouge">/etc/fstab</code></li>
<li>Menggunakan aplikasi Mounty.app</li>
</ul>
<!--more-->
<p>Alternatif pertama, tidak saya coba lebih lanjut. Di semua artikel yang saya baca, membahas metode ini, mengharuskan kita untuk men-disable System Integrity Protection (SIP). Saya lebih baik pinjam laptop lain untuk copy file daripada men-disable fitur keamanan. Jadi opsi pertama ini tidak kita lanjutkan.</p>
<p>Alternatif kedua, sebetulnya sangat mudah. MacOS sebetulnya sudah bisa menulis ke partisi NTFS, tapi tidak di-enable, entah karena alasan teknis atau alasan bisnis. Jadi <em>by-default</em> pada waktu kita memasang external disk atau flashdisk berpartisi NTFS, MacOS hanya menyediakan akses <em>read only</em>. Kita cuma bisa buka file dan folder, tapi tidak bisa membuat file dan folder baru. Untuk itu, kita bisa melakukan <em>mount</em> ulang terhadap disk NTFS kita dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo umount /Volume/BackupEndy
sudo mount -t ntfs -o rw,auto,nobrowse /dev/disk3s1 /Volume/BackupEndy
</code></pre></div></div>
<p>Bila kita sering cabut-pasang disk yang sama, kita bisa mengotomasinya dengan cara menulis konfigurasi di file <code class="language-plaintext highlighter-rouge">/etc/fstab</code>. File ini bukanlah file yang aneh bagi yang biasa menggunakan Linux. Ini adalah file yang diedit untuk mendefinisikan partisi di harddisk kita, baik external maupun internal.</p>
<p>Kita harus tahu dulu label volume external harddisk. Misalnya harddisk saya namanya <code class="language-plaintext highlighter-rouge">BackupEndy</code>, maka saya tambahkan baris berikut di file <code class="language-plaintext highlighter-rouge">/etc/fstab</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LABEL=BackupEndy none ntfs rw,auto,nobrowse
</code></pre></div></div>
<p>Bila labelnya mengandung spasi, maka kita harus menggunakan <code class="language-plaintext highlighter-rouge">UUID</code>. Nilainya bisa didapatkan dengan menggunakan <code class="language-plaintext highlighter-rouge">diskutil</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>diskutil info /Volumes/Backup\ Endy | grep UUID
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Disk / Partition UUID: F542CBB9-590E-4ECB-8F27-BA79C8DA6C9E
</code></pre></div></div>
<p>Kita pasang nilai UUID tersebut di <code class="language-plaintext highlighter-rouge">/etc/fstab</code> seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UUID=F542CBB9-590E-4ECB-8F27-BA79C8DA6C9E none ntfs rw,auto,nobrowse
</code></pre></div></div>
<p>Setelah kita tambahkan konfigurasi tersebut, maka setiap kali harddisk/flashdisk tersebut kita pasang, otomatis akan di-mount secara read-write alias bisa ditulisi.</p>
<p>Tapi ini mungkin kurang <em>user-friendly</em> karena harus mengedit file sistem yang membutuhkan penggunaan akun superuser. Selain itu, kita harus tahu label partisi dan harus menambahkannya ke <code class="language-plaintext highlighter-rouge">/etc/fstab</code> setiap kali ada flashdisk/harddisk baru yang ingin kita pasang.</p>
<p>Alternatif terakhir adalah menggunakan aplikasi gratisan, yaitu <a href="https://mounty.app/">Mounty.app</a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/ntfs-macos/mounty-website.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/ntfs-macos/mounty-website.png" alt="Mounty App" /></a></p>
<p>Sebetulnya di belakang layar, aplikasi Mounty ini juga menggunakan cara yang sama dengan metode kedua kita barusan. Bedanya, dia menyediakan antarmuka visual yang lebih memudahkan pengguna awam.</p>
<p>Cara instalasinya mudah, bisa langsung unduh dan instal dari websitenya, atau instalasi menggunakan <code class="language-plaintext highlighter-rouge">Homebrew</code>. Perintahnya sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install --cask mounty
</code></pre></div></div>
<p>Setelah itu, kita bisa jalankan seperti biasa menggunakan Spotlight.</p>
<p>Demikianlah beberapa alternatif untuk mengakses partisi NTFS di MacOS. Semoga bermanfaat …</p>
Intermittent Moodle 502 Post Mortem2021-01-15T07:00:00+07:00https://software.endy.muhardin.com/devops/moodle-502-post-mortem<p>Beberapa hari terakhir, <a href="https://elearning.tazkia.ac.id">aplikasi e-learning Tazkia</a> kadang menampilkan error 502 Bad Gateway pada saat diakses. Berikut adalah diagnosa, analisa, dan tindakan yang dilakukan untuk mengatasi masalah tersebut.</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#error-yang-terjadi" id="markdown-toc-error-yang-terjadi">Error yang terjadi</a></li>
<li><a href="#skema-dan-topologi-deployment-e-learning" id="markdown-toc-skema-dan-topologi-deployment-e-learning">Skema dan Topologi Deployment E-Learning</a></li>
<li><a href="#error-log" id="markdown-toc-error-log">Error Log</a></li>
<li><a href="#hasil-googling" id="markdown-toc-hasil-googling">Hasil Googling</a></li>
<li><a href="#analisa" id="markdown-toc-analisa">Analisa</a></li>
<li><a href="#pilihan-dan-implementasi-solusi" id="markdown-toc-pilihan-dan-implementasi-solusi">Pilihan dan implementasi solusi</a></li>
</ul>
<h2 id="error-yang-terjadi">Error yang terjadi</h2>
<p>Beberapa user (lebih dari 5) melaporkan tampilnya halaman 502 Bad Gateway pada waktu mengakses aplikasi e-learning. Laporan user tidak menyebutkan detail lain seperti:</p>
<ul>
<li>perangkat yang digunakan untuk mengakses</li>
<li>jenis dan versi browser yang digunakan</li>
<li>koneksi internet yang dipakai</li>
<li>fitur aplikasi yang diakses</li>
</ul>
<p>Walaupun demikian, karena errornya terjadi di sisi server (kode error 5xx), maka harusnya error log akan ada di server, sehingga untuk diagnosa awal informasi tersebut belum perlu diminta.</p>
<h2 id="skema-dan-topologi-deployment-e-learning">Skema dan Topologi Deployment E-Learning</h2>
<p>Aplikasi e-learning Tazkia menggunakan Moodle, dideploy dengan skema seperti ini:</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/nginx-moodle-502/moodle-deployment.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/nginx-moodle-502/moodle-deployment.png" alt="Moodle Deployment" /></a></p>
<p>Untuk itu, diagnosa error akan dilakukan dengan cara:</p>
<ul>
<li>Melihat isi file <code class="language-plaintext highlighter-rouge">/var/log/nginx/error.log</code></li>
<li>Melihat isi file <code class="language-plaintext highlighter-rouge">/var/log/php7.4-fpm.log</code></li>
</ul>
<h2 id="error-log">Error Log</h2>
<p>Berikut adalah pesan error yang ditemukan di <code class="language-plaintext highlighter-rouge">/var/log/nginx/error.log</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2021/01/15 03:36:34 [error] 5451#5451: *2671271 connect() to unix:/run/php/php7.4-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream, client: 114.122.234.47, server: elearning.tazkia.ac.id, request: "GET /lib/ajax/setuserpref.php?sesskey=abcdabcd&pref=filepicker_recentrepository&value=6 HTTP/2.0", upstream: "fastcgi://unix:/run/php/php7.4-fpm.sock:", host: "elearning.tazkia.ac.id", referrer: "https://elearning.tazkia.ac.id/mod/quiz/attempt.php?attempt=123456&cmid=123456"
2021/01/15 03:36:34 [error] 5451#5451: *2671271 connect() to unix:/run/php/php7.4-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream, client: 114.122.234.47, server: elearning.tazkia.ac.id, request: "POST /repository/repository_ajax.php?action=list HTTP/2.0", upstream: "fastcgi://unix:/run/php/php7.4-fpm.sock:", host: "elearning.tazkia.ac.id", referrer: "https://elearning.tazkia.ac.id/mod/quiz/attempt.php?attempt=123456&cmid=123456"
2021/01/15 03:36:34 [error] 5451#5451: *2658592 connect() to unix:/run/php/php7.4-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream, client: 203.153.22.41, server: elearning.tazkia.ac.id, request: "GET /mod/quiz/attempt.php?attempt=123456&cmid=123456 HTTP/2.0", upstream: "fastcgi://unix:/run/php/php7.4-fpm.sock:", host: "elearning.tazkia.ac.id", referrer: "https://elearning.tazkia.ac.id/mod/quiz/view.php?id=123456"
</code></pre></div></div>
<p>Dan ini error log di <code class="language-plaintext highlighter-rouge">/var/log/php7.4-fpm.log</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[15-Jan-2021 03:04:39] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
[15-Jan-2021 03:05:21] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
[15-Jan-2021 03:18:28] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
[15-Jan-2021 03:33:04] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
[15-Jan-2021 03:33:32] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
</code></pre></div></div>
<h2 id="hasil-googling">Hasil Googling</h2>
<p>Error message yang didapat bisa langsung kita copas ke Google, dan segera muncul beberapa artikel yang menjelaskan error tersebut.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/nginx-moodle-502/nginx-502.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/nginx-moodle-502/nginx-502.png" alt="Nginx 502" /></a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/nginx-moodle-502/server-reach-pm-max.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/nginx-moodle-502/server-reach-pm-max.png" alt="Server Reach PM Max" /></a></p>
<h2 id="analisa">Analisa</h2>
<p>Beberapa artikel yang membahas masalah tersebut diantaranya:</p>
<ul>
<li><a href="https://ubiq.co/tech-blog/fix-502-bad-gateway-error-nginx/">Blog Ubiq</a></li>
<li><a href="https://community.webcore.cloud/tutorials/how_to_solve_php_fpm_server_reached_max_children/">Forum WebCore Cloud</a></li>
<li><a href="https://support.plesk.com/hc/en-us/articles/214528405--Websites-on-PHP-FPM-are-unavailable-or-loading-slowly-server-reached-max-children-setting-OR-pool-seems-busy-">Support Forum Plesk</a></li>
</ul>
<p>Pada artikel di atas, dijelaskan bahwa error 502 di Nginx disebabkan karena dia mencoba menghubungi PHP-7.4 FPM untuk memproses script Moodle, tapi tidak kunjung mendapatkan response sampai timeout.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2021/nginx-moodle-502/ubiq-cause-502.png"><img src="https://software.endy.muhardin.com/images/uploads/2021/nginx-moodle-502/ubiq-cause-502.png" alt="Ubiq 502" /></a></p>
<p>Beberapa kemungkinan penyebabnya adalah:</p>
<ul>
<li>ada script PHP yang membutuhkan waktu lama untuk dijalankan</li>
<li>ada query yang membutuhkan waktu lama untuk dijalankan</li>
<li>jumlah request yang datang melebihi worker yang tersedia dalam PHP-FPM pool, sehingga kelebihan request tersebut harus antri. Antrian tersebut tidak selesai dalam rentang waktu timeout Nginx, sehingga Nginx bosan menunggu (timeout) dan mengeluarkan error 502.</li>
</ul>
<p>Beberapa alternatif solusi yang bisa diterapkan:</p>
<ol>
<li>Menambah rentang waktu timeout di sisi Nginx, sehingga dia lebih sabar menunggu antrian atau eksekusi script/query</li>
<li>Menambah worker yang tersedia dalam PHP-FPM pool, sehingga bisa melayani lebih banyak request dan mengurangi antrian</li>
</ol>
<h2 id="pilihan-dan-implementasi-solusi">Pilihan dan implementasi solusi</h2>
<p>Melihat pesan error dalam <code class="language-plaintext highlighter-rouge">/var/log/php7.4-fpm.log</code>, dapat disimpulkan bahwa penyebab masalah yang utama adalah kurangnya worker yang melayani request. Sedangkan timeout yang terjadi merupakan akibat dari panjangnya antrian request.</p>
<p>Untuk itu, maka kita akan menambah worker dalam pool. Setelah ditambah, diharapkan antrian tidak akan terbentuk, atau ada dalam jumlah sedikit dan bisa dilayani sebelum mencapai batas timeout.</p>
<p>Implementasi solusi dilakukan dengan cara mengubah setting <code class="language-plaintext highlighter-rouge">pm.max_children</code> dalam file <code class="language-plaintext highlighter-rouge">/etc/php/7.4/fpm/pool.d/www.conf</code> menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pm.max_children = 15
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 5
</code></pre></div></div>
<p>Jumlah <code class="language-plaintext highlighter-rouge">max_children</code> yang sesuai bisa dihitung dengan mengikuti petunjuk <a href="https://www.kinamo.be/en/support/faq/determining-the-correct-number-of-child-processes-for-php-fpm-on-nginx">di artikel ini</a>.</p>
<p>Setelah itu, restart <code class="language-plaintext highlighter-rouge">php7.4-fpm</code> dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart php7.4-fpm
</code></pre></div></div>
<p>Selanjutnya, kita bisa memantau jumlah proses <code class="language-plaintext highlighter-rouge">php-fpm</code> dengan perintah <code class="language-plaintext highlighter-rouge">ps aux | grep php-fpm</code>. Harusnya akan terlihat 5 proses, karena <code class="language-plaintext highlighter-rouge">pm.start_servers</code> dan <code class="language-plaintext highlighter-rouge">pm.min_spare_servers</code> diset di angka <code class="language-plaintext highlighter-rouge">5</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@elearning:~# ps aux| grep php-fpm
root 21977 0.6 0.3 466004 30164 ? Ss 06:40 0:00 php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)
www-data 22007 5.6 0.7 472928 57468 ? R 06:40 0:00 php-fpm: pool www
www-data 22008 6.5 0.7 473200 57968 ? R 06:40 0:00 php-fpm: pool www
www-data 22021 4.2 0.6 470892 56608 ? S 06:40 0:00 php-fpm: pool www
www-data 22023 1.7 0.5 470780 46716 ? S 06:40 0:00 php-fpm: pool www
www-data 22024 1.6 0.5 470776 41048 ? S 06:40 0:00 php-fpm: pool www
www-data 22025 2.0 0.5 474876 48412 ? R 06:40 0:00 php-fpm: pool www
</code></pre></div></div>
<p>Saat ini perubahan tersebut sudah diimplementasikan dan akan dipantau pada saat load tinggi. Seharusnya sudah tidak terjadi lagi error 502. Bila masih terjadi, maka kita bisa meningkatkan lagi nilai <code class="language-plaintext highlighter-rouge">max_children</code> sesuai kebutuhan.</p>
<p>Semoga bermanfaat …</p>
VPN dengan Wireguard Bagian V : Menghubungkan Cloud dengan On Premise2021-01-03T07:00:00+07:00https://software.endy.muhardin.com/devops/vpn-wireguard-05-cloud-to-onpremise<p>Di awal 2021 ini, cloud services sudah sangat lazim digunakan orang di seluruh dunia, termasuk di Indonesia. Skema tagihannya yang bisa per jam dan kecepatan provisioningnya memungkinkan banyak usaha rintisan (start up company) untuk bisa memulai bisnisnya dengan cepat dan murah.</p>
<p>Yang tadinya harus menyediakan modal besar untuk membeli server, melakukan instalasi, membuat atau menyewa data center, menyediakan listrik, disaster recovery, dan pos investasi lain yang nilainya besar, sekarang tidak perlu lagi memusingkan hal tersebut. Cukup bikin akun, masukkan data kartu kredit, dan kita bisa langsung membuat virtual private server yang memiliki IP public dan siap diakses dari mana saja.</p>
<p>Akan tetapi, tidak semua orang bisa memanfaatkan cloud services. Beberapa jenis industri mengharuskan data transaksi disimpan di dalam negeri, sedangkan penyedia cloud services tidak banyak yang memiliki data center di Indonesia. Untuk itu, kita perlu solusi untuk menghubungkan VPS kita di cloud provider dengan database server kita di data center sendiri.</p>
<p>Salah satu contoh kegunaannya adalah apabila kita ingin menggunakan layanan serverless semacam AWS Lambda untuk mengakses database kita di data center on premise.</p>
<p>Skema tersebut diilustrasikan seperti pada gambar berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-cloud-to-onpremise.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-cloud-to-onpremise.jpg" alt="VPN Cloud - OnPremise" /></a></p>
<p>Langkah-langkah implementasinya sebagai berikut:</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#konfigurasi-vpn-gateway-di-cloud-service" id="markdown-toc-konfigurasi-vpn-gateway-di-cloud-service">Konfigurasi VPN Gateway di Cloud Service</a></li>
<li><a href="#konfigurasi-vpn-gateway-di-data-center-on-premise" id="markdown-toc-konfigurasi-vpn-gateway-di-data-center-on-premise">Konfigurasi VPN Gateway di Data Center On Premise</a></li>
<li><a href="#konfigurasi-routing" id="markdown-toc-konfigurasi-routing">Konfigurasi Routing</a></li>
<li><a href="#pengetesan" id="markdown-toc-pengetesan">Pengetesan</a></li>
<li><a href="#konfigurasi-networking-aws" id="markdown-toc-konfigurasi-networking-aws">Konfigurasi Networking AWS</a></li>
</ul>
<p>Untuk merealisasikan konfigurasi di atas, kita perlu membuat subnet internal di layanan cloud, atau biasa disebut dengan istilah Virtual Private Cloud, di mana layanan-layanan yang kita gunakan di cloud (seperti VPS, Function as a Service, dan lainnya) terhubung dalam satu subnet. Biasanya ini akan terlihat dengan adanya routing atau interface jaringan dengan alamat IP private yang terhubung ke subnet yang sama.</p>
<p>Konfigurasi VPC berbeda antar cloud provider, demikian juga pengaturan routingnya. Oleh karena itu kita tidak bahas di artikel ini. Silahkan baca dokumentasi di provider cloud yang Anda gunakan. Intinya, kita harus mengarahkan paket ke IP private database server ke VPS yang bertindak sebagai VPN gateway.</p>
<p>Dibandingkan konfigurasi skenario sebelumnya, skema cloud-on premise ini tergolong sederhana. Kita cukup menghubungkan VPN Gateway di sisi cloud provider dengan VPN gateway di sisi data center on premise.</p>
<h2 id="konfigurasi-vpn-gateway-di-cloud-service">Konfigurasi VPN Gateway di Cloud Service</h2>
<p>Di cloud services, kita perlu membuatkan VPN gateway. Di cloud DigitalOcean, VPN gateway ini kita buat berupa droplet biasa. Di Amazon AWS, kita bisa menggunakan layanan EC2 untuk dijadikan VPN gateway.</p>
<p>Di VPS ini, kita bisa menginstal WireGuard seperti biasa. Caranya bisa dibaca di <a href="/devops/vpn-wireguard-01-intro/">artikel sebelumnya</a></p>
<p>Kemudian, kita buat konfigurasi interface dan peer yang mengarah ke VPN gateway di sisi on-premise seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = sCVDCzfC39bffUYR6v5ZnjqQOfmn3otUyhCi8ndIk3I=
Address = 10.100.10.22/24
ListenPort = 51515
[Peer]
PublicKey = FQcUiIzvvvQ2hHplCsUgR+RN4avDWi/ucF57LTvq11k=
Endpoint = 180.244.234.226:51515
AllowedIPs = 10.100.10.11/32,192.168.0.0/24
</code></pre></div></div>
<p>Jangan lupa kita cantumkan alamat subnet database server kita, yaitu <code class="language-plaintext highlighter-rouge">192.168.0.0/24</code> di <code class="language-plaintext highlighter-rouge">AllowedIPs</code> agar WireGuard membuatkan routing untuk mengarahkan paket menuju database server kita di IP <code class="language-plaintext highlighter-rouge">192.168.0.10</code> melalui jaringan VPN di <code class="language-plaintext highlighter-rouge">10.100.10.0/24</code>.</p>
<p>Bila kita menggunakan AWS EC2, kita perlu menambahkan <code class="language-plaintext highlighter-rouge">PersistentKeepalive = 25</code> di blok <code class="language-plaintext highlighter-rouge">[Peer]</code> karena AWS meletakkan VPS kita di jaringan internal, di belakang firewall/load balancer. Oleh karena itu kita perlu <a href="/devops/vpn-wireguard-03-publish-laptop/">mengaktifkan hole punching</a>.</p>
<h2 id="konfigurasi-vpn-gateway-di-data-center-on-premise">Konfigurasi VPN Gateway di Data Center On Premise</h2>
<p>Di sisi on-premise, kita cukup membuat konfigurasi interface dan peer untuk menerima koneksi dari VPN gateway di cloud, seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
Address = 10.100.10.11/24
ListenPort = 51515
PrivateKey = mHDydqFCC7jcXgwn3TaMN718ekgaJmLOeQqgWxP5fUA=
[Peer]
PublicKey = bGalHOzArxIoTFDVz0fMcidw6k01Vlk3Zo5ancGjIlg=
Endpoint = 3.1.84.2:51515
AllowedIPs = 10.100.10.22/32
</code></pre></div></div>
<p>Kita tidak perlu mengisi alamat lain di <code class="language-plaintext highlighter-rouge">AllowedIPs</code> selain IP VPN Gateway di cloud.</p>
<h2 id="konfigurasi-routing">Konfigurasi Routing</h2>
<p>Kita bisa mengecek konfigurasi routing di VPN gateway cloud dengan perintah <code class="language-plaintext highlighter-rouge">ip route</code>. Hasilnya kurang lebih seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ip route
default via 172.17.0.1 dev eth0 proto dhcp src 172.17.0.22 metric 100
10.100.0.0/24 dev wg0 proto kernel scope link src 10.100.0.22
192.168.0.0/24 dev wg0 scope link
</code></pre></div></div>
<p>Yang perlu diperhatikan adalah route menuju subnet <code class="language-plaintext highlighter-rouge">192.168.0.0/24</code> di mana database on-premise berada sudah mengarah ke interface WireGuard, yaitu <code class="language-plaintext highlighter-rouge">wg0</code>.</p>
<p>Konfigurasi routing ini dibuatkan otomatis oleh WireGuard, asalkan kita mengkonfigurasi <code class="language-plaintext highlighter-rouge">AllowedIPs</code> dengan benar.</p>
<h2 id="pengetesan">Pengetesan</h2>
<p>Agar yakin 100% bahwa konfigurasi ini sudah oke, tentunya kita harus :</p>
<ul>
<li>menyiapkan database yang akan digunakan, di VPS dengan IP <code class="language-plaintext highlighter-rouge">192.168.0.2</code> dalam data center on-premise</li>
<li>bikin aplikasi atau function yang dideploy di AWS Lambda, yang mengakses database di IP <code class="language-plaintext highlighter-rouge">192.168.0.2</code></li>
<li>membuat routing VPC agar paket data dari AWS Lambda bisa diarahkan ke VPN gateway kita di cloud, untuk kemudian dikirim melalui VPN ke gateway di on-premise, dan kemudian diteruskan ke database server</li>
</ul>
<p>Tapi untuk gampangnya, kita bisa ping saja dari EC2 di AWS ke IP database</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ping 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=63 time=214 ms
64 bytes from 192.168.0.2: icmp_seq=3 ttl=63 time=233 ms
^C
--- 192.168.0.2 ping statistics ---
4 packets transmitted, 2 received, 50% packet loss, time 3011ms
rtt min/avg/max/mdev = 213.564/223.223/232.882/9.659 ms
</code></pre></div></div>
<p>Bila kita ingin mengetes koneksi database dari AWS Lambda ke database on-premise, kita bisa mengikuti tutorial <a href="https://www.linkedin.com/pulse/aws-lambda-mysql-rds-api-gateway-vaquar-khan-?articleId=6606968660907085824">Vaquar Khan</a>. Dari artikel tersebut, kita bisa copy-paste dan menyesuaikan kode programnya menjadi seperti ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">mysql</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">mysql</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">con</span> <span class="o">=</span> <span class="nx">mysql</span><span class="p">.</span><span class="nx">createConnection</span><span class="p">({</span>
<span class="na">host</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_HOST</span><span class="p">,</span>
<span class="na">port</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_PORT</span><span class="p">,</span>
<span class="na">database</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_NAME</span><span class="p">,</span>
<span class="na">user</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_USERNAME</span><span class="p">,</span>
<span class="na">password</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_PASSWORD</span><span class="p">,</span>
<span class="na">connectionLimit</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
<span class="na">multipleStatements</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span><span class="c1">// Prevent nested sql statements</span>
<span class="na">connectionLimit</span><span class="p">:</span> <span class="mi">1000</span><span class="p">,</span>
<span class="na">connectTimeout</span><span class="p">:</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span>
<span class="na">acquireTimeout</span><span class="p">:</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span>
<span class="na">timeout</span><span class="p">:</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span>
<span class="na">debug</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">});</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">context</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Query employee dengan ID : </span><span class="dl">'</span><span class="o">+</span><span class="nx">event</span><span class="p">.</span><span class="nx">emp_id</span><span class="p">);</span>
<span class="c1">// allows for using callbacks as finish/error-handlers</span>
<span class="nx">context</span><span class="p">.</span><span class="nx">callbackWaitsForEmptyEventLoop</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">sql</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">select * from employee where emp_id = </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">event</span><span class="p">.</span><span class="nx">emp_id</span><span class="p">;</span>
<span class="nx">con</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="nx">sql</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">result</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">err</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="nx">callback</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">result</span><span class="p">)</span>
<span class="p">});</span>
<span class="p">};</span>
</code></pre></div></div>
<p>Kode program tersebut kita simpan dengan nama <code class="language-plaintext highlighter-rouge">index.js</code></p>
<p>Kita harus menyediakan dependensi yang dibutuhkan oleh function kita di atas, untuk itu kita buat struktur project standar <code class="language-plaintext highlighter-rouge">npm</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm init
npm install --save mysql
</code></pre></div></div>
<p>Perintah di atas akan menghasilkan file <code class="language-plaintext highlighter-rouge">package.json</code></p>
<p>Setelah kita jalankan perintah di atas, maka folder kita akan berisi sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -l
-rw-r--r-- 1 855 Jan 3 23:07 index.js
drwxr-xr-x 14 448 Jan 3 22:54 node_modules
-rw-r--r-- 1 316 Jan 3 23:04 package.json
</code></pre></div></div>
<p>Kita zip semuanya menjadi satu untuk diupload ke AWS Lambda</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zip lambda-db-onprem.zip -r * .[^.]* -x ".git/*"
</code></pre></div></div>
<p>Jangan lupa untuk menyediakan environment variable untuk mengisi konfigurasi di atas, yaitu:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">DB_HOST</code> : <code class="language-plaintext highlighter-rouge">192.168.0.2</code></li>
<li><code class="language-plaintext highlighter-rouge">DB_PORT</code> : <code class="language-plaintext highlighter-rouge">3306</code></li>
<li><code class="language-plaintext highlighter-rouge">DB_NAME</code> : <code class="language-plaintext highlighter-rouge">belajarvpndb</code></li>
<li><code class="language-plaintext highlighter-rouge">DB_USERNAME</code> : <code class="language-plaintext highlighter-rouge">belajarvpn</code></li>
<li><code class="language-plaintext highlighter-rouge">DB_PASSWORD</code> : <code class="language-plaintext highlighter-rouge">belajar1234</code></li>
</ul>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-env-var.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-env-var.png" alt="AWS Lambda Environment Variable" /></a></p>
<p>Setelah function dibuat, environment variable diisi, dan file zip berisi source code diupload, kita bisa membuat test event dengan isi seperti ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="dl">"</span><span class="s2">emp_id</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span>
<span class="p">}</span>
</code></pre></div></div>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-test.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-test.png" alt="AWS Lambda Test" /></a></p>
<p>Kemudian kita test functionnya. Kalau routing VPN kita belum berjalan dengan baik, maka akan muncul error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Response:
{
"errorMessage": "2021-01-03T16:14:07.262Z c3ddf9d6-ac02-45a6-9275-b841542b50a3 Task timed out after 3.00 seconds"
}
</code></pre></div></div>
<p>Kita perlu membuatkan user dan databasenya di MySQL server kita</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE DATABASE belajarvpndb;
CREATE USER 'belajarvpn'@'%' IDENTIFIED BY 'belajar1234';
GRANT ALL ON belajarvpndb.* TO 'belajarvpn'@'%';
flush privileges;
</code></pre></div></div>
<p>Skema tabelnya sebagai berikut, kita siapkan dulu tabel dan contoh data di database</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`employee`</span> <span class="p">(</span>
<span class="nv">`emp_id`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="nv">`emp_name`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="nv">`emp_id`</span><span class="p">)</span>
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span> <span class="k">DEFAULT</span> <span class="n">CHARSET</span><span class="o">=</span><span class="n">latin1</span><span class="p">;</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">employee</span> <span class="k">values</span><span class="p">(</span><span class="s1">'1'</span><span class="p">,</span><span class="s1">'Vaquar khan'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">employee</span> <span class="k">values</span><span class="p">(</span><span class="s1">'2'</span><span class="p">,</span><span class="s1">'Zidan khan'</span><span class="p">);</span>
</code></pre></div></div>
<p>Bila konfigurasi routing sudah benar, maka function kita di atas akan menghasilkan log output seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Response:
[
{
"emp_id": "1",
"emp_name": "Vaquar khan"
}
]
Request ID:
"597b4c33-338b-4d10-a0cd-38d41a363ac9"
</code></pre></div></div>
<h2 id="konfigurasi-networking-aws">Konfigurasi Networking AWS</h2>
<p>Agar function kita di AWS Lambda bisa menggunakan VPN Gateway yang kita buat dengan EC2, maka kita harus membuat function di VPC yang sama dengan VPC tempat EC2 berada. Konfigurasinya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-vpc.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-vpc.png" alt="AWS Lambda VPC" /></a>.</p>
<p>Kemudian, kita juga harus menambahkan routing di VPC agar paket menuju subnet <code class="language-plaintext highlighter-rouge">192.168.0.0/24</code> diarahkan melalui VPN gateway kita berupa EC2 instance.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-vpc-route.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-vpc-route.png" alt="AWS VPC Route" /></a></p>
<p>Terakhir, kita harus mematikan <code class="language-plaintext highlighter-rouge">Source/Destination Check</code> di EC2 instance.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-ec2-srcdstcheck.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-ec2-srcdstcheck.png" alt="AWS EC2 Src/Dest Check" /></a></p>
<p>Centang checkbox Stop</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-disable-srcdstcheck.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-disable-srcdstcheck.png" alt="AWS EC2 Stop Src/Dest Check" /></a></p>
<p>Secara default, EC2 hanya akan memproses paket data yang berasal atau menuju dirinya. Dengan demikian, paket dari Lambda ke DB on-premise yang sekedar numpang lewat (sourcenya lambda dan destinationnya database <code class="language-plaintext highlighter-rouge">192.168.0.2</code>) tidak akan diteruskan oleh EC2. Dengan mematikan pengecekan, EC2 akan memproses semua data yang numpang lewat tersebut.</p>
<p>Setelah semua routing dikonfigurasi, kita bisa mencoba menjalankan function kita tersebut. Function akan bisa mengakses database kita di on-premise dengan lancar.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-result-1.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-result-1.png" alt="AWS Lambda Result 1" /></a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-result-2.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-result-2.png" alt="AWS Lambda Result 2" /></a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-result-3.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/aws-lambda-result-3.png" alt="AWS Lambda Result 3" /></a></p>
<p>Selamat mencoba. Semoga bermanfaat …</p>
VPN dengan Wireguard Bagian IV : Road Warrior2021-01-01T07:00:00+07:00https://software.endy.muhardin.com/devops/vpn-wireguard-04-roadwarrior<p>Penggunaan VPN yang kita bahas pada artikel kali ini biasa disebut dengan <em>skenario Road Warrior</em> di berbagai artikel di internet. Definisinya bisa dilihat sendiri <a href="https://en.wikipedia.org/wiki/Road_warrior_(computing)">di wikipedia</a></p>
<p>Pada intinya, <em>Road Warrior</em> adalah pekerja yang bekerja di luar kantor (banyak tugas luar misalnya) dan butuh akses terhadap perangkat di dalam jaringan kantor, seperti database server, printer, aplikasi internal, shared file/folder, dan sebagainya.</p>
<p>Di awal tahun 2021 ini, banyak kebutuhan di atas (share file, printer, aplikasi) yang sudah tidak relevan lagi di era komputasi awan sekarang ini. File sharing bisa dilakukan dengan mudah dengan layanan Dropbox, Google Drive, dan lainnya. Bahkan kita bisa berkirim file melalui chat. Printer juga bisa dihubungkan dengan layanan Google Cloud Print, sehingga kita bisa menggunakan printer kita dari mana saja. Aplikasi bisnis kebanyakan sekarang sudah berbasis web dan dihosting di internet, sehingga bisa diakses dari mana saja. Walaupun demikian, kadangkala kita tetap butuh, sehingga saya tuliskan sekalian di sini cara konfigurasinya.
<!--more--></p>
<ul id="markdown-toc">
<li><a href="#berbagai-varian-topologi-road-warrior" id="markdown-toc-berbagai-varian-topologi-road-warrior">Berbagai varian topologi Road Warrior</a></li>
<li><a href="#konfigurasi-vpn-gateway-dengan-ip-public" id="markdown-toc-konfigurasi-vpn-gateway-dengan-ip-public">Konfigurasi VPN Gateway dengan IP Public</a></li>
<li><a href="#instalasi-wireguard-di-raspberry-pi" id="markdown-toc-instalasi-wireguard-di-raspberry-pi">Instalasi WireGuard di Raspberry PI</a></li>
<li><a href="#konfigurasi-vpn-gateway-di-subnet-internal" id="markdown-toc-konfigurasi-vpn-gateway-di-subnet-internal">Konfigurasi VPN Gateway di Subnet Internal</a></li>
<li><a href="#membuat-konfigurasi-remote-worker" id="markdown-toc-membuat-konfigurasi-remote-worker">Membuat konfigurasi remote worker</a></li>
<li><a href="#pengetesan" id="markdown-toc-pengetesan">Pengetesan</a></li>
</ul>
<h2 id="berbagai-varian-topologi-road-warrior">Berbagai varian topologi Road Warrior</h2>
<p>Sebetulnya konfigurasi VPN road warrior ini sangat bervariasi, tergantung topologi jaringan kantor yang bersangkutan. Akan tetapi pada umumnya, ada tiga jenis topologi yang sering digunakan.</p>
<p>Pertama, kantor yang mengelola sendiri internet connection sharingnya. Biasanya kantor seperti ini berlangganan internet broadband, mendapatkan IP public yang static, dan memiliki internet router yang disetting dan dikonfigurasi sendiri sehingga lebih bebas dalam mengatur jaringannya. Salah satu merek yang sering digunakan di sini antara lain adalah Mikrotik. Atau banyak juga yang memilih solusi DIY (do it yourself) dan merakit router dengan distro *nix khusus router seperti IPFire, OpenWrt, pfSense, dan sebagainya. Skema road warrior untuk kantor seperti ini digambarkan sebagai berikut.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-roadwarrior.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-roadwarrior.jpg" alt="VPN Road Warrior" /></a></p>
<p>Di belakang router internet, biasanya cuma ada satu LAN yang berisi beberapa komputer, printer, scanner, dan perangkat lainnya yang terhubung ke satu subnet. Dengan topologi ini, kita cukup memasang VPN gateway di router internet. Para pekerja remote kemudian langsung connect ke VPN yang ada di router internet tersebut dengan menggunakan IP public si router.</p>
<p>Skema kedua adalah kantor yang lebih kompleks. Biasanya kantor seperti ini memiliki beberapa server yang digunakan di internal, seperti database server, DNS server/proxy, server aplikasi internal, dan sejenisnya. Server ini diakses oleh user yang berada di dalam kantor. Server-server ini biasanya ditaruh di subnet tersendiri yang disebut dengan istilah <a href="https://en.wikipedia.org/wiki/DMZ_(computing)">DMZ (Demiliterized Zone)</a>. Stafnya juga bisa terdiri dari banyak divisi, dan masing-masingnya tergabung dalam subnet sendiri. Skema seperti ini pernah saya warisi dari senior yang resign pada waktu bekerja di BaliCamp pada periode 2005-2008.</p>
<p>Untuk kasus ini, kita perlu membuat VPN host lagi di jaringan dalam seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-roadwarrior-double-dmz.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-roadwarrior-double-dmz.jpg" alt="VPN Road Warrior DMZ" /></a></p>
<p>Road warrior terhubung ke VPN gateway di router luar yang punya public IP, dan di jaringan dalam ada VPN gateway lagi yang juga terhubung ke VPN gateway di router luar. Setelah ketiga perangkat (road warrior, VPN luar, VPN dalam) terhubung, road warrior bisa mengakses perangkat di jaringan dalam.</p>
<p>Ketiga, adalah topologi rumah atau kantor kecil yang berlangganan paket internet retail (bukan korporat). Paket internet retail ini biasanya tidak diberikan IP public yang static. IP public routernya (kalaupun ada) berganti-ganti setiap kali reconnect. Oleh karena itu, kita tidak bisa memasang VPN gateway di routernya. Lagipula biasanya pengguna rumahan hanya diberikan router low end yang tidak memiliki kemampuan VPN. Topologi VPN untuk skenario ketiga ini bisa digambarkan sebagai berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-roadwarrior-soho.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-roadwarrior-soho.jpg" alt="VPN Road Warrior SOHO" /></a></p>
<p>Karena kita tidak bisa menjadikan routernya sebagai VPN gateway, maka kita butuh satu mesin di internet yang berfungsi sebagai gateway. Kita bisa menyewa VPS murah meriah dari berbagai provider di internet seperti AWS Lightsail, Digital Ocean Droplet, Scaleway, Linode, dan sebagainya. Harganya berkisar $2 - $10 per bulan.</p>
<p>Kita akan bahas skenario yang ketiga ini. Skenario kedua sebetulnya mirip-mirip, cuma beda lokasi VPN gateway external saja. Yang di skenario kedua ada di router sendiri, sedangkan di skenario ketiga ada di datacenter cloud service provider. Walaupun demikian, konfigurasinya sama persis</p>
<h2 id="konfigurasi-vpn-gateway-dengan-ip-public">Konfigurasi VPN Gateway dengan IP Public</h2>
<p>Pertama, kita akan siapkan dulu konfigurasi VPN gateway di internet. Konfigurasinya sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
Address = 10.100.10.11/24
ListenPort = 51515
PrivateKey = YDN0Mxg5cLgGcfmviPPFUBbv0fYD/by4/Vld4vH/9UI=
[Peer] # VPN Gateway di Internal Subnet
PublicKey = bGalHOzArxIoTFDVz0fMcidw6k01Vlk3Zo5ancGjIlg=
AllowedIPs = 10.100.10.33/32, 192.168.0.0/24
[Peer] # Remote Worker
PublicKey = Qzq14DnLcF6IX6BCT5/dRckR9LUEAXk4LrWMR/6k0i0=
AllowedIPs = 10.100.10.22/32
</code></pre></div></div>
<p>Blok <code class="language-plaintext highlighter-rouge">[Interface]</code> sama seperti artikel terdahulu, cukup pasang private key, alamat IP dalam VPN, dan port untuk menerima koneksi.</p>
<p>Konfigurasi peer terdiri dari satu peer di jaringan internal kantor, dan satu peer untuk masing-masing remote worker. Bila remote worker/road warrior ada 10 orang, maka kita harus siapkan 10 blok <code class="language-plaintext highlighter-rouge">[Peer]</code> juga.</p>
<p>Yang penting untuk diperhatikan adalah konfigurasi <code class="language-plaintext highlighter-rouge">AllowedIPs</code> di <code class="language-plaintext highlighter-rouge">[Peer]</code> VPN internal. Di sini kita harus menambahkan subnet jaringan internal kantor, yaitu <code class="language-plaintext highlighter-rouge">192.168.0.0/24</code> agar Wireguard bisa mengarahkan paket data yang menuju printer di kantor (IP: <code class="language-plaintext highlighter-rouge">192.168.0.10</code>) ke <code class="language-plaintext highlighter-rouge">Peer</code> yang sesuai.</p>
<p>Selanjutnya, kita bahas dulu konfigurasi VPN gateway di internal kantor.</p>
<h2 id="instalasi-wireguard-di-raspberry-pi">Instalasi WireGuard di Raspberry PI</h2>
<p>Kita membutuhkan mesin Linux yang bisa kita instalkan Wireguard. Salah satu solusi murah meriah praktis adalah Raspberry Pi. Kita bisa mendapatkannya di toko online dengan kisaran harga beberapa ratus ribu rupiah saja. Jauh lebih efisien daripada harus menyediakan PC khusus. Ukurannya juga sangat kecil, sehingga tidak boros tempat. Bisa ditenagai dengan charger handphone, hemat energi.</p>
<p>Pada saat artikel ini ditulis, distro Debian untuk Raspberry, yaitu Raspbian, belum memiliki paket instalasi Wireguard. Oleh karena itu kita harus compile sendiri. Berikut rangkaian perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo -i
apt install raspberrypi-kernel-headers libelf-dev libmnl-dev build-essential git
git clone https://git.zx2c4.com/wireguard-linux-compat
git clone https://git.zx2c4.com/wireguard-tools
make -C wireguard-linux-compat/src -j$(nproc)
make -C wireguard-linux-compat/src install
make -C wireguard-tools/src -j$(nproc)
make -C wireguard-tools/src install
perl -pi -e 's/#{1,}?net.ipv4.ip_forward ?= ?(0|1)/net.ipv4.ip_forward = 1/g' /etc/sysctl.conf
reboot
</code></pre></div></div>
<p>Tes instalasi dengan mengetikkan perintah <code class="language-plaintext highlighter-rouge">wg --version</code> dan <code class="language-plaintext highlighter-rouge">wg-quick</code>. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ wg --version
wireguard-tools v1.0.20200827-8-g66ed611 - https://git.zx2c4.com/wireguard-tools/
$ wg-quick
Usage: wg-quick [ up | down | save | strip ] [ CONFIG_FILE | INTERFACE ]
</code></pre></div></div>
<h2 id="konfigurasi-vpn-gateway-di-subnet-internal">Konfigurasi VPN Gateway di Subnet Internal</h2>
<p>Konfigurasi untuk VPN gateway di internal kantor/rumah seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = qAAqQgz1+Y+CbALmMSvr0QEhbrze5zcHiTw8LJRTZnM=
Address = 10.100.10.33/24
[Peer]
PublicKey = FQcUiIzvvvQ2hHplCsUgR+RN4avDWi/ucF57LTvq11k=
AllowedIPs = 10.100.10.0/24
Endpoint = 180.244.234.226:51515
PersistentKeepalive = 10
</code></pre></div></div>
<p>Kita daftarkan VPN gateway di internet tadi sebagai <code class="language-plaintext highlighter-rouge">Peer</code> di sini. <code class="language-plaintext highlighter-rouge">AllowedIPs</code> kita set ke subnet VPN, sehingga semua <code class="language-plaintext highlighter-rouge">Peer</code> dengan alamat IP VPN (<code class="language-plaintext highlighter-rouge">10.100.10.xxx</code>) bisa terlihat oleh Raspi ini. <code class="language-plaintext highlighter-rouge">Endpoint</code> kita arahkan ke VPN gateway kita di cloud service. Kemudian kita tambahkan <code class="language-plaintext highlighter-rouge">PersistentKeepalive</code> agar VPN internal ini mengirim paket setiap 10 detik ke VPN external di cloud service untuk menjaga agar koneksi tidak dihapus oleh router, seperti sudah kita bahas di bagian mengenai <em>Hole Punching</em> pada <a href="/devops/vpn-wireguard-03-publish-laptop/">artikel sebelumnya</a></p>
<h2 id="membuat-konfigurasi-remote-worker">Membuat konfigurasi remote worker</h2>
<p>Terakhir, kita buatkan konfigurasi untuk masing-masing remote worker/road warrior. Bentuknya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = YJBfmlovRqQgDjj9zZ46+gMZaWR62QFLNkFOOhQRIEc=
Address = 10.100.10.22/24
[Peer]
PublicKey = FQcUiIzvvvQ2hHplCsUgR+RN4avDWi/ucF57LTvq11k=
AllowedIPs = 10.100.10.0/24, 192.168.0.0/24
Endpoint = 180.244.234.226:51515
</code></pre></div></div>
<p>Kita bisa copy paste ini sebanyak jumlah remote worker, kemudian bagikan dengan QR code atau file teks seperti di <a href="/devops/vpn-wireguard-02-internet-proxy/">artikel terdahulu</a>. Cukup ganti <code class="language-plaintext highlighter-rouge">PrivateKey</code> dan <code class="language-plaintext highlighter-rouge">Address</code> untuk masing-masing worker.</p>
<p>Yang penting diperhatikan di sini adalah konfigurasi <code class="language-plaintext highlighter-rouge">AllowedIPs</code> di <code class="language-plaintext highlighter-rouge">Peer</code>. Pastikan bahwa subnet jaringan internal kantor (<code class="language-plaintext highlighter-rouge">192.168.0.0/24</code>) sudah tertulis di sana.</p>
<p>Kita tidak mencantumkan <code class="language-plaintext highlighter-rouge">0.0.0.0/0</code> di situ karena kita ingin koneksi internet kita tetap menggunakan koneksi yang sekarang sedang kita gunakan (wifi rumah, tethering hp, dan sebagainya). Lagipula, VPN gateway kita baik di cloud maupun di kantor tidak kita konfigurasi untuk menjadi internet gateway. Bila ingin kita jadikan internet gateway, barulah kita arahkan semua paket (<code class="language-plaintext highlighter-rouge">0.0.0.0/0</code>) ke <code class="language-plaintext highlighter-rouge">Peer</code> tersebut. Jangan lupa tambahkan aturan <code class="language-plaintext highlighter-rouge">iptables</code> untuk internet proxy seperti kita bahas di <a href="/devops/vpn-wireguard-02-internet-proxy/">artikel sebelumnya</a>.</p>
<h2 id="pengetesan">Pengetesan</h2>
<p>Mengetesnya gampang saja. Kita bisa ping ke salah satu komputer di jaringan internal, misalnya Raspberry yang menjadi VPN gateway internal tadi, menggunakan IP internalnya yaitu <code class="language-plaintext highlighter-rouge">192.168.0.100</code>. Atau kalau kita punya printer yang terhubung ke jaringan seperti di gambar, kita bisa langsung tes print ke sana. Seharusnya kita bisa print seolah-olah kita sedang berada di kantor.</p>
<p>Demikian konfigurasi VPN untuk Road Warrior. Semoga bermanfaat …</p>
VPN dengan Wireguard Bagian III : Expose Aplikasi di Laptop2020-12-30T07:00:00+07:00https://software.endy.muhardin.com/devops/vpn-wireguard-03-publish-laptop<p>Penggunaan VPN kali ini sangat bermanfaat bagi para programmer. Seringkali kita membuat aplikasi yang tentunya kita buat di laptop kita sendiri. Setelah kode program kita tulis, kita biasa jalankan aplikasinya di laptop kita dengan alamat <code class="language-plaintext highlighter-rouge">http://localhost</code> dan kita akses dari browser di laptop kita sendiri.</p>
<p>Akan tetapi, ada kalanya kita ingin mempresentasikan aplikasi kita tersebut untuk diakses oleh orang lain melalui internet. Atau kita ingin menyuruh orang lain mengakses aplikasi kita dari smartphone atau gadget lain yang menggunakan paket data. Karena browser/aplikasi yang mengakses aplikasi kita berjalan di perangkat yang berbeda, bahkan di jaringan yang berbeda juga (wifi rumah vs wifi kantor, wifi rumah vs paket data). Oleh karena itu kita tidak bisa menggunakan alamat <code class="language-plaintext highlighter-rouge">http://localhost</code> untuk mengaksesnya.</p>
<p>Solusinya adalah menghubungkan laptop kita ke VPN gateway yang memiliki IP public, kemudian membuat konfigurasi routing dan forwarding untuk meneruskan request ke port tertentu di gateway ke komputer kita di rumah.</p>
<p>Skema tersebut diilustrasikan seperti pada gambar berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-expose-laptop-app.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-expose-laptop-app.jpg" alt="VPN Expose Laptop" /></a></p>
<p>Langkah-langkah implementasinya sebagai berikut:</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#konfigurasi-vpn-gateway-di-cloud-service" id="markdown-toc-konfigurasi-vpn-gateway-di-cloud-service">Konfigurasi VPN Gateway di Cloud Service</a></li>
<li><a href="#konfigurasi-vpn-di-laptop" id="markdown-toc-konfigurasi-vpn-di-laptop">Konfigurasi VPN di Laptop</a> <ul>
<li><a href="#nat-hole-punching" id="markdown-toc-nat-hole-punching">NAT Hole Punching</a></li>
</ul>
</li>
<li><a href="#pengetesan" id="markdown-toc-pengetesan">Pengetesan</a></li>
<li><a href="#bonus--ssl-termination" id="markdown-toc-bonus--ssl-termination">Bonus : SSL Termination</a></li>
</ul>
<h2 id="konfigurasi-vpn-gateway-di-cloud-service">Konfigurasi VPN Gateway di Cloud Service</h2>
<p>Pertama, kita butuh VPN gateway yang memiliki IP public. Kita bisa membuatnya dengan menggunakan VPS yang disediakan layanan cloud. Langkah instalasi dan konfigurasinya mirip dengan yang sudah kita bahas di artikel sebelumnya. Bedanya hanya di tambahan konfigurasi port forwardingnya.</p>
<p>Sebagai contoh, kita akan membuka port 8000 di VPN gateway dan meneruskan requestnya ke laptop kita. Jadi kalau VPN gatewaynya memiliki alamat IP publik <code class="language-plaintext highlighter-rouge">139.59.112.17</code>, maka aplikasi kita di laptop bisa diakses dengan alamat <code class="language-plaintext highlighter-rouge">http://139.59.112.17:8000</code>.</p>
<p>Konsep dan penjelasan tentang port forwarding ini bisa dibaca di artikel terdahulu tentang <a href="/linux/network-address-translation/">Network Address Translation</a></p>
<p>Intinya adalah kita menambahkan rule di <code class="language-plaintext highlighter-rouge">iptables</code> untuk</p>
<ul>
<li>
<p>mengubah tujuan paket (destination NAT - DNAT) yang tadinya ke IP public gateway menjadi ke IP private laptop kita sebelum gateway melakukan routing. Yang diubah kita batasi hanya yang mengarah ke port <code class="language-plaintext highlighter-rouge">8000</code> saja. Perintahnya sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> iptables -t nat -A PREROUTING -d 139.59.112.17 -p tcp --dport 8000 -j DNAT --to-destination 10.100.100.22:8000
</code></pre></div> </div>
</li>
<li>
<p>setelah paket dirouting, sebelum dikirim ke laptop, alamat pengirim diubah (source NAT - SNAT) dari IP public perangkat yang mengirim menjadi alamat IP private gateway. Supaya laptop bisa mengirim response balik ke gateway. Perintahnya sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> iptables -t nat -A POSTROUTING -p tcp --dport 8000 -j SNAT --to-source 10.100.100.11
</code></pre></div> </div>
</li>
</ul>
<p>Konfigurasi WireGuard untuk VPN gateway secara lengkapnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
Address = 10.100.100.11/24
ListenPort = 51515
PrivateKey = mHDydqFCC7jcXgwn3TaMN718ekgaJmLOeQqgWxP5fUA=
PostUp = iptables -t nat -A PREROUTING -d 139.59.112.17 -p tcp --dport 8000 -j DNAT --to-destination 10.100.100.22:8000; iptables -t nat -A POSTROUTING -p tcp --dport 8000 -j SNAT --to-source 10.100.100.11
PostDown = iptables -t nat -D PREROUTING -d 139.59.112.17 -p tcp --dport 8000 -j DNAT --to-destination 10.100.100.22:8000; iptables -t nat -D POSTROUTING -p tcp --dport 8000 -j SNAT --to-source 10.100.100.11
</code></pre></div></div>
<p>Kemudian kita tambahkan setting <code class="language-plaintext highlighter-rouge">Peer</code> untuk laptop yang ingin dipublish port <code class="language-plaintext highlighter-rouge">8000</code> nya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Peer]
PublicKey = YQJ8wfZ++ebVPlJas9iNmgk5zxyuA1Cav9Exo4CvSQQ=
AllowedIPs = 10.100.100.22/32
</code></pre></div></div>
<h2 id="konfigurasi-vpn-di-laptop">Konfigurasi VPN di Laptop</h2>
<p>Di sisi laptop, konfigurasinya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = YJBfmlovRqQgDjj9zZ46+gMZaWR62QFLNkFOOhQRIEc=
Address = 10.100.100.22/24
[Peer]
PublicKey = FQcUiIzvvvQ2hHplCsUgR+RN4avDWi/ucF57LTvq11k=
AllowedIPs = 10.100.100.11/32
Endpoint = 139.59.112.17:51515
PersistentKeepalive = 25
</code></pre></div></div>
<h3 id="nat-hole-punching">NAT Hole Punching</h3>
<p>Ada dua hal yang berbeda dengan konfigurasi internet gateway pada artikel sebelumnya, yaitu:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">AllowedIPs</code> hanya diisi IP VPN di sisi gateway. Artinya koneksi internet tetap diarahkan melalui router wifi kita di rumah. Hanya paket yang menuju VPN gateway yang dikirim melalui interface WireGuard</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">PersistentKeepalive</code> adalah konfigurasi agar WireGuard di laptop mengirim data setiap 25 detik ke VPN gateway. Karena posisi laptop kita berada di balik router wifi rumah (istilahnya : <em>behind NAT proxy</em>), maka jalur datanya akan tertutup bila dia nganggur selama periode waktu tertentu. Kalau sudah tertutup, VPN gateway tidak bisa mengirim request lagi ke laptop kita. Dengan konfigurasi <code class="language-plaintext highlighter-rouge">PersistentKeepalive</code> ini, laptop kita akan mengirim data secara periodik untuk menjaga agar jalurnya tetap terbuka, sehingga request dari luar bisa tetap masuk menembus NAT router wifi kita.</p>
</li>
</ul>
<p>Teknik untuk menjaga jalur data tetap terbuka di belakang NAT ini disebut dengan istilah <a href="https://en.wikipedia.org/wiki/Hole_punching_(networking)">NAT Hole Punching</a>. Ada banyak teknik yang bisa digunakan, salah satunya adalah mengirim paket secara periodik, supaya jalurnya tidak ditutup oleh router.</p>
<h2 id="pengetesan">Pengetesan</h2>
<p>Untuk mengetesnya, kita bisa menjalankan aplikasi web dengan sebaris perintah. Silahkan pilih metode yang disukai dari <a href="https://gist.github.com/willurd/5720255">gist ini</a></p>
<p>Kita sediakan dulu satu file html sederhana seperti ini</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><html></span>
<span class="nt"><head><title></span>Belajar Wireguard<span class="nt"></title></head></span>
<span class="nt"><body><h1></span>Belajar Wireguard<span class="nt"></h1></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>Save dengan nama <code class="language-plaintext highlighter-rouge">index.html</code></p>
<p>Kemudian, jalankan webserver di folder yang berisi file <code class="language-plaintext highlighter-rouge">index.html</code> tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python -m SimpleHTTPServer 8000
Serving HTTP on 0.0.0.0 port 8000
</code></pre></div></div>
<p>Kita test dulu di laptop sendiri dengan cara browse ke <a href="http://localhost:8000">http://localhost:8000</a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/laptop-localhost.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/laptop-localhost.png" alt="Laptop Localhost" /></a></p>
<p>Setelah itu kita nyalakan WireGuard, kemudian coba akses melalui IP public VPN gateway kita</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/laptop-ip-public.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/laptop-ip-public.png" alt="VPN Expose Laptop" /></a></p>
<h2 id="bonus--ssl-termination">Bonus : SSL Termination</h2>
<p>Selain menggunakan port forwarding dengan <code class="language-plaintext highlighter-rouge">iptables</code>, sebetulnya kita juga bisa menggunakan <code class="language-plaintext highlighter-rouge">Nginx</code> sebagai reverse proxy, dan memasang sertifikat SSL di situ. Cara setupnya bisa dibaca di artikel terdahulu tentang <a href="/devops/deployment-microservice-kere-hore-2/">Deployment Aplikasi Kere Hore Bagian 2</a></p>
<p>Konfigurasi Nginx-nya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
server_name aplikasi.artivisi.id;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/aplikasi.artivisi.id/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/aplikasi.artivisi.id/privkey.pem;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/aplikasi.artivisi.id/html;
index index.php index.html;
location / {
proxy_pass http://10.100.100.22:8000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
}
}
server {
if ($host = aplikasi.artivisi.id) {
return 301 https://$host$request_uri;
}
listen 80;
listen [::]:80;
server_name aplikasi.artivisi.id;
return 404;
}
</code></pre></div></div>
<p>Bila kita sudah menggunaakn reverse proxy dengan <code class="language-plaintext highlighter-rouge">Nginx</code>, maka tidak perlu lagi memasang <code class="language-plaintext highlighter-rouge">DNAT</code> dan <code class="language-plaintext highlighter-rouge">SNAT</code> dengan <code class="language-plaintext highlighter-rouge">iptables</code>.</p>
<p>Demikianlah cara setup VPN agar aplikasi di laptop kita bisa diakses dari internet. Semoga bermanfaat …</p>
VPN dengan Wireguard Bagian II : Internet Proxy2020-12-28T07:00:00+07:00https://software.endy.muhardin.com/devops/vpn-wireguard-02-internet-proxy<p>Pada artikel bagian kedua ini, kita akan membuat internet gateway atau proxy. Ada banyak kegunaan dari internet proxy ini, diantaranya:</p>
<ul>
<li>mencegah ISP kita untuk memasukkan iklan-iklan yang mengganggu</li>
<li>mencegah pengelola jaringan internet di negara tertentu untuk memantau kegiatan internet kita. Ini berguna misalnya untuk jurnalis yang bertugas di daerah yang otoriter, sehingga rawan diciduk ketika menulis artikel yang sensitif</li>
<li>mengakses layanan yang hanya ada di negara tertentu. Misalnya hiburan streaming yang pilihan tayangannya berbeda antar negara</li>
<li>mengetes aplikasi yang kita buat, apakah berjalan dengan baik ketika diakses dari luar negeri</li>
</ul>
<p>Skenario ini bisa diilustrasikan seperti pada gambar berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-internet-proxy.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-internet-proxy.jpg" alt="VPN Internet Proxy" /></a></p>
<p>Langkah-langkah implementasinya sebagai berikut:</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#menyiapkan-vps" id="markdown-toc-menyiapkan-vps">Menyiapkan VPS</a></li>
<li><a href="#konfigurasi-wireguard-dan-internet-sharing" id="markdown-toc-konfigurasi-wireguard-dan-internet-sharing">Konfigurasi WireGuard dan Internet Sharing</a></li>
<li><a href="#membuat-konfigurasi-user" id="markdown-toc-membuat-konfigurasi-user">Membuat Konfigurasi User</a></li>
<li><a href="#pengetesan" id="markdown-toc-pengetesan">Pengetesan</a></li>
<li><a href="#cara-singkat-dengan-easy-wg-quick" id="markdown-toc-cara-singkat-dengan-easy-wg-quick">Cara Singkat dengan Easy WG Quick</a></li>
<li><a href="#antarmuka-web-dengan-wg-gen-web" id="markdown-toc-antarmuka-web-dengan-wg-gen-web">Antarmuka Web dengan Wg Gen Web</a></li>
<li><a href="#kill-switch" id="markdown-toc-kill-switch">Kill Switch</a></li>
</ul>
<h2 id="menyiapkan-vps">Menyiapkan VPS</h2>
<p>Untuk implementasi internet proxy, kita membutuhkan host yang berlokasi di negara yang kita inginkan. Sebagai contoh, kita berlangganan layanan streaming film yang mana ketersediaan pilihan filmnya berbeda untuk warga +1 dan warga +62. Untuk itu, kita perlu memiliki host yang berlokasi di negara +1.</p>
<p>Host ini bisa kita dapatkan dengan cara berlangganan jasa cloud service yang memiliki data center di negara +1. Misalnya Digital Ocean, Amazon, Google, dan sebagainya. Untuk coba-coba, biasanya mereka menyediakan akun gratis yang bisa kita gunakan selama periode tertentu.</p>
<p>Kita membutuhkan layanan cloud berupa VPS (virtual private server) atau biasa dikenal dengan istilah IaaS (infrastructure as a service).</p>
<p>Contohnya, kita bisa menggunakan layanan DigitalOcean dan membuat VPS (droplet dalam istilah DigitalOcean) di region US seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/digitalocean-usa.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/digitalocean-usa.png" alt="DigitalOcean USA" /></a></p>
<p>Kita juga bisa membuat VPS di Amazon, berupa layanan Lightsail seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/lightsail-usa.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/lightsail-usa.png" alt="Lightsail USA" /></a></p>
<p>Setelah kita buat VPS, kita bisa login menggunakan SSH dan melakukan instalasi seperti dibahas pada <a href="https://software.endy.muhardin.com/devops/vpn-wireguard-01-intro/">artikel sebelumnya</a>.</p>
<h2 id="konfigurasi-wireguard-dan-internet-sharing">Konfigurasi WireGuard dan Internet Sharing</h2>
<p>Pada prinsipnya, membuat internet proxy dengan VPN sama saja dengan mengkonfigurasi internet connection sharing. Konsep dan implementasinya sudah pernah saya bahas di <a href="/linux/network-address-translation/">artikel jaman dulu tentang Network Address Translation</a>. Walaupun tulisan tersebut saat ini sudah berusia 13 tahun, tapi konsepnya masih sama. Bahkan command <code class="language-plaintext highlighter-rouge">iptables</code>nya masih bisa dicopas. Jadi, silahkan baca dan pahami dulu artikel tersebut.</p>
<blockquote>
<p>Sudah dibaca ??? Oke baik mari kita lanjutkan …</p>
</blockquote>
<p>Dari artikel tentang Network Address Translation (NAT) tadi, kita mendapatkan bahwa untuk membuat internet connection sharing, kita perlu melakukan 2 hal:</p>
<ol>
<li>Mengaktifkan packet forwarding di kernel linux</li>
<li>Memasang rule <code class="language-plaintext highlighter-rouge">iptables</code> untuk melakukan NAT, yaitu source NAT atau SNAT.</li>
</ol>
<p>Aktivasi IP forwarding di Ubuntu dilakukan dengan cara mengedit file <code class="language-plaintext highlighter-rouge">/etc/sysctl.conf</code> dan mengaktifkan baris konfigurasi berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>net.ipv4.ip_forward=1
</code></pre></div></div>
<p>Setelah itu, reload file konfigurasinya dengan perintah:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sysctl -p /etc/sysctl.conf
</code></pre></div></div>
<p>Rule <code class="language-plaintext highlighter-rouge">iptables</code> untuk melakukan SNAT ada 2, sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -A FORWARD -i wg0 -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
</code></pre></div></div>
<p>Kita menggunakan tipe khusus dari SNAT, yaitu MASQUERADE. Ini biasa digunakan untuk mengantisipasi IP public yang tidak tetap bila kita mengunakan internet di rumah. Walaupun pada kasus kita IP publicnya tetap (static) karena berada di cloud, untuk alasan konsistensi dan keseragaman kita tetap menggunakan MASQUERADE alih-alih SNAT biasa.</p>
<p>Perintah <code class="language-plaintext highlighter-rouge">iptables</code> tersebut ingin kita jalankan setelah interface <code class="language-plaintext highlighter-rouge">wg0</code> aktif. WireGuard menyediakan hook untuk menjalankan perintah tertentu setelah interface aktif (<code class="language-plaintext highlighter-rouge">PostUp</code>) dan setelah interface nonaktif (<code class="language-plaintext highlighter-rouge">PostDown</code>). Di dalam hook command kita bisa menggunakan variabel <code class="language-plaintext highlighter-rouge">%i</code> untuk mengacu kepada nama interface (misal: <code class="language-plaintext highlighter-rouge">wg0</code>, <code class="language-plaintext highlighter-rouge">wg1</code>, dst).</p>
<p>Dengan demikian, konfigurasi WireGuard kita di VPN Proxy menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = 2KSQxhAa4EmrFV//t5Lbvq5L4nCDo6bHrM2/Dolxo04=
Address = 10.100.10.1/24 # IP Private VPN Gateway
ListenPort = 51515 # Port untuk menerima koneksi dari user
# Aturan Firewall untuk meneruskan paket dari user ke internet
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Kalau VPN dimatikan, hapus aturan firewall untuk meneruskan paket dari user
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
</code></pre></div></div>
<p>Di dalam file konfigurasi WireGuard, kita bisa menggunakan variabel <code class="language-plaintext highlighter-rouge">%i</code> yang akan terisi dengan nama interface wireguard yang digunakan, misalnya <code class="language-plaintext highlighter-rouge">wg0</code>.</p>
<p>Konfigurasi interface WireGuard di sisi VPN Gateway selesai. Sekarang kita bisa membuatkan konfigurasi untuk masing-masing user yang ingin menggunakan proxy tersebut.</p>
<h2 id="membuat-konfigurasi-user">Membuat Konfigurasi User</h2>
<p>Ada beberapa hal yang harus dilakukan setiap kita menambah user baru, yaitu:</p>
<ol>
<li>
<p>Membuatkan file konfigurasi user yang berisi blok <code class="language-plaintext highlighter-rouge">[Interface]</code> berisi setting private key dan alamat IP private dalam VPN. Kemudian membuat juga blok <code class="language-plaintext highlighter-rouge">[Peer]</code> berisi alamat IP public VPN Proxy, public keynya, dan aturan routing (<code class="language-plaintext highlighter-rouge">AllowedIPs</code>)</p>
</li>
<li>
<p>Mendaftarkan user ke dalam file konfigurasi VPN Gateway berupa blok <code class="language-plaintext highlighter-rouge">[Peer]</code> berisi public key dan alamat IP private dalam VPN.</p>
</li>
</ol>
<p>Konfigurasi untuk user isinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = ADE2d+4CT/kJIVC3YlCp3b45YHqpy3FZji15zUTqG1c=
Address = 10.100.10.11/24 # IP Private User
DNS = 1.1.1.1, 1.0.0.1 # DNS diarahkan ke Cloudflare
[Peer]
# Public Key VPN Gateway
PublicKey = bGalHOzArxIoTFDVz0fMcidw6k01Vlk3Zo5ancGjIlg=
# Semua paket dilewatkan ke VPN gateway
AllowedIPs = 0.0.0.0/0
# IP Public VPN Gateway dan ListenPort WireGuard
Endpoint = 139.59.112.17:51515
</code></pre></div></div>
<p>Sedangkan pasangannya berupa blok <code class="language-plaintext highlighter-rouge">[Peer]</code> di dalam konfigurasi VPN gateway terlihat seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
# blok interface seperti dijelaskan di atas
# Konfigurasi per user. Satu user satu blok [Peer]
[Peer]
PublicKey = YQJ8wfZ++ebVPlJas9iNmgk5zxyuA1Cav9Exo4CvSQQ=
AllowedIPs = 10.100.10.11/32
</code></pre></div></div>
<p>File konfigurasi user bisa kita tampilkan dalam QR Code dengan aplikasi <code class="language-plaintext highlighter-rouge">qrencode</code>. Kita gunakan perintah <code class="language-plaintext highlighter-rouge">cat</code> untuk menampilkan isi file, kemudian kita <em>pipe</em> outputnya ke <code class="language-plaintext highlighter-rouge">qrencode</code>. Aplikasi <code class="language-plaintext highlighter-rouge">qrencode</code> bisa menampilkan QR Code dalam format teks, sehingga bisa dijalankan di command line, seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qrencode -t ansiutf8 < user.conf
█████████████████████████████████████████████████████████████████████████████
█████████████████████████████████████████████████████████████████████████████
████ ▄▄▄▄▄ █▀▄▀▄▄▄ ▀█ ▀█▀▄▀███▀ ▄█▀█▄▄▀ ▀▀ ▄ █▄▄▄▀▄█▄█▀▀ █ ▄▄▄█ ▄▄▄▄▄ ████
████ █ █ █▀ ▀▀▄▄▀ █▄▀█ ▄█▀▄ █ █ █▀ ▀▄ ▄ ▀▄▀ ▄ ▄▄▄██▀▀ █▄▄█▀▄█▀█ █ █ ████
████ █▄▄▄█ █▀ █▀▀▀▄▄█▀▀▀ ▄▀▀▄▀ ▄█▀▄ ▄▄▄ ▀▄▀▀▀██▄▄▀█ █▀ ▄▀▄ ▄▄ █▄█ █▄▄▄█ ████
████▄▄▄▄▄▄▄█▄█▄█ █ █▄▀ ▀▄█▄█ ▀ ▀▄█ █ █▄█ █ ▀ █ ▀ ▀▄▀▄█ █▄▀▄▀ ▀ █▄█▄▄▄▄▄▄▄████
████ ▄ ▄ ▀▄▄ ▀ █ ▀▀▄▀██▄ ▄█▀▄█▀ ▄▄▀▀▄ ▄ ▄▀▄▀▀█▄█ █▄▀▄█ ▄ ▄ ██ █ ▀▄▀ █ ▀▄▀████
████▀▄█▀██▄ ▀▄▄▄▄▀▀▄▄█▀▀███▀█▄▄ █▄ ▀█ ▀ ██▄▄▄ █ ██▀▄ ▄▀▀██▀▀▀▄▄▄ ▄▀▀█▄█▀████
████▄▀▀▄ █▄▀█▀█▄▄▀▀ ▀▀ █▄ ▄▄██▄ ▄ ▄▀▄▀▄▀██ █▄▀██ ██▀▄▄ ██ ██▀ █▀█▀▄▀██▄▀████
█████▄▄▀█ ▄▀█ ▄█ ▄▀▀▄▄███ ██▀▀█ █▄▄█▄▄█▀▀▀█▄▀▄█▀▀█ ▄█▄▄▀▄▄▀▀█▄█ ▄▄▀▀ ██████
████▀█▄▀ ▀▄▄ ▄▀▄█ ▄▄▄▀▀▀▄ ▄█▀█▀█▀▄▄ ██ ▄█▀ █ ██ █▄ ▄▄▀█ ▀▀▄▄▄ ▄██████████
████▀█▄█ ▄▄▀ ██▀▄█▀███▀▀ ▀██▀ █▀█ ▄▄ ▀▄██▄▄▄▀▀ ▄▀ █ ▄ █▄ ▄ ▀▄▄▄ ▄█ ▄▀██ ▀████
████▀█▄ ▄█▄ █ █ █ ▄█ ▀ ▄█ ▄█▄█▀ ▀ █ █ ▀▄██ ▄█▀▀█ ▀ ▄▄ ▀█▄█▄▀▄▄ ▄▀ ▀ ▀█ ████
████▄▄█▄▄▄▄▀▀ ▀▄█▄█▄▄ █▀▀ ▀█▄▀ ▀▀▀▄ ▄ ▄▀▄█▄▀ ▀ █▀█▄▄ ▄▀█ ▀█ ▀ █▀▀█ ██████
██████▀█▀▄▄ ▄█▀▀▄▄▀▀█▄▄▀▄▄ █▀▀▀█ ▄█▄█ ▄█▀▀█ █ ██▄▄█▀▄█▀ ▀█▀▄▀▀▄ ███▀▀████
████ ▀██▀▄▄ ▀█▀█ ▄ ▄▀▄ █▄ █▄▀█▄██▀▄▀█ ▄██▄▀ █▄▄ ▄▄ █▄▀ ▀▄ ▄█▀█▄▀ █ █▄████
█████ ▀▄▀▀▄ ▀▀█ ▀█ █▀ ▄▀▄ ▄██▀ ▄▄▄▄██▀▀▄▄█ ▀█▀█ ▀▀▀███▄▄▄▀█▄ ▀▄ ▄▀ ▀ ▄▀█████
█████ ▄▄ ▄▄ ▄ ▀██▀ ▀▀ ▄█▀▄▄█▄ █▀▄ █▄ ██▄▀█▄▄▄█▀▀█▄▀█ ▄ ▀▄▄ ▄▀▄ ▀ █ ▀▀▀█ ████
████▄▀██ ▄▄▄ █▄██▄▀▀▄▀ ▄█ ▄▄▀▄▄▄▀▀██ ▄▄▄ █▄ ▀▀ █▀▀██▀██ ██▄▀ ██ ▄▄▄ ▄█▀█████
████▄ █ █▄█ ▄▀▄▀ ▄ ██ ▄█ ▄██▀█▄ █ █ █▄█ ▄▄ ▀██▀█ ▄█ █▀▀▀▀▄▀ █ █▄█ ▄▄ ▀████
█████▀ ▀ ▄▄▄▀▄██▀▄▀▄█▄▄█▄▀▄█ █▀ ▀▄ ▄ ▄▄▀▀ ▀▄██▀▀ █▀▄▀▄▄▄▀██ ▀ ▄▄▄ ▀ ▄████
████▀▄ █▄ ▄▄▄██▀ ▄▀ █▄██▄██ ▀ ▄█ ██▄█▄███▀█▄█▀▀█ ▀ █ ▄ ▀▀██▄▀ █▄▄▄█▀▄████
█████ ▀▀█▄██ ▄ █ ▀█▀█▄▄▄█▀▄▀█▀ █ ▄▀▀▀█▀▄██▀█ █ ▄█ ▄▄▄▄ █ ▄ ██▀▄▄▀▀▀████
████ ▄█ ▀▄▀▄ ▄█ ▀█▀██▀▄██▄▀███ ▄ ▀███ ▄▄▀▄█▀▀ ███ ▄▄▄█▄ ▀▀ █▀█▀██ ▀ ████
████▀ █▄▄▄▄█ ▄ ▄▀▀▀ ▄▀ ▀▄█▀ ▀▄ ▄▄▀▄█ ▄█▄▀█ ▀▄ ▀█ ▄ ██ █▄ ▄█ █ ▀▄▀▀▀▀████
████▄▄ ▄▄▀▄▄▄ ██▄██▄▄█ ▀██ ▀▀█ █▀ █▄██▄█▀▄▀▄▀▀▄█▄ ▀▄▄▀▀█▄▀ █▀▀▀▀ █▀████
████▄ ▀█▄▀▄▄█▀▄█▀ ▀▀▀▄▀▀▀ ▀▄█▀█ █▀▀█▄▄▀▄▀█ ▀▀▀▀ ▄▀▀▄▀█▀▄██ ██ ▄█▀█▄████
████▀█▀ █▄█▀█ █▄▄ █▀▀ ▄▄█ ██ █▀▄ █▄█▄█▄ ▄▀▄█▀▀▀▀ ▄█▄▄▀▄▀▀█▄▄ █▀████▄█████
████▄▄▀ █▄▄ ▄▄▄ ▄█▄▀ ▀▄▄▀▄▄▀ █▀▀▄▀█▀█ ██ █ ▄▀██▀█ █▀██ ▄█ █▀▀▄▀ ▄▄▄ █▀█████
█████▀█ █▄▄▀▀▀ ▀▀ ▄█▄▀▀ ▀ ██ █ ██▄▀▄▀▀▄▄▀▄▀▀ ▀▄▄ ▄▄██▀▀▄▀▀██▀██ ▄▄ ▀█████
█████▀█ ▀▀▄█ ▄▀▀█▄▄▄▀▀▀▄ ▀▄▄ █▀▀ ▄ ██▄▀█▄ █▄██▄▀▄█▄▄▄▄▀▄▄▀ ▀▀▀█ ▄ ▀ ██▄▀████
████ █▄█▄▄▄ ▄█ ▀ ▄ ▄█ ▀▄ ▄▀██▀▄█ ▄█ ▄ ▀▀ █▄▀▀█ ██▀ ▀ ▀▄██▀▄ █ ▀█████
████▄██▄▄█▄█▀ █ ▀ ▀▀█▀ █▀ █▄▀▀▀ ▄ ▄▄ ▄▄▄ ▄▀ ▄▄███▀▄█▄▄█ ▄▄▀▀ █ ▄▄▄ ▀▀▀████
████ ▄▄▄▄▄ █▄▀▄ ███ ▀▀ ██ ▄▀▀▄ ▀▄█ ▄ █▄█ █ █▀▄▀▀▀▀█▀█▄ ▄ ▀ █▄▄ █▄█ █▄████
████ █ █ █ ▀▀▄▄ ▀█▀ ▄█▄▀▄█▄▄ ▀█▄ ▄ ▄▄▄ ▄▄▀ ██ █▀█ ▄▄██▀█▄██▄▄▄▄▄ ▄███████
████ █▄▄▄█ █ ▄█▀▀ █▀▀▄█ █▄▀ ▄ ▄█ ▀▀███▀▄▄█▀▄▀▀▀ ▄▀▄███▀ ▀ █ ▀▀▄▄▄ ▄▄ ██████
████▄▄▄▄▄▄▄█▄█▄▄▄▄██▄█▄▄█▄██▄███▄███▄██▄▄█▄▄████▄▄██▄█▄▄████▄▄████▄▄██▄▄█████
█████████████████████████████████████████████████████████████████████████████
█████████████████████████████████████████████████████████████████████████████
</code></pre></div></div>
<p>Atau bisa juga kita tulis ke file, sehingga bisa dikirim melalui email atau chat.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qrencode -o user.png < user.conf
</code></pre></div></div>
<p>QR Code ini bisa kita scan di aplikasi Android atau IOS. Sedangkan untuk aplikasi desktop (Windows, Linux, MacOS), kita bisa langsung copy-paste file konfigurasi dalam format teks tadi.</p>
<p>Supaya lebih rapi dan portable, di sisi internet gateway kita bisa mengumpulkan konfigurasi semua user dalam satu file, misalnya kita beri nama <code class="language-plaintext highlighter-rouge">peer.conf</code>. Kemudian file ini kita load dari konfigurasi utama di file <code class="language-plaintext highlighter-rouge">wg0.conf</code> dengan menambakan baris berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PostUp = wg addconf %i /etc/wireguard/peer.conf
</code></pre></div></div>
<p>sehingga isinya menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = 2KSQxhAa4EmrFV//t5Lbvq5L4nCDo6bHrM2/Dolxo04=
Address = 10.100.10.1/24 # IP Private VPN Gateway
ListenPort = 51515 # Port untuk menerima koneksi dari user
# Aturan Firewall untuk meneruskan paket dari user ke internet
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Load konfigurasi user
PostUp = wg addconf %i /etc/wireguard/peer.conf
# Kalau VPN dimatikan, hapus aturan firewall untuk meneruskan paket dari user
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
</code></pre></div></div>
<h2 id="pengetesan">Pengetesan</h2>
<p>Kita bisa mengetes konfigurasi ini dengan cara browse ke website <a href="https://ifconfig.me/">ifconfig.me</a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/ifconfig.me.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/ifconfig.me.png" alt="IFConfig.Me" /></a></p>
<p>atau menjalankan perintah <code class="language-plaintext highlighter-rouge">curl ifconfig.me</code> dari command line.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl ifconfig.me
180.244.232.79
</code></pre></div></div>
<h2 id="cara-singkat-dengan-easy-wg-quick">Cara Singkat dengan Easy WG Quick</h2>
<p>Bila kita tidak ingin melakukan konfigurasi secara manual seperti cara di atas, kita bisa menggunakan script yang sudah dibuatkan orang di internet. Salah satunya adalah <a href="https://github.com/burghardt/easy-wg-quick">Easy WG Quick</a>. Aplikasi ini terinspirasi dari aplikasi konfigurator untuk OpenVPN, yaitu <a href="https://github.com/OpenVPN/easy-rsa">Easy RSA</a>.</p>
<p>Berikut rangkaian perintah untuk membuat client dengan <code class="language-plaintext highlighter-rouge">easy-wg-quick</code>.</p>
<ol>
<li>
<p>Download scriptnya. Kemudian set executable</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> wget https://git.io/fjb5R -O easy-wg-quick
chmod +x easy-wg-quick
</code></pre></div> </div>
</li>
<li>
<p>Buat client <code class="language-plaintext highlighter-rouge">endy-laptop</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ./easy-wg-quick endy-laptop
</code></pre></div> </div>
</li>
<li>
<p>Download dan install konfigurasi ke laptop. Ini tidak saya jelaskan, karena tergantung aplikasi client yang digunakan di perangkat masing-masing.</p>
</li>
<li>
<p>Aktifkan konfigurasi peer di sisi server</p>
<ul>
<li>
<p>Copy file <code class="language-plaintext highlighter-rouge">wghub.conf</code> ke folder <code class="language-plaintext highlighter-rouge">/etc/wireguard</code>. Ini harus dilakukan tiap kali ada penambahan client baru.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cp wghub.conf /etc/wireguard/
</code></pre></div> </div>
</li>
<li>
<p>Instal sebagai <code class="language-plaintext highlighter-rouge">systemd</code> service (ini sekali saja pertama kali)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> systemctl enable wg-quick@wghub
</code></pre></div> </div>
</li>
<li>
<p>(Re)start service <code class="language-plaintext highlighter-rouge">systemd</code>. Dilakukan setiap kali kita menimpa <code class="language-plaintext highlighter-rouge">wghub.conf</code> dengan yang baru (setelah menambah client baru)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> systemctl start wg-quick@wghub
</code></pre></div> </div>
</li>
</ul>
</li>
</ol>
<h2 id="antarmuka-web-dengan-wg-gen-web">Antarmuka Web dengan Wg Gen Web</h2>
<p>Bila kita tidak mau menggunakan antarmuka berbasis command line, kita juga bisa menggunakan tampilan berbasis web, yaitu <a href="https://github.com/vx3r/wg-gen-web">Wg Gen Web</a>. Ini adalah aplikasi web untuk mengelola konfigurasi di sisi server dan juga di sisi client.</p>
<p>Untuk menjalankan aplikasi ini, kita bisa menggunakan perintah docker seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --rm -it -v /Users/endymuhardin/tmp/wg-gen-web/data:/data -p 8080:8080 -e "WG_CONF_DIR=/data" vx3r/wg-gen-web:latest
</code></pre></div></div>
<p>Atau kalau saya, lebih suka menggunakan file <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> yang isinya seperti ini:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3.6'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">wg-gen-web</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">vx3r/wg-gen-web:latest</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">wg-gen-web</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">8080:8080"</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">WG_CONF_DIR=/data</span>
<span class="pi">-</span> <span class="s">OAUTH2_PROVIDER_NAME=fake</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./data:/data</span>
</code></pre></div></div>
<p>Setelah dijalankan, kita bisa melihat tampilan untuk mengelola konfigurasi server sebagai berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-server-1.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-server-1.png" alt="Wg Gen Web Server Config 1" /></a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-server-2.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-server-2.png" alt="Wg Gen Web Server Config 2" /></a></p>
<p>Dan berikut adalah tampilan untuk mendaftarkan client baru</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-add-client.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-add-client.png" alt="Wg Gen Web Add Client" /></a></p>
<p>Setelah didaftarkan, kita bisa melihat daftar client yang ada, berikut dengan QR Code untuk konfigurasinya</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-list-client.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-list-client.png" alt="Wg Gen Web List Client" /></a></p>
<p>Aplikasi ini akan membuat beberapa file konfigurasi yang nantinya bisa kita pasang di server ataupun dikirim ke client.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-data-folder-content.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/wg-gen-web-data-folder-content.png" alt="Wg Gen Web Konfigurasi" /></a></p>
<p>Aplikasi ini bisa dipasang di server, sehingga bila ada penambahan client, file <code class="language-plaintext highlighter-rouge">wg0.conf</code> akan langsung ter-update. Akan tetapi, kita harus melakukan konfigurasi <code class="language-plaintext highlighter-rouge">systemd</code> supaya service Wireguard direstart pada saat file <code class="language-plaintext highlighter-rouge">wg0.conf</code> berubah isinya. Caranya adalah dengan mendaftarkan <code class="language-plaintext highlighter-rouge">Systemd Path Monitor</code>. Berikut konfigurasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /etc/systemd/system/wg-gen-web.path
[Unit]
Description=Watch /etc/wireguard for changes
[Path]
PathModified=/etc/wireguard
[Install]
WantedBy=multi-user.target
</code></pre></div></div>
<p>Konfigurasi di atas akan memonitor folder <code class="language-plaintext highlighter-rouge">/etc/wireguard</code>. Bila isinya berubah, maka dia akan mengeksekusi service <code class="language-plaintext highlighter-rouge">wg-gen-web</code> yang konfigurasinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /etc/systemd/system/wg-gen-web.service
[Unit]
Description=Reload WireGuard
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl reload wg-quick@wg0.service
[Install]
WantedBy=multi-user.target
</code></pre></div></div>
<p>Akan tetapi, bila kita tidak mau menambah aplikasi yang kurang esensial ke server kita, maka kita bisa jalankan aplikasi ini di lokal menggunakan docker pada saat ingin menambah/mengurangi client, dan kemudian kita copy manual file hasilnya ke server.</p>
<h2 id="kill-switch">Kill Switch</h2>
<p>Dalam penggunaan VPN, kita mengenal adanya istilah VPN leak. Yaitu kadangkala koneksi VPN terputus dan komputer/smartphone kita mengirim paket tanpa VPN. Dengan demikian, di sisi penerima bisa melihat alamat IP kita yang asli, kemudian mengkorelasikannya dengan request kita sebelumnya (misal dengan session ID) dan mendeteksi bahwa ada dua request yang usernya sama, tapi alamat IPnya berbeda.</p>
<p>VPN leak ini bisa memicu tindakan preventif di sisi penerima / penyedia layanan. Beberapa aplikasi sosial media bisa mengira bahwa akun kita dihack (karena diakses dari negara yang berbeda dalam hitungan menit, menit 1 dari USA, menit 2 dari Indonesia), dan kemudian segera memblokir akun kita untuk mencegah penyalahgunaan. Demikian juga dengan aplikasi perbankan (internet atau mobile banking), mencurigai adanya fraud dan segera memblokir akun kita. Atau bisa juga layanan tontonan streaming yang mendeteksi bahwa kita mencoba memalsukan alamat asal kita untuk mendapatkan tayangan yang lebih lengkap. Intinya, VPN leak bisa menyebabkan akun kita diblokir.</p>
<p>Untuk mencegah VPN leak, di sisi pengguna harus ada mekanisme untuk mencegah komputer/smartphone agar tidak mengirim paket dulu ketika VPN sedang terputus. Ini disebut dengan fitur <em>kill switch</em>.</p>
<p>Di komputer Linux, kita bisa mengimplementasikan <em>kill switch</em> dengan aturan <code class="language-plaintext highlighter-rouge">iptables</code>, blokir semua akses ke internet selain interface <code class="language-plaintext highlighter-rouge">wg0</code>. Tutorialnya bisa dibaca di <a href="https://www.ivpn.net/knowledgebase/linux/linux-wireguard-kill-switch/">artikel ini</a></p>
<p>Di smartphone Android, sayangnya aplikasi resmi WireGuard belum menyertakn fitur killswitch pada waktu artikel ini saya tulis. Sebagai alternatif, kita bisa menggunakan aplikasi lain yaitu <a href="https://play.google.com/store/apps/details?id=com.tunsafe.app">TunSafe</a>. Aplikasi ini sudah memiliki fitur killswitch.</p>
<p>Sedangkan untuk laptop MacOS dan iPhone, saat ini saya belum menemukan cara untuk mengimplementasikan killswitch. Kalau nanti ada informasi baru mengenai hal ini, tulisan ini akan saya update.</p>
<p>Demikianlah setup VPN untuk skenario Internet Proxy. Semoga bermanfaat …</p>
VPN dengan Wireguard Bagian I : Pengenalan Wireguard2020-12-25T07:00:00+07:00https://software.endy.muhardin.com/devops/vpn-wireguard-01-intro<p>Sejak belasan tahun yang lalu, saya sudah <a href="https://software.endy.muhardin.com/life/pengetahuan-wajib-buat-programmer/">merekomendasikan pentingnya menguasai konsep dan konfigurasi jaringan</a>, khususnya administrasi jaringan dan server dengan Linux. Di penghujung 2020 ini, rekomendasi tersebut terasa lebih relevan seiring dengan semakin naik daunnya budaya DevOps.</p>
<p>Untuk itu, kita akan menutup tahun 2020 ini dengan membahas implementasi VPN yang sekarang sedang naik daun, yaitu <a href="https://www.wireguard.com/">WireGuard</a>. WireGuard adalah aplikasi VPN yang sangat mudah digunakan. Jauh lebih sederhana daripada pendahulunya seperti OpenVPN dan IPSec. Selain itu, performanya juga jauh melampaui para pendahulunya tersebut. Bahkan Linus Torvalds sendiri memuji kode program dan kesederhanaan WireGuard, padahal Linus biasanya terkenal tajam lidahnya.</p>
<p>Informasi lebih rinci mengenai keunggulan Wireguard dapat dibaca di <a href="https://itsfoss.com/wireguard/">artikel ini</a>, <a href="https://arstechnica.com/gadgets/2018/08/wireguard-vpn-review-fast-connections-amaze-but-windows-support-needs-to-happen/">artikel ini</a>, dan <a href="https://www.ivpn.net/pptp-vs-ipsec-ikev2-vs-openvpn-vs-wireguard/">artikel ini</a>. Tapi tidak ada yang sempurna di dunia ini, Wireguard juga memiliki beberapa masalah dalam urusan privasi, seperti dibahas di <a href="https://restoreprivacy.com/vpn/wireguard/">artikel ini</a>. Walaupun demikian, untuk keperluan yang akan kita bahas di sini, privasi tidak terlalu menjadi pertimbangan kita, karena di hampir seluruh skenario yang akan kita bahas komputer dan jaringan yang terlibat adalah milik kita sendiri.</p>
<p>Dalam rangkaian artikel kali ini, kita akan membahas beberapa skenario penggunaan VPN yang umum digunakan oleh masyarakat umum dan juga software developer, yaitu:</p>
<ul>
<li>Membuat proxy internet</li>
<li>Mengakses jaringan internal kantor dari luar kantor</li>
<li>Membuka akses ke aplikasi yang berjalan di laptop untuk diakses dari internet</li>
<li>Menghubungkan aplikasi yang berjalan di cloud services ke database yang berjalan di data center on premise</li>
</ul>
<blockquote>
<p>Disclaimer : rangkaian artikel ini mengasumsikan pembaca sudah menguasai dasar-dasar jaringan komputer dan pengoperasian Linux dengan command line/terminal.</p>
</blockquote>
<p>Sebelum masuk ke konfigurasi yang kompleks, terlebih dulu kita akan mempelajari konsep dasar dari aplikasi Wireguard. Kita akan berlatih menghubungkan dua atau beberapa host dengan Wireguard. Untuk latihan ini, kita bisa menggunakan dua laptop, satu laptop dan satu VPS di cloud, ataupun satu laptop dan satu smartphone.</p>
<p>Skemanya dapat digambarkan sebagai berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-intro-wireguard.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/vpn-use-case-intro-wireguard.jpg" alt="VPN Peers" /></a></p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#konsep-dasar-wireguard---peer-vs-client-server" id="markdown-toc-konsep-dasar-wireguard---peer-vs-client-server">Konsep Dasar Wireguard - Peer vs Client-Server</a></li>
<li><a href="#instalasi-wireguard-di-ubuntu" id="markdown-toc-instalasi-wireguard-di-ubuntu">Instalasi Wireguard di Ubuntu</a></li>
<li><a href="#instalasi-wireguard-di-macos" id="markdown-toc-instalasi-wireguard-di-macos">Instalasi Wireguard di MacOS</a></li>
<li><a href="#aplikasi-android" id="markdown-toc-aplikasi-android">Aplikasi Android</a></li>
<li><a href="#konfigurasi-peer" id="markdown-toc-konfigurasi-peer">Konfigurasi Peer</a></li>
<li><a href="#pengetesan" id="markdown-toc-pengetesan">Pengetesan</a></li>
</ul>
<h2 id="konsep-dasar-wireguard---peer-vs-client-server">Konsep Dasar Wireguard - Peer vs Client-Server</h2>
<p>Agak berbeda dengan konsep VPN yang biasa kita kenal di OpenVPN, IPSec, dan produk VPN lainnya, WireGuard sebetulnya tidak mengenal konsep server dan client. Di produk lain biasanya kita akan menginstal VPN server yang berisi semua konfigurasi client, kemudian kita instal konfigurasi di masing-masing perangkat yang ingin ikut bergabung dalam VPN sebagai client. Setelah itu, kita jalankan aplikasi VPN client di masing-masing perangkat, dan kemudian dia akan berusaha menghubungi VPN server untuk ikut bergabung dalam jaringan.</p>
<p>Dalam WireGuard, semua perangkat yang terhubung dianggap setara (peer). Kalaupun ada yang dianggap sebagai server, itu biasanya hanya karena perangkat tersebut memiliki IP public yang static. Sehingga bisa dihubungi dengan alamat yang jelas. Tidak seperti perangkat kita yang menggunakan internet rumah atau paket data. Belum tentu punya IP public dan kalaupun ada, bisa berganti setiap kali reconnect.</p>
<p>Di produk lain seperti OpenVPN dan lainnya, sebetulnya pada dasarnya juga menggunakan skema peer to peer. Tapi karena ada satu perangkat yang dipasangi banyak tambahan aksesoris lain seperti DHCP server, DNS server, routing, authentication service, dan sebagainya, maka dia dinobatkan menjadi server.</p>
<p>WireGuard hanya menyediakan fungsi dasar untuk menyediakan perangkat jaringan (ethernet card virtual) dan layanan enkripsi dari komputer satu ke komputer lain. Dia tidak menyediakan DHCP service, DNS service, routing, dan sebagainya. Masing-masing perangkat yang ingin bergabung harus memilih dan memasang alamat IP sendiri. WireGuard hanya menyediakan hook untuk menjalankan perintah lain ketika dia dinyalakan (PostUp) dan dimatikan (PostDown). Kalau kita ingin mengatur routing dan forwarding, maka kita taruh perintahnya di <code class="language-plaintext highlighter-rouge">PostUp</code> dan <code class="language-plaintext highlighter-rouge">PostDown</code>. WireGuard hanya membantu menjalankan saja.</p>
<p>Fitur yang sedikit ini menyebabkan setup dan penggunaan WireGuard menjadi sangat sederhana. Akan tetapi, ini menyebabkan administrator jaringan harus kerja sedikit ekstra untuk membuatkan konfigurasi secara manual. Tapi tidak masalah, karena banyak kontributor di internet yang membuatkan script untuk membantu konfigurasi. Contohnya project <a href="https://github.com/burghardt/easy-wg-quick">easy-wg-quick</a> yang memudahkan kita untuk menambahkan peer. Dan kalaupun kita ingin membuatkan aplikasi untuk pengelolaan jaringan, tidak akan terlalu sulit. Cukup kita sediakan UI, database, kemampuan generate konfigurasi berupa text file, dan kemampuan untuk merestart WireGuard.</p>
<p>Perbedaan lain yang cukup mendasar antara WireGuard dan OpenVPN adalah di penggunaan encryption key. OpenVPN menggunakan Public Key Infrastructure dimana tiap pihak harus membuat pasangan public key dan private key. Public keynya kemudian harus di-sign oleh pemilik sertifikat induk (Certificate Authority). Sedangkan WireGuard cukup dengan pasangan public key dan private key yang dipasang secara bersilang. Public key A didaftarkan di komputer B, dan sebaliknya public key B dipasang di komputer A.</p>
<h2 id="instalasi-wireguard-di-ubuntu">Instalasi Wireguard di Ubuntu</h2>
<p>Seperti biasa, instalasi di Ubuntu sangat mudah. Cukup satu baris perintah saja.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># apt update && apt install wireguard -y
</code></pre></div></div>
<p>Setelah terinstal, kita bisa menggunakan perintah <code class="language-plaintext highlighter-rouge">wg</code> di terminal. Kita mulai dengan perintah untuk membuat pasangan private key dan public key.</p>
<ul>
<li>
<p>Membuat private key</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ wg genkey
oNkc7Mz0Zn0GQLuHPhkRsvrnaOL5FANvnRbTsw7dtnY=
</code></pre></div> </div>
</li>
<li>
<p>Membuat private key, menulis ke file dengan perintah <code class="language-plaintext highlighter-rouge">tee</code>, dan menampilkan hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ wg genkey | tee private.key
iKNKroX3g8XXKh9hxaETERYVsnuedJaifog9OHctCFQ=
$ ls -l | grep private
-rw-r--r-- 1 endymuhardin staff 45 Dec 25 10:47 private.key
</code></pre></div> </div>
</li>
<li>
<p>Membuat private key, menulis ke file, kemudian menampilkan public key pasangannya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ wg genkey | tee private.key | wg pubkey
jeRlN4f55GgOVl481Ncla/n28ZWTLkGN3Hu2zIAaWyM=
$ cat private.key
sD11f3UX7DfoiVegO6VGCE5/mehYFfmQno0ZlBG3Wmg=
</code></pre></div> </div>
</li>
<li>
<p>Membuat private key, menulis private key ke file, membuat public key pasangannya, menulis public key ke file, dan menampilkan public key</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ wg genkey | tee private.key | wg pubkey | tee public.key
ZsYN7IFaSpfQLblrTGzivcF8tiVqLIna8AUueZOfvFo=
$ ls -l | grep key
-rw-r--r-- 1 endymuhardin staff 45 Dec 25 10:51 private.key
-rw-r--r-- 1 endymuhardin staff 45 Dec 25 10:51 public.key
</code></pre></div> </div>
</li>
</ul>
<p>Private key dan public key ini nantinya akan kita gunakan untuk menghubungkan komputer satu dan komputer lain. Kita akan membuat interface baru yang nantinya akan menjadi pintu keluar masuk data yang terenkripsi. Di Linux, biasanya interface ini berawal dengan <code class="language-plaintext highlighter-rouge">wg</code>, misalnya <code class="language-plaintext highlighter-rouge">wg0</code> dan <code class="language-plaintext highlighter-rouge">wg1</code>.</p>
<p>Perintah-perintah berikut ini dijalankan dengan user <code class="language-plaintext highlighter-rouge">root</code>.</p>
<ul>
<li>
<p>Melihat interface yang sudah ada</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 02:89:8b:a6:39:ce brd ff:ff:ff:ff:ff:ff
</code></pre></div> </div>
</li>
<li>
<p>Menambah interface wireguard</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # ip link add dev wg0 type wireguard
# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 02:89:8b:a6:39:ce brd ff:ff:ff:ff:ff:ff
4: wg0: <POINTOPOINT,NOARP> mtu 1420 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/none
</code></pre></div> </div>
</li>
<li>
<p>Menghapus interface wireguard</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # ip link del dev wg0
</code></pre></div> </div>
</li>
<li>
<p>Melihat alamat IP di masing-masing interface</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
link/ether 02:89:8b:a6:39:ce brd ff:ff:ff:ff:ff:ff
inet 172.26.3.231/20 brd 172.26.15.255 scope global dynamic eth0
valid_lft 3048sec preferred_lft 3048sec
inet6 fe80::89:8bff:fea6:39ce/64 scope link
valid_lft forever preferred_lft forever
4: wg0: <POINTOPOINT,NOARP> mtu 1420 qdisc noop state DOWN group default qlen 1000
link/none
</code></pre></div> </div>
</li>
<li>
<p>Memberikan alamat IP ke interface <code class="language-plaintext highlighter-rouge">wg0</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # ip address add dev wg0 10.100.10.22/24
# ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
link/ether 02:89:8b:a6:39:ce brd ff:ff:ff:ff:ff:ff
inet 172.26.3.231/20 brd 172.26.15.255 scope global dynamic eth0
valid_lft 3048sec preferred_lft 3048sec
inet6 fe80::89:8bff:fea6:39ce/64 scope link
valid_lft forever preferred_lft forever
4: wg0: <POINTOPOINT,NOARP> mtu 1420 qdisc noop state DOWN group default qlen 1000
link/none
inet 10.100.10.22/24 scope global wg1
valid_lft forever preferred_lft forever
</code></pre></div> </div>
</li>
<li>
<p>Memasang private key ke interface <code class="language-plaintext highlighter-rouge">wg0</code> dan port <code class="language-plaintext highlighter-rouge">51515</code> untuk menunggu koneksi dari peer. Pasangan key dibuat dengan perintah yang sudah dijelaskan di atas. Port yang digunakan bebas. Saya seringkali menggunakan port <code class="language-plaintext highlighter-rouge">80</code>, <code class="language-plaintext highlighter-rouge">443</code>, dan port-port yang sering digunakan, karena port selain itu diblokir oleh Indihome. Untuk contoh di sini, kita akan pakai port <code class="language-plaintext highlighter-rouge">51515</code> saja supaya tidak rancu dengan layanan http.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # wg set wg0 listen-port 51515 private-key ./private.key
</code></pre></div> </div>
</li>
<li>
<p>Mengaktifkan interface <code class="language-plaintext highlighter-rouge">wg0</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # ip link set wg1 up
</code></pre></div> </div>
</li>
<li>
<p>Menampilkan informasi WireGuard untuk interface <code class="language-plaintext highlighter-rouge">wg0</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # wg
interface: wg1
public key: TTJoZNk8JMHWDK4BfnjZdjf0pO9g25oYdXHmuUJqeno=
private key: (hidden)
listening port: 51515
</code></pre></div> </div>
</li>
<li>
<p>Menampilkan konfigurasi dalam format yang bisa disimpan ke file</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # wg showconf
[Interface]
ListenPort = 51515
PrivateKey = gG0hpGeTRpZjSqXfW8s88kNAIF7zKIPSqYfXLASH6UY=
</code></pre></div> </div>
</li>
<li>
<p>Menyimpan konfigurasi ke file, supaya bisa dijalankan otomatis pada waktu booting</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # wg showconf wg0 > /etc/wireguard/wg0.conf
</code></pre></div> </div>
</li>
<li>
<p>Mengaktifkan interface <code class="language-plaintext highlighter-rouge">wg0</code> pada waktu booting</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # systemctl enable wg-quick@wg0
</code></pre></div> </div>
</li>
</ul>
<p>File konfigurasi di atas belum memuat alamat IP dari interface <code class="language-plaintext highlighter-rouge">wg0</code>, sehingga bila dinyalakan pada waktu booting, dia tidak akan memiliki alamat IP. Untuk itu, kita perlu tambahkan lagi alamat IPnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = gG0hpGeTRpZjSqXfW8s88kNAIF7zKIPSqYfXLASH6UY=
Address = 10.100.10.22/24
ListenPort = 51515
</code></pre></div></div>
<h2 id="instalasi-wireguard-di-macos">Instalasi Wireguard di MacOS</h2>
<p>MacOS memiliki aplikasi desktop untuk WireGuard. Aplikasi ini bahkan diinstal melalui AppStore, menunjukkan bahwa pembuatnya cukup serius bukan hanya membuat aplikasinya, tapi mengurus birokrasi yang diperlukan agar bisa disetujui masuk AppStore.</p>
<p>Akan tetapi, tampilannya tidak terlalu jauh beda dengan konfigurasi melalui command line. Penambahan interface dilakukan dengan cara mengetik file konfigurasi seperti ini.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/macos-wireguard.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/macos-wireguard.png" alt="MacOS Wireguard" /></a></p>
<p>Kita bisa tambahkan konfigurasi alamat IP pada tampilan tersebut, sehingga menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = 0Dz3pDjd5eRmze+ila1hdF+S2S0RRT42qnt2tHG5hFk=
Address = 10.100.10.100/24
</code></pre></div></div>
<p>Kita tidak perlu memasang konfigurasi <code class="language-plaintext highlighter-rouge">ListenPort</code> karena laptop biasanya ada di belakang router rumah. Alamat IPnya tidak public, sehingga tidak akan bisa diakses dari luar juga.</p>
<p>Selain menggunakan aplikasi desktop, kita juga bisa menggunakan versi command line. Instalasinya menggunakan <code class="language-plaintext highlighter-rouge">brew</code> sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install wireguard-tools
</code></pre></div></div>
<p>Untuk mengaktifkan interface tersebut, kita bisa menggunakan tampilan desktop</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/macos-wireguard-active.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/macos-wireguard-active.png" alt="MacOS Wireguard Active" /></a></p>
<p>atau command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wg-quick up /path/ke/konfigurasi-wireguard.conf
</code></pre></div></div>
<p>Setelah interface aktif, kita bisa mengecek dengan perintah <code class="language-plaintext highlighter-rouge">ifconfig</code></p>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
inet 127.0.0.1 netmask 0xff000000
inet6 ::1 prefixlen 128
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
nd6 options=201<PERFORMNUD,DAD>
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
options=400<CHANNEL_IO>
ether f4:0f:24:2d:5a:6a
inet6 fe80::c45:9e4f:9b8a:3dd6%en0 prefixlen 64 secured scopeid 0x4
inet 192.168.100.4 netmask 0xffffff00 broadcast 192.168.100.255
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
utun4: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1420
options=6463<RXCSUM,TXCSUM,TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
inet 10.100.10.100 --> 10.100.10.100 netmask 0xffffff00
</code></pre></div></div>
<p>Di MacOS biasanya WireGuard aktif dengan nama device <code class="language-plaintext highlighter-rouge">utun4</code>.</p>
<h2 id="aplikasi-android">Aplikasi Android</h2>
<p>Di Android juga sama, kita bisa menginstal aplikasi Wireguard yang ada di Play Store. Setelah itu kita bisa menambahkan konfigurasi secara manual seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/android-wireguard-config.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/vpn-wireguard/android-wireguard-config.jpg" alt="Konfigurasi Wireguard Android" /></a></p>
<p>Atau mengambil konfigurasi berbasis teks seperti yang sudah kita buat di Ubuntu atau MacOS, dan membuatkan QR Code yang mudah discan. Perintah untuk membuat dan menampilkan QR code adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat konfigurasi.conf | qrencode -t ansiutf8
</code></pre></div></div>
<p>atau kita juga bisa membuat file <code class="language-plaintext highlighter-rouge">png</code> untuk dikirim kepada pengguna, dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat konfigurasi.conf | qrencode -o $CLIENT.png
</code></pre></div></div>
<p>Bila ingin membuat konfigurasi untuk banyak user, biasanya saya menggunakan rangkaian perintah sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p /etc/wireguard/clients; cd /etc/wireguard/clients;
export VPNUSER=namaclient
wg genkey | tee $VPNUSER.key | wg pubkey | tee $VPNUSER.pub
vim $VPNUSER.conf
[Interface]
PrivateKey = oPd+N/9pAa3uCvCPGOKcWJowa8YO9ulGVHUWz0zABnQ=
Address = 172.17.0.101/24
[Peer]
PublicKey = FQcUiIzvvvQ2hHplCsUgR+RN4avDWi/ucF57LTvq11k=
Endpoint = vpngateway.id:51515
AllowedIPs = 0.0.0.0/0
cat $VPNUSER.conf | qrencode -t ansiutf8
cat $VPNUSER.conf | qrencode -o $VPNUSER.png
</code></pre></div></div>
<p>Jangan lupa mengganti isi variabel <code class="language-plaintext highlighter-rouge">VPNUSER</code>, alamat endpoint peer, public key, dan private key sesuai kebutuhan.</p>
<h2 id="konfigurasi-peer">Konfigurasi Peer</h2>
<p>Sekarang kita sudah memiliki mesin Ubuntu, MacOS, smartphone Android, dan iPhone yang telah terpasang interface WireGuard. Semua perangkat ini ada di jaringan internal rumah kita, tergabung dalam satu jaringan wifi yang sama. Sebagai ilustrasi, kita misalkan masing-masing perangkat mendapat alamat IP dari router wifi kita sebagai berikut:</p>
<ul>
<li>Laptop Ubuntu : 192.168.100.101</li>
<li>Laptop Mac : 192.168.100.102</li>
<li>Smartphone Android : 192.168.100.103</li>
<li>iPhone : 192.168.100.104</li>
</ul>
<p>Selanjutnya kita bisa menghubungkan semua host tersebut dengan menambahkan konfigurasi <code class="language-plaintext highlighter-rouge">Peer</code>. Kita daftarkan public key dari masing-masing perangkat di perangkat yang lain. Contohnya seperti ini:</p>
<ul>
<li>
<p>Di laptop Ubuntu</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [Interface]
PrivateKey = gG0hpGeTRpZjSqXfW8s88kNAIF7zKIPSqYfXLASH6UY=
Address = 10.100.10.101/24
ListenPort = 51515
# Public Key Laptop Mac
[Peer]
PublicKey = X8qWb9P8xRZurOiH5p6PxAyLjNjBaYWLmBs38sijQAE=
AllowedIPs = 10.100.10.102/32
# Public Key Android
[Peer]
PublicKey = FQcUiIzvvvQ2hHplCsUgR+RN4avDWi/ucF57LTvq11k=
AllowedIPs = 10.100.10.103/32
# Public Key iPhone
[Peer]
PublicKey = bGalHOzArxIoTFDVz0fMcidw6k01Vlk3Zo5ancGjIlg=
AllowedIPs = 10.100.10.104/32
</code></pre></div> </div>
</li>
<li>
<p>Di laptop Mac</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [Interface]
PrivateKey = GNZuFAzGpn3Ht7Bm6PYxMFoztI7xwZL77zyoAcSwvWc=
Address = 10.100.10.102/24
ListenPort = 51515
# Public Key Laptop Ubuntu
[Peer]
PublicKey = kNzxJEnlglygOYn5GcUEuA8Uqc/QUBzmQ+eledLVVWg=
AllowedIPs = 10.100.10.101/32
# Public Key Android
[Peer]
PublicKey = FQcUiIzvvvQ2hHplCsUgR+RN4avDWi/ucF57LTvq11k=
AllowedIPs = 10.100.10.103/32
# Public Key iPhone
[Peer]
PublicKey = bGalHOzArxIoTFDVz0fMcidw6k01Vlk3Zo5ancGjIlg=
AllowedIPs = 10.100.10.104/32
</code></pre></div> </div>
</li>
</ul>
<p>Dan seterusnya sama untuk smartphone Android dan juga iPhone. Setelah kita buatkan konfigurasinya, kita bisa generate QR code untuk discan di masing-masing smartphone.</p>
<p>Pada konfigurasi di atas, kita mengisi nilai <code class="language-plaintext highlighter-rouge">AllowedIPs</code> dengan alamat IP untuk masing-masing perangkat, sesuai yang kita definisikan pada blok konfigurasi <code class="language-plaintext highlighter-rouge">[Interface]</code>. Kita menggunakan subnet <code class="language-plaintext highlighter-rouge">/32</code> yang artinya hanya mengacu pada satu alamat IP saja. Nantinya, kita bisa mengisi <code class="language-plaintext highlighter-rouge">AllowedIPs</code> dengan alamat network seperti misalnya <code class="language-plaintext highlighter-rouge">192.168.10.0/24</code>, untuk membuat aturan routing yang mengarahkan semua paket bertujuan subnet <code class="language-plaintext highlighter-rouge">192.168.10.0/24</code> ke <code class="language-plaintext highlighter-rouge">Peer</code> yang sesuai. Skenario ini akan kita bahas lebih detail pada artikel selanjutnya.</p>
<h2 id="pengetesan">Pengetesan</h2>
<p>Setelah kita mengkonfigurasi <code class="language-plaintext highlighter-rouge">[Interface]</code> dan <code class="language-plaintext highlighter-rouge">[Peer]</code> di masing-masing perangkat, kita bisa menguji konfigurasinya dengan mudah. Cukup lakukan ping ke alamat IP yang kita buat di konfigurasi WireGuard. Contohnya, bila kita di laptop MacOs, maka coba ping ke IP Ubuntu di <code class="language-plaintext highlighter-rouge">10.100.10.101</code>. Dan sebaliknya bila kita ada di laptop Ubuntu, coba ping ke laptop MacOs di <code class="language-plaintext highlighter-rouge">10.100.10.102</code>.</p>
<p>Untuk mengetes konfigurasi di perangkat Android, kita bisa menjalankan aplikasi web sederhana di laptop, kemudian mencoba browse ke aplikasi web tersebut. Misalnya, kita jalankan aplikasi web di laptop Ubuntu, kemudian browse ke <code class="language-plaintext highlighter-rouge">http://10.100.10.101</code> dari Android atau iPhone.</p>
<p>Demikianlah penjelasan awal mengenai VPN dengan WireGuard. Stay tuned ….
Artikel berikutnya akan membahas penggunaan VPN untuk membuat internet proxy.</p>
<p>Semoga bermanfaat …</p>
Kirim SMS OTP dengan Gammu2020-05-01T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/sms-otp-gammu<p>Project yang saat ini kita sedang tangani, memiliki fitur untuk otentikasi menggunakan OTP. Dikirim dengan SMS dan Email. Tentunya sebelum fitur OTP aktif, kita juga harus verifikasi dulu nomor handphone dan emailnya.</p>
<p>Fitur verifikasi dan OTP ini tentunya mengharuskan aplikasi kita untuk bisa mengirim email dan SMS. Cara untuk mengirim email sudah saya bahas pada <a href="https://software.endy.muhardin.com/java/mengirim-email-gmail-api/">artikel terdahulu</a>. Jadi pada artikel ini, kita akan membahas cara mengirim SMS dari kode program kita.</p>
<!--more-->
<h2 id="problem-dan-solusi">Problem dan Solusi</h2>
<p>Untuk skenario ini, ada beberapa problem yang kita temui, yaitu:</p>
<p>Problem pertama adalah bagaimana cara mengirim SMS dari aplikasi?</p>
<p>Ada beberapa alternatif yang bisa kita gunakan untuk mengirim SMS, diantaranya :</p>
<ol>
<li>Menggunakan sms provider. Kita mengirim http request ke server provider dengan URL dan format data yang ditentukan provider.</li>
<li>Menggunakan modem sms.</li>
</ol>
<p>Untuk keperluan development, kita seringkali belum mendapatkan kepastian provider SMS yang akan digunakan. Jadi kita terpaksa menggunakan opsi kedua.</p>
<p>Kedua, kita harus memikirkan cara bagaimana caranya supaya aplikasi yang kita hosting di cloud bisa terkoneksi dengan modem.</p>
<p>Solusi yang saya ambil di sini adalah menginstal modem sms di rumah, kemudian secara periodik mengecek ke server di cloud untuk mendapatkan data SMS yang harus dikirim.</p>
<p>Pada prakteknya, kita tidak akan mengirim SMS secara langsung dari kode program kita di aplikasi. Kita akan menyuruh aplikasi bernama <a href="https://wammu.eu/gammu/">Gammu</a> untuk melakukan pengiriman SMS. Aplikasi kita cukup menyiapkan nomor tujuan dan isi pesan. Selanjutnya, data tersebut kita format menjadi bentuk yang bisa dimengerti dan diproses oleh Gammu.</p>
<p>Gammu memiliki fitur <code class="language-plaintext highlighter-rouge">daemon</code> atau background service yang akan berjalan terus dan memproses SMS yang dikirim dan diterima, namanya <code class="language-plaintext highlighter-rouge">gammu-smsd</code>. Ada beberapa pilihan penyimpanan data SMS, diantaranya database relasional (MySQL) dan file. Untuk lebih mudahnya, kita akan menggunakan file.</p>
<p><code class="language-plaintext highlighter-rouge">Gammu-smsd</code> dengan metode penyimpanan file akan membuat dan memantau beberapa folder sebagai berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">/var/spool/gammu/inbox</code> : untuk menerima SMS</li>
<li><code class="language-plaintext highlighter-rouge">/var/spool/gammu/outbox</code> : antrian SMS yang ingin dikirim</li>
<li><code class="language-plaintext highlighter-rouge">/var/spool/gammu/sent</code> : data SMS yang sudah dikirim</li>
<li><code class="language-plaintext highlighter-rouge">/var/spool/gammu/error</code> : untuk SMS yang gagal terkirim</li>
</ul>
<p>Untuk mengirim SMS, kita perlu membuat file di folder <code class="language-plaintext highlighter-rouge">/var/spool/gammu/outbox</code> dengan aturan nama file <code class="language-plaintext highlighter-rouge">OUT08xxxx.txt</code>. Diawali <code class="language-plaintext highlighter-rouge">OUT</code>, diikuti nomor tujuan, dan diakhiri dengan ekstensi <code class="language-plaintext highlighter-rouge">.txt</code>. Isi file akan dijadikan isi SMS.</p>
<p>Dengan demikian, aplikasi kita cukup bisa membuat file dengan format tersebut.</p>
<h2 id="skema-deployment">Skema Deployment</h2>
<p>Modem yang kita pasang di rumah harus mengambil file yang dibuat aplikasi kita di server secara periodik. Untuk itu, saya menjalankan aplikasi <code class="language-plaintext highlighter-rouge">rsync</code> setiap satu menit untuk memindahkan file di server ke mesin di rumah yang terpasang modem. Ini dilakukan dengan menggunakan scheduler <code class="language-plaintext highlighter-rouge">cron</code> yang sudah tersedia di semua distro linux. Berikut adalah baris konfigurasi <code class="language-plaintext highlighter-rouge">cron</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* * * * * /usr/bin/rsync -r --remove-source-files root@server-aplikasi.artivisi.id:/var/lib/aplikasi-saya/smsout/ /var/spool/gammu/outbox/
</code></pre></div></div>
<p>Secara garis besar, skemanya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/otp-gammu/01-skema-sms-otp.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/otp-gammu/01-skema-sms-otp.jpg" alt="Skema OTP" /></a></p>
<h2 id="konfigurasi-gammu">Konfigurasi Gammu</h2>
<p>Di rumah saya, mesin yang bertugas menjalankan modem dan mengambil data dari server adalah Raspberry Pi. Seperti ini penampakannya</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/otp-gammu/02-raspi-modem.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/otp-gammu/02-raspi-modem.jpg" alt="Raspi Modem" /></a></p>
<p>Cara instalasi Raspberry sudah pernah saya tulis di <a href="https://software.endy.muhardin.com/linux/raspi-hardening/">artikel terdahulu</a>. Setelah Raspberry terinstal, kita perlu menginstal aplikasi <code class="language-plaintext highlighter-rouge">gammu-smsd</code> dan <code class="language-plaintext highlighter-rouge">wvdial</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install gammu-smsd wvdial -y
</code></pre></div></div>
<p>Kita membutuhkan dua nilai untuk diisikan di konfigurasi <code class="language-plaintext highlighter-rouge">gammu-smsd</code>, yaitu lokasi tempat modem kita terinstal dan kecepatan prosesnya. Nilai ini kita dapat dengan cara menjalankan deteksi konfigurasi dengan menggunakan aplikasi <code class="language-plaintext highlighter-rouge">wvdialconf</code>.</p>
<p>Berikut outputnya ketika kita jalankan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># wvdialconf
Editing `/etc/wvdial.conf'.
Scanning your serial ports for a modem.
WvModem<*1>: Cannot set information for serial port.
ttyUSB0<*1>: ATQ0 V1 E1 -- failed with 2400 baud, next try: 9600 baud
ttyUSB0<*1>: ATQ0 V1 E1 --
ttyUSB0<*1>: failed with 9600 baud, next try: 115200 baud
ttyUSB0<*1>: ATQ0 V1 E1 -- OK
ttyUSB0<*1>: ATQ0 V1 E1 Z -- OK
ttyUSB0<*1>: ATQ0 V1 E1 S0=0 -- OK
ttyUSB0<*1>: ATQ0 V1 E1 S0=0 &C1 -- OK
ttyUSB0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 -- OK
ttyUSB0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
ttyUSB0<*1>: Modem Identifier: ATI -- WAVECOM MODEM
ttyUSB0<*1>: Speed 230400: AT -- x?
ttyUSB0<*1>: Speed 230400: AT -- x?
ttyUSB0<*1>: Speed 230400: AT --
ttyUSB0<*1>: Max speed is 115200; that should be safe.
ttyUSB0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
Found a modem on /dev/ttyUSB0.
/etc/wvdial.conf<Warn>: Can't open '/etc/wvdial.conf' for reading: No such file or directory
/etc/wvdial.conf<Warn>: ...starting with blank configuration.
Modem configuration written to /etc/wvdial.conf.
ttyUSB0<Info>: Speed 115200; init "ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0"
</code></pre></div></div>
<p>Dari output tersebut kita mendapatkan lokasi modem kita, yaitu di <code class="language-plaintext highlighter-rouge">/dev/ttyUSB0</code> dengan kecepatan <code class="language-plaintext highlighter-rouge">115200</code>. Nilai tersebut kita pasang di konfigurasi <code class="language-plaintext highlighter-rouge">gammu-smsd</code> membutuhkan konfigurasi di file <code class="language-plaintext highlighter-rouge">/etc/gammu-smsdrc</code> yang isinya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Configuration file for Gammu SMS Daemon
# Gammu library configuration, see gammurc(5)
[gammu]
# Please configure this!
port = /dev/ttyUSB0
connection = at115200
# Debugging
logformat = textall
# SMSD configuration, see gammu-smsdrc(5)
[smsd]
service = files
logfile = syslog
# Increase for debugging information
debuglevel = 0
# Paths where messages are stored
inboxpath = /var/spool/gammu/inbox/
outboxpath = /var/spool/gammu/outbox/
sentsmspath = /var/spool/gammu/sent/
errorsmspath = /var/spool/gammu/error/
</code></pre></div></div>
<p>Kalau sudah dibuat, maka kita bisa jalankan aplikasi <code class="language-plaintext highlighter-rouge">gammu-smsd</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl enable gammu-smsd
systemctl start gammu-smsd
</code></pre></div></div>
<p>Test reboot, kita cek apakah gammunya otomatis start ketika boot. Setelah raspberry menyala kembali, kita cek apakah <code class="language-plaintext highlighter-rouge">gammu-smsd</code> aktif dengan perintah <code class="language-plaintext highlighter-rouge">ps aux | grep gammu</code>.</p>
<p>Harusnya outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root 396 0.1 0.3 29316 3360 ? Ss 05:58 0:00 /usr/bin/gammu-smsd --pid=/var/run/gammu-smsd.pid --daemon
root 637 0.0 0.0 7352 508 pts/0 S+ 06:01 0:00 grep gammu
</code></pre></div></div>
<h2 id="pengetesan">Pengetesan</h2>
<p>Kita perlu mengetes sistem secara keseluruhan untuk memastikan flownya berjalan dengan baik. Cara mengetesnya sederhana, bahkan tidak perlu menunggu aplikasinya selesai. Kita cukup mengisi file ke <code class="language-plaintext highlighter-rouge">server-aplikasi.artivisi.id</code> di folder <code class="language-plaintext highlighter-rouge">/var/lib/aplikasi-saya/smsout/</code> dengan nama file <code class="language-plaintext highlighter-rouge">OUT081234567890.txt</code>. File bisa diisi tulisan sembarang, misalnya <code class="language-plaintext highlighter-rouge">test kirim via rsync</code>. Biasanya saya edit dulu di laptop, kemudian upload ke server menggunakan <code class="language-plaintext highlighter-rouge">scp</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp OUT081234567890.txt root@server-aplikasi.artivisi.id:/var/lib/aplikasi-saya/smsout/
</code></pre></div></div>
<p>Kemudian tunggu beberapa menit, harusnya kita akan mendapatkan sms di nomor <code class="language-plaintext highlighter-rouge">081234567890</code> seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/otp-gammu/03-test-kirim-sms.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2020/otp-gammu/03-test-kirim-sms.jpg" alt="Test Kirim SMS" /></a></p>
<h2 id="epilog">Epilog</h2>
<p>Demikian setup gammu agar aplikasi kita bisa kirim SMS. Pesan moral dari artikel ini sebenarnya adalah</p>
<blockquote>
<blockquote>
<p>Tidak semua masalah harus diselesaikan dengan coding. Seringkali kita cuma perlu menggunakan dan merangkai aplikasi yang sudah ada.</p>
</blockquote>
</blockquote>
<p>Dan satu lagi …</p>
<blockquote>
<blockquote>
<p>Walaupun profesi utama kita adalah programmer, tapi skill linux dan jaringan merupakan hal yang wajib kita kuasai.</p>
</blockquote>
</blockquote>
<p>Semoga bermanfaat :D</p>
2 Mahzab Founder Startup2020-04-20T16:00:00+07:00https://software.endy.muhardin.com/manajemen/2-mahzab-founder-startup<p>Dari sejak jaman kolonial dulu sampe milenial sekarang, bikin bisnis itu ada 2 mahzab:</p>
<p>Mahzab A. Bikin dari nol, besarkan sampai puluhan tahun. Ngomongnya cashflow, profitability, ROA, ROE. Retensi founder dan karyawan biasanya tinggi, turnover rendah. Orientasinya pertumbuhan organik. Yang disebut sukses adalah perusahaan jalan sustainable, founder tetap pegang saham, karyawan makmur sejahtera.</p>
<p>Mahzab B. Bikin seminim mungkin, pitching setinggi mungkin. Ngomongnya elevator pitch, valuation, growth hacking, burn rate, term sheet. Retensi karyawan biasanya rendah, turnover tinggi, bahkan founder pun pada akhirnya entah kemana. Orientasinya funding series. Yang disebut sukses adalah perusahaan terjual, entah ke bigger company, atau IPO.</p>
<p>Nah, Anda mau jadi founder pakai mahzab mana??</p>
<p>Founder mahzab A biasanya :</p>
<ul>
<li>Kalau bisnisnya rugi dan susah diperbaiki, dijual</li>
<li>Bahagia kalau berhasil mengakuisisi</li>
</ul>
<p>Founder mahzab B biasanya :</p>
<ul>
<li>Kalau bisnisnya untung, dijual</li>
<li>Bahagia kalau berhasil diakuisisi</li>
</ul>
<p>Contoh founder mahzab A :</p>
<ul>
<li>Warren Buffet</li>
<li>Bill Gates</li>
</ul>
<p>Contoh founder mahzab B :</p>
<ul>
<li>Elon Musk</li>
<li>Jan Koum</li>
</ul>
<p>Ada yang berpendapat bahwa Musk masuk mahzab A. Tapi menurut pendapat saya, kalau kita lihat track recordnya di Wikipedia, perusahaan-perusahaan dia masih termasuk kategori bakar-bakar duit. Sedangkan perusahaan bikinan dia yang sudah mapan, yaitu PayPal, sudah lama dijual ke eBay. Jadi ya tentu saja dia masuk kategori B.</p>
<p>Mahzab mana yang lebih benar? Wallahu a’lam. Tidak harus dicari siapa benar siapa salah. Tidak juga cuma boleh pilih salah satu. Bikin banyak perusahaan, sebagian dijalankan dengan mahzab A dan sebagian B juga bebas saja :D</p>
Switch User dengan Spring Security2020-03-15T17:00:00+07:00https://software.endy.muhardin.com/java/switch-user-spring-security<p>Alhamdulillah, aplikasi kita sudah naik production, dan digunakan oleh banyak user. Aplikasi sudah kita rancang dengan baik, sehingga data yang tampil di aplikasi sesuai dengan user yang sedang login.</p>
<p>Masalah timbul ketika ada pertanyaan atau laporan dari user dalam penggunaan aplikasi. Sebagai administrator, kita tidak tahu data apa yang sedang tampil di layar user, karena aplikasi hanya bisa menampilkan data user yang sedang login. Apabila kita login dengan akun administrator, kita tidak bisa melihat apa yang dilihat user, sehingga kita sulit untuk mendebug aplikasi.</p>
<p>Biasanya, orang-orang mengatasi isu ini dengan meminta password user. Programmer/administrator kemudian mencoba aplikasi dengan cara login sebagai user. Praktek seperti ini sangat tidak dianjurkan. Pertama karena programmer/administrator menjadi tahu password user. Mayoritas user di dunia, menggunakan password yang sama di berbagai aplikasi, sehingga password yang diberikan ke programmer/administrator ini ada kemungkinan bisa dipakai di akunnya yang lain (internet banking, online shop, dan sebagainya). Kedua, karena ini belum tentu bisa digunakan pada sistem single sign on, seperti yang kita sudah pernah buat <a href="https://software.endy.muhardin.com/java/spring-boot-google-sso/">di artikel terdahulu</a>.</p>
<p>Solusinya, kita harus buatkan di aplikasi kita fitur untuk administrator supaya dia bisa pindah menjadi user lain. Dengan demikian, dia bisa melihat apa yang dilihat user lain tersebut. Tentunya fitur ini harus dijaga dengan baik, jangan sampai disalahgunakan oleh administrator. Caranya bisa dengan mengimplementasikan audit log untuk mencatat aktifitas administrator selama pindah menjadi user lain.</p>
<p>Nah, demikian konsepnya secara garis besar, sekarang mari kita langsung ke kode program.</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#studi-kasus" id="markdown-toc-studi-kasus">Studi Kasus</a></li>
<li><a href="#skema-database" id="markdown-toc-skema-database">Skema Database</a></li>
<li><a href="#starter-project-spring" id="markdown-toc-starter-project-spring">Starter Project Spring</a></li>
<li><a href="#menampilkan-daftar-transaksi" id="markdown-toc-menampilkan-daftar-transaksi">Menampilkan Daftar Transaksi</a></li>
<li><a href="#header-aplikasi" id="markdown-toc-header-aplikasi">Header Aplikasi</a></li>
<li><a href="#konfigurasi-security" id="markdown-toc-konfigurasi-security">Konfigurasi Security</a></li>
<li><a href="#mekanisme-switch-user" id="markdown-toc-mekanisme-switch-user">Mekanisme Switch User</a></li>
<li><a href="#konfigurasi-switch-user-filter" id="markdown-toc-konfigurasi-switch-user-filter">Konfigurasi Switch User Filter</a></li>
<li><a href="#pengetesan-switch-user" id="markdown-toc-pengetesan-switch-user">Pengetesan Switch User</a></li>
<li><a href="#implementasi-audit-log" id="markdown-toc-implementasi-audit-log">Implementasi Audit Log</a></li>
<li><a href="#penutup" id="markdown-toc-penutup">Penutup</a></li>
</ul>
<h2 id="studi-kasus">Studi Kasus</h2>
<p>Seperti biasanya, untuk memudahkan kita belajar pemrograman, kita karang studi kasus yang membutuhkan fitur yang ingin kita pelajari. Studi kasus kita kali ini adalah aplikasi yang menampilkan riwayat transaksi di rekening pengguna. Pengguna bisa login, kemudian melihat history transaksi yang dia lakukan. Agar tidak terlalu rumit, kita tidak membuat fitur input transaksi. Datanya langsung saja kita sediakan dari database.</p>
<p>Kita juga membuat audit log untuk merekam kegiatan administrator selama dia menggunakan user orang lain, agar ada akuntabilitas terhadap tindakan dia.</p>
<h2 id="skema-database">Skema Database</h2>
<p>Berikut adalah skema database untuk aplikasi ini.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">s_role</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">nama</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">unique</span> <span class="p">(</span><span class="n">nama</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">pengguna</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">id_role</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">username</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">hashed_password</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">nama</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">unique</span> <span class="p">(</span><span class="n">username</span><span class="p">),</span>
<span class="k">foreign</span> <span class="k">key</span> <span class="p">(</span><span class="n">id_role</span><span class="p">)</span> <span class="k">references</span> <span class="n">s_role</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">transaksi</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">id_pengguna</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">waktu_transaksi</span> <span class="nb">datetime</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">keterangan</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">nilai</span> <span class="nb">decimal</span><span class="p">(</span><span class="mi">19</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">foreign</span> <span class="k">key</span> <span class="p">(</span><span class="n">id_pengguna</span><span class="p">)</span> <span class="k">references</span> <span class="n">pengguna</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">audit_log</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">id_pengguna_asli</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">id_pengguna_dipakai</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">waktu_kegiatan</span> <span class="nb">datetime</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">keterangan</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">foreign</span> <span class="k">key</span> <span class="p">(</span><span class="n">id_pengguna_asli</span><span class="p">)</span> <span class="k">references</span> <span class="n">pengguna</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">foreign</span> <span class="k">key</span> <span class="p">(</span><span class="n">id_pengguna_dipakai</span><span class="p">)</span> <span class="k">references</span> <span class="n">pengguna</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Berikut adalah sampel data untuk mengisi tabel-tabel di atas.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">s_role</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">nama</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="s1">'admin'</span><span class="p">,</span> <span class="s1">'Administrator'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">s_role</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">nama</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="s1">'pengguna'</span><span class="p">,</span> <span class="s1">'Pengguna'</span><span class="p">);</span>
<span class="c1">-- password : admin123</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">pengguna</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">id_role</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">hashed_password</span><span class="p">,</span> <span class="n">nama</span><span class="p">)</span>
<span class="k">values</span> <span class="p">(</span><span class="s1">'adminuser'</span><span class="p">,</span> <span class="s1">'admin'</span><span class="p">,</span> <span class="s1">'admin'</span><span class="p">,</span> <span class="s1">'$2a$13$i2JE6bqZ1YghFX2RZHUE0O0H4bYeiB.h4zsgiaJ8tg.ejHtdPJ5XW'</span><span class="p">,</span> <span class="s1">'Administrator'</span><span class="p">);</span>
<span class="c1">-- password : user00</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">pengguna</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">id_role</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">hashed_password</span><span class="p">,</span> <span class="n">nama</span><span class="p">)</span>
<span class="k">values</span> <span class="p">(</span><span class="s1">'u001'</span><span class="p">,</span> <span class="s1">'pengguna'</span><span class="p">,</span> <span class="s1">'user001'</span><span class="p">,</span> <span class="s1">'$2a$13$UHzktQVUWnzI46FqJBMVgunMPKWaxsvuKdHw5LWWsczDCvf.CtoQu'</span><span class="p">,</span> <span class="s1">'User 001'</span><span class="p">);</span>
<span class="c1">-- password : user00</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">pengguna</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">id_role</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">hashed_password</span><span class="p">,</span> <span class="n">nama</span><span class="p">)</span>
<span class="k">values</span> <span class="p">(</span><span class="s1">'u002'</span><span class="p">,</span> <span class="s1">'pengguna'</span><span class="p">,</span> <span class="s1">'user002'</span><span class="p">,</span> <span class="s1">'$2a$13$UHzktQVUWnzI46FqJBMVgunMPKWaxsvuKdHw5LWWsczDCvf.CtoQu'</span><span class="p">,</span> <span class="s1">'User 002'</span><span class="p">);</span>
<span class="c1">-- password : user00</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">pengguna</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">id_role</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">hashed_password</span><span class="p">,</span> <span class="n">nama</span><span class="p">)</span>
<span class="k">values</span> <span class="p">(</span><span class="s1">'u003'</span><span class="p">,</span> <span class="s1">'pengguna'</span><span class="p">,</span> <span class="s1">'user003'</span><span class="p">,</span> <span class="s1">'$2a$13$UHzktQVUWnzI46FqJBMVgunMPKWaxsvuKdHw5LWWsczDCvf.CtoQu'</span><span class="p">,</span> <span class="s1">'User 003'</span><span class="p">);</span>
</code></pre></div></div>
<p>Sedangkan untuk transaksinya, kita akan input secara random menggunakan library faker.</p>
<h2 id="starter-project-spring">Starter Project Spring</h2>
<p>Project baru kita buat menggunakan <a href="https://start.spring.io">Spring Initialzr</a> dengan mengaktifkan modul-modul berikut:</p>
<ul>
<li>Spring Web</li>
<li>Spring Security</li>
<li>Thymeleaf</li>
<li>Lombok</li>
<li>Spring Data JPA</li>
<li>Flyway Migration</li>
<li>H2 Database</li>
</ul>
<p>Atau bisa langsung digenerate dengan <a href="https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.2.5.RELEASE&packaging=jar&jvmVersion=1.8&groupId=com.muhardin.endy.belajar.switchuser&artifactId=belajar-switchuser&name=belajar-switchuser&description=Demo%20project%20for%20Spring%20Boot&packageName=com.muhardin.endy.belajar.switchuser.belajar-switchuser&dependencies=web,thymeleaf,lombok,data-jpa,flyway,h2">klik link ini</a></p>
<p>Nantinya sepanjang perjalanan, kita akan menambahkan beberapa library tambahan untuk memudahkan hidup kita, yaitu:</p>
<ul>
<li><a href="https://ultraq.github.io/thymeleaf-layout-dialect/">Thymeleaf Layout Dialect</a></li>
<li><a href="https://github.com/thymeleaf/thymeleaf-extras-springsecurity">Thymeleaf Spring Security Dialect</a></li>
<li><a href="https://github.com/DiUS/java-faker">Java Faker</a></li>
</ul>
<h2 id="menampilkan-daftar-transaksi">Menampilkan Daftar Transaksi</h2>
<p>Ini adalah satu-satunya fitur di aplikasi kita. Berikut adalah controller untuk menampilkannya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Controller</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TransaksiController</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">TransaksiDao</span> <span class="n">transaksiDao</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">PenggunaDao</span> <span class="n">penggunaDao</span><span class="o">;</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/transaksi/list"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ModelMap</span> <span class="nf">daftarTransaksi</span><span class="o">(</span><span class="nc">Authentication</span> <span class="n">currentUser</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ModelMap</span> <span class="n">mm</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ModelMap</span><span class="o">();</span>
<span class="n">penggunaDao</span><span class="o">.</span><span class="na">findByUsername</span><span class="o">(</span><span class="n">currentUser</span><span class="o">.</span><span class="na">getName</span><span class="o">())</span>
<span class="o">.</span><span class="na">ifPresent</span><span class="o">(</span><span class="n">p</span><span class="o">->{</span>
<span class="n">mm</span><span class="o">.</span><span class="na">addAttribute</span><span class="o">(</span>
<span class="s">"daftarTransaksi"</span><span class="o">,</span>
<span class="n">transaksiDao</span><span class="o">.</span><span class="na">findByPengguna</span><span class="o">(</span><span class="n">p</span><span class="o">));</span>
<span class="o">});</span>
<span class="k">return</span> <span class="n">mm</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada kode program di atas, kita lihat bahwa ada argumen method bertipe <code class="language-plaintext highlighter-rouge">Authentication</code>. Argumen ini akan diisi oleh Spring Boot pada waktu aplikasi dijalankan dengan object berisi data user yang sedang login. Kita gunakan method <code class="language-plaintext highlighter-rouge">getName</code> untuk mendapatkan usernamenya. Kemudian kita cari data <code class="language-plaintext highlighter-rouge">Pengguna</code> dari database berdasarkan username tersebut. Terakhir, kita lakukan query untuk mencari transaksi yang dimiliki pengguna tersebut.</p>
<p>Dan ini adalah template HTML untuk menampilkan data transaksi.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><h1></span>Daftar Transaksi<span class="nt"></h1></span>
<span class="nt"><table</span> <span class="na">class=</span><span class="s">"table table-striped"</span><span class="nt">></span>
<span class="nt"><thead></span>
<span class="nt"><tr></span>
<span class="nt"><th></span>Waktu Transaksi<span class="nt"></th></span>
<span class="nt"><th></span>Pengguna<span class="nt"></th></span>
<span class="nt"><th></span>Keterangan<span class="nt"></th></span>
<span class="nt"><th></span>Nilai<span class="nt"></th></span>
<span class="nt"></tr></span>
<span class="nt"></thead></span>
<span class="nt"><tbody></span>
<span class="nt"><tr</span> <span class="na">th:each=</span><span class="s">"t : ${daftarTransaksi}"</span><span class="nt">></span>
<span class="nt"><td</span> <span class="na">th:text=</span><span class="s">"${t.waktuTransaksi}"</span><span class="nt">></span>12-Mar-2020 20:20:11<span class="nt"></td></span>
<span class="nt"><td</span> <span class="na">th:text=</span><span class="s">"${t.pengguna.nama}"</span><span class="nt">></span>Pengguna 1<span class="nt"></td></span>
<span class="nt"><td</span> <span class="na">th:text=</span><span class="s">"${t.keterangan}"</span><span class="nt">></span>Setoran Tunai<span class="nt"></td></span>
<span class="nt"><td</span> <span class="na">th:text=</span><span class="s">"${t.nilai}"</span><span class="nt">></span>100.000<span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="nt"></tbody></span>
<span class="nt"></table></span>
</code></pre></div></div>
<p>Tidak ada yang istimewa di sini, hanya query dari database, kemudian render di HTML.</p>
<h2 id="header-aplikasi">Header Aplikasi</h2>
<p>Ada beberapa hal yang kita pasang di header aplikasi, yaitu:</p>
<ul>
<li>Link ke halaman Daftar Transaksi</li>
<li>Tulisan nama user yang sedang login</li>
<li>Tombol Logout</li>
<li>Link ke halaman untuk Ganti User. Link ini hanya boleh terlihat oleh admin</li>
<li>Link untuk menampilkan Audit Log. Ini juga hanya boleh dilihat admin</li>
<li>Link untuk kembali menjadi admin. Link ini hanya tampil ketika admin berubah user</li>
</ul>
<p>Kode HTMLnya seperti ini</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><nav</span> <span class="na">class=</span><span class="s">"my-2 my-md-0 mr-md-3"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">class=</span><span class="s">"p-2 text-dark"</span> <span class="na">th:href=</span><span class="s">"@{/transaksi/list}"</span><span class="nt">></span>Daftar Transaksi<span class="nt"></a></span>
<span class="ni">&nbsp;</span> | <span class="ni">&nbsp;</span>
<span class="nt"><a</span> <span class="na">sec:authorize=</span><span class="s">"hasAuthority('Administrator')"</span>
<span class="na">class=</span><span class="s">"p-2 text-dark"</span> <span class="na">th:href=</span><span class="s">"@{/switchuser/select}"</span><span class="nt">></span>Ganti User<span class="nt"></a></span>
<span class="ni">&nbsp;</span> | <span class="ni">&nbsp;</span>
<span class="nt"><a</span> <span class="na">sec:authorize=</span><span class="s">"hasAuthority('Administrator')"</span>
<span class="na">class=</span><span class="s">"p-2 text-dark"</span> <span class="na">th:href=</span><span class="s">"@{/switchuser/auditlog}"</span><span class="nt">></span>Audit Log<span class="nt"></a></span>
<span class="ni">&nbsp;</span> | <span class="ni">&nbsp;</span>
<span class="nt"><a</span> <span class="na">sec:authorize=</span><span class="s">"hasRole('ROLE_PREVIOUS_ADMINISTRATOR')"</span>
<span class="na">class=</span><span class="s">"p-2 text-dark"</span> <span class="na">th:href=</span><span class="s">"@{/switchuser/exit}"</span><span class="nt">></span>Kembali ke Admin<span class="nt"></a></span>
<span class="nt"></nav></span>
| <span class="ni">&nbsp;</span> Hello, <span class="nt"><span</span> <span class="na">th:text=</span><span class="s">"${#authentication.name}"</span><span class="nt">></span>current user<span class="nt"></span></span>
<span class="ni">&nbsp;</span> <span class="ni">&nbsp;</span> <span class="ni">&nbsp;</span>
<span class="nt"><form</span> <span class="na">th:action=</span><span class="s">"@{/logout}"</span> <span class="na">method=</span><span class="s">"post"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">class=</span><span class="s">"btn btn-outline-primary"</span> <span class="na">value=</span><span class="s">"Logout"</span> <span class="nt">/></span>
<span class="nt"></form></span>
</code></pre></div></div>
<p>Kita menggunakan atribut <code class="language-plaintext highlighter-rouge">sec:authorize</code> dan variabel <code class="language-plaintext highlighter-rouge">${#authentication.name}</code>. Ini disediakan oleh library Thymeleaf Spring Security Dialect.</p>
<h2 id="konfigurasi-security">Konfigurasi Security</h2>
<p>Berikut adalah konfigurasi security kita. Data user dan permission/authority kita simpan di database. Oleh karena itu, kita mengkonfigurasikan query untuk login dan menentukan permission seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span> <span class="nd">@EnableWebSecurity</span>
<span class="nd">@EnableGlobalMethodSecurity</span><span class="o">(</span><span class="n">prePostEnabled</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">KonfigurasiSecurity</span> <span class="kd">extends</span> <span class="nc">WebSecurityConfigurerAdapter</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_LOGIN</span>
<span class="o">=</span> <span class="s">"select p.username, p.hashed_password as password, true as enabled "</span> <span class="o">+</span>
<span class="s">"from pengguna p where p.username = ?"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_ROLE</span>
<span class="o">=</span> <span class="s">"select p.username, r.nama as authority from s_role r "</span> <span class="o">+</span>
<span class="s">"inner join pengguna p on p.id_role = r.id "</span>
<span class="o">+</span> <span class="s">"where p.username = ?"</span><span class="o">;</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">PasswordEncoder</span> <span class="nf">passwordEncoder</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">BCryptPasswordEncoder</span><span class="o">(</span><span class="mi">13</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Autowired</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">configureGlobal</span><span class="o">(</span><span class="nc">AuthenticationManagerBuilder</span> <span class="n">auth</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">auth</span>
<span class="o">.</span><span class="na">jdbcAuthentication</span><span class="o">()</span>
<span class="o">.</span><span class="na">dataSource</span><span class="o">(</span><span class="n">dataSource</span><span class="o">)</span>
<span class="o">.</span><span class="na">usersByUsernameQuery</span><span class="o">(</span><span class="no">SQL_LOGIN</span><span class="o">)</span>
<span class="o">.</span><span class="na">authoritiesByUsernameQuery</span><span class="o">(</span><span class="no">SQL_ROLE</span><span class="o">)</span>
<span class="o">.</span><span class="na">passwordEncoder</span><span class="o">(</span><span class="n">passwordEncoder</span><span class="o">());</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span><span class="o">.</span><span class="na">authorizeRequests</span><span class="o">(</span><span class="n">authorize</span> <span class="o">-></span> <span class="n">authorize</span>
<span class="o">.</span><span class="na">anyRequest</span><span class="o">().</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">)</span>
<span class="o">.</span><span class="na">logout</span><span class="o">().</span><span class="na">permitAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">().</span><span class="na">formLogin</span><span class="o">()</span>
<span class="o">.</span><span class="na">defaultSuccessUrl</span><span class="o">(</span><span class="s">"/transaksi/list"</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">WebSecurity</span> <span class="n">web</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">web</span><span class="o">.</span><span class="na">ignoring</span><span class="o">()</span>
<span class="o">.</span><span class="na">antMatchers</span><span class="o">(</span><span class="s">"/js/*"</span><span class="o">)</span>
<span class="o">.</span><span class="na">antMatchers</span><span class="o">(</span><span class="s">"/img/*"</span><span class="o">)</span>
<span class="o">.</span><span class="na">antMatchers</span><span class="o">(</span><span class="s">"/css/*"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Konfigurasi tersebut belum mencakup tambahan konfigurasi untuk mengaktifkan switch user. Dengan konfigurasi di atas, aplikasi kita sudah bisa login dan melihat data transaksi untuk user yang sedang login.</p>
<h2 id="mekanisme-switch-user">Mekanisme Switch User</h2>
<p>Setelah kita sukses melakukan login, Spring Security akan menyimpan object <code class="language-plaintext highlighter-rouge">Authentication</code> dalam http session. Object ini berisi data user yang sedang login, diantaranya username dan daftar permission yang dia miliki. Pada waktu kita melakukan switch user, maka Spring Security akan mengganti object tersebut dengan object <code class="language-plaintext highlighter-rouge">Authentication</code> baru yang berisi data user yang ingin kita gantikan berikut permissionnya.</p>
<p>Untuk memudahkan kita kembali ke user semula, maka data user yang asli akan dibungkus dalam object <code class="language-plaintext highlighter-rouge">Authentication</code> yang baru itu. Spring Security juga menambahkan authority dengan nama <code class="language-plaintext highlighter-rouge">ROLE_PREVIOUS_ADMINISTRATOR</code> untuk menandai bahwa object <code class="language-plaintext highlighter-rouge">Authentication</code> yang sedang ada dalam session merupakan hasil switch user.</p>
<p>Object yang asli, sebelum ditukar, bentuknya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.springframework.security.authentication.UsernamePasswordAuthenticationToken@f2c7bd42: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Administrator; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: BBECDE2D51218EB2F707B9F416A58A22; Granted Authorities: Administrator
</code></pre></div></div>
<p>Sedangkan setelah dilakukan switch user, bentuknya menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.springframework.security.authentication.UsernamePasswordAuthenticationToken@f301ed8d: Principal: org.springframework.security.core.userdetails.User@f73a3687: Username: user002; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Pengguna; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 5651E3798C6B9F49944B4605E50EFB18; Granted Authorities: Pengguna, Switch User Authority [ROLE_PREVIOUS_ADMINISTRATOR,org.springframework.security.authentication.UsernamePasswordAuthenticationToken@f2c7bd42: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Administrator; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: BBECDE2D51218EB2F707B9F416A58A22; Granted Authorities: Administrator]
</code></pre></div></div>
<p>Pertukaran ini dilakukan dengan cara melakukan request <code class="language-plaintext highlighter-rouge">GET</code> atau <code class="language-plaintext highlighter-rouge">POST</code> ke url yang kita tentukan dengan membawa parameter <code class="language-plaintext highlighter-rouge">username</code> yang berisi username yang ingin kita gantikan. Karena tidak ada pengecekan lebih lanjut, maka kita harus pastikan url ganti user ini harus kita amankan dengan ijin akses yang memadai. Pada artikel ini, url ganti user hanya boleh diakses oleh user dengan authority <code class="language-plaintext highlighter-rouge">Administrator</code>.</p>
<p>URL ganti user kita konfigurasi menggunakan class <code class="language-plaintext highlighter-rouge">SwitchUserFilter</code> yang sudah disediakan oleh Spring Security.</p>
<h2 id="konfigurasi-switch-user-filter">Konfigurasi Switch User Filter</h2>
<p>Berikut adalah kode program untuk menginstankan <code class="language-plaintext highlighter-rouge">SwitchUserFilter</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">SwitchUserFilter</span> <span class="nf">switchUserFilter</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">SwitchUserFilter</span> <span class="n">filter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SwitchUserFilter</span><span class="o">();</span>
<span class="n">filter</span><span class="o">.</span><span class="na">setUserDetailsService</span><span class="o">(</span><span class="n">userDetailsService</span><span class="o">());</span>
<span class="n">filter</span><span class="o">.</span><span class="na">setSwitchUserUrl</span><span class="o">(</span><span class="s">"/switchuser/form"</span><span class="o">);</span>
<span class="n">filter</span><span class="o">.</span><span class="na">setExitUserUrl</span><span class="o">(</span><span class="s">"/switchuser/exit"</span><span class="o">);</span>
<span class="n">filter</span><span class="o">.</span><span class="na">setTargetUrl</span><span class="o">(</span><span class="s">"/transaksi/list"</span><span class="o">);</span>
<span class="k">return</span> <span class="n">filter</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Ada beberapa hal yang kita konfigurasikan:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">SwitchUserUrl</code> : adalah url yang akan dihit untuk mengganti user. URL ini menangkap method <code class="language-plaintext highlighter-rouge">GET</code> dan <code class="language-plaintext highlighter-rouge">POST</code>, jadi kita bisa pilih mana yang mau dipakai. Saya pribadi memilih menggunakan <code class="language-plaintext highlighter-rouge">GET</code>. Berikut form switch usernya</li>
</ul>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><form</span> <span class="na">method=</span><span class="s">"post"</span> <span class="na">th:action=</span><span class="s">"@{/switchuser/form}"</span> <span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"form-group row"</span><span class="nt">></span>
<span class="nt"><label</span> <span class="na">for=</span><span class="s">"pengguna"</span> <span class="na">class=</span><span class="s">"col-sm-2 col-form-label"</span><span class="nt">></span>Pengguna<span class="nt"></label></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"col-sm-10"</span><span class="nt">></span>
<span class="nt"><select</span> <span class="na">class=</span><span class="s">"form-control"</span> <span class="na">id=</span><span class="s">"pengguna"</span> <span class="na">name=</span><span class="s">"username"</span><span class="nt">></span>
<span class="nt"><option></span>Pilih Pengguna<span class="nt"></option></span>
<span class="nt"><option</span> <span class="na">th:each=</span><span class="s">"p : ${daftarPengguna}"</span>
<span class="na">th:text=</span><span class="s">"${p.nama}"</span>
<span class="na">th:value=</span><span class="s">"${p.username}"</span><span class="nt">></span>2<span class="nt"></option></span>
<span class="nt"></select></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"form-group row"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"col-sm-10"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span><span class="nt">></span>Switch User<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></form></span>
</code></pre></div></div>
<p>Perhatikan bahwa <code class="language-plaintext highlighter-rouge">action</code> di form HTML diarahkan ke properti <code class="language-plaintext highlighter-rouge">SwitchUserUrl</code> di <code class="language-plaintext highlighter-rouge">SwitchUserFilter</code> yang kita instankan sebelumnya.</p>
<p>Selain itu, pastikan juga bahwa nama request parameter yang dikirim pada waktu submit form namanya adalah <code class="language-plaintext highlighter-rouge">username</code>. Bisa dilihat pada komponen <code class="language-plaintext highlighter-rouge">select</code> di atas. Value yang dikirim diambil dari username yang dipasang di dropdown/combo pilihan pengguna.</p>
<p>Filter tersebut harus kita daftarkan supaya aktif. Urutannya ditaruh paling bawah. Kode programnya seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="na">addFilterAfter</span><span class="o">(</span><span class="n">switchUserFilter</span><span class="o">(),</span> <span class="nc">FilterSecurityInterceptor</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
</code></pre></div></div>
<p>Jangan lupa kita atur permission urlnya sehingga cuma bisa diakses oleh orang yang berhak. Kode programnya seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="na">mvcMatchers</span><span class="o">(</span><span class="s">"/switchuser/exit"</span><span class="o">)</span>
<span class="o">.</span><span class="na">hasAuthority</span><span class="o">(</span><span class="nc">SwitchUserFilter</span><span class="o">.</span><span class="na">ROLE_PREVIOUS_ADMINISTRATOR</span><span class="o">)</span>
<span class="o">.</span><span class="na">mvcMatchers</span><span class="o">(</span><span class="s">"/switchuser/select"</span><span class="o">,</span> <span class="s">"/switchuser/form"</span><span class="o">)</span>
<span class="o">.</span><span class="na">hasAuthority</span><span class="o">(</span><span class="s">"Administrator"</span><span class="o">)</span>
</code></pre></div></div>
<p>Ada tiga URL yang harus kita proteksi:</p>
<ul>
<li>URL untuk menampilkan form pilihan user yang ingin digantikan. URLnya adalah <code class="language-plaintext highlighter-rouge">/switchuser/select</code>, hanya bisa diakses <code class="language-plaintext highlighter-rouge">Administrator</code></li>
<li>URL untuk memproses penggantian user. Pada contoh kita, URLnya adalah <code class="language-plaintext highlighter-rouge">/switchuser/form</code>, hanya bisa diakses <code class="language-plaintext highlighter-rouge">Administrator</code></li>
<li>URL untuk kembali ke user asli. Pada contoh kita, urlnya adalah <code class="language-plaintext highlighter-rouge">/switchuser/exit</code>, hanya bisa diakses oleh yang sedang melakukan switch user.</li>
</ul>
<p>Berikut satu method lengkap, supaya lebih mudah memahami di mana baris kodenya ditaruh.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span><span class="o">.</span><span class="na">authorizeRequests</span><span class="o">(</span><span class="n">authorize</span> <span class="o">-></span> <span class="n">authorize</span>
<span class="o">.</span><span class="na">mvcMatchers</span><span class="o">(</span><span class="s">"/switchuser/exit"</span><span class="o">)</span>
<span class="o">.</span><span class="na">hasAuthority</span><span class="o">(</span><span class="nc">SwitchUserFilter</span><span class="o">.</span><span class="na">ROLE_PREVIOUS_ADMINISTRATOR</span><span class="o">)</span>
<span class="o">.</span><span class="na">mvcMatchers</span><span class="o">(</span><span class="s">"/switchuser/select"</span><span class="o">,</span> <span class="s">"/switchuser/form"</span><span class="o">)</span>
<span class="o">.</span><span class="na">hasAuthority</span><span class="o">(</span><span class="s">"Administrator"</span><span class="o">)</span>
<span class="o">.</span><span class="na">anyRequest</span><span class="o">().</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">)</span>
<span class="o">.</span><span class="na">addFilterAfter</span><span class="o">(</span><span class="n">switchUserFilter</span><span class="o">(),</span> <span class="nc">FilterSecurityInterceptor</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="o">.</span><span class="na">logout</span><span class="o">().</span><span class="na">permitAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">().</span><span class="na">formLogin</span><span class="o">()</span>
<span class="o">.</span><span class="na">defaultSuccessUrl</span><span class="o">(</span><span class="s">"/transaksi/list"</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="pengetesan-switch-user">Pengetesan Switch User</h2>
<p>Untuk mengetes apakah switch user sudah jalan atau belum, kita bisa jalankan aplikasi seperti biasa, menggunakan perintah <code class="language-plaintext highlighter-rouge">mvn spring-boot:run</code>. Setelah aplikasi aktif, kita akses ke <a href="http://localhost:8080">http://localhost:8080</a>.</p>
<p>Kita akan dihadapkan pada halaman login.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/switch-user/01-login-screen.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/switch-user/01-login-screen.png" alt="Screen Login" /></a></p>
<p>Ada beberapa login yang bisa dipilih:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">admin</code> dengan password <code class="language-plaintext highlighter-rouge">admin123</code></li>
<li><code class="language-plaintext highlighter-rouge">user001</code> dengan password <code class="language-plaintext highlighter-rouge">user00</code></li>
<li><code class="language-plaintext highlighter-rouge">user002</code> dengan password <code class="language-plaintext highlighter-rouge">user00</code></li>
<li><code class="language-plaintext highlighter-rouge">user003</code> dengan password <code class="language-plaintext highlighter-rouge">user00</code></li>
</ul>
<p>Bila kita login dengan user <code class="language-plaintext highlighter-rouge">admin</code> maka kita akan melihat menu di kanan atas seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/switch-user/02-menu-admin.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/switch-user/02-menu-admin.png" alt="Menu Admin" /></a></p>
<p>Link tersebut tidak kita dapati bila login dengan user yang lain.</p>
<p>Klik link <code class="language-plaintext highlighter-rouge">Ganti User</code> dan kita akan mendapati form pilihan user sebagai berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/switch-user/03-pilih-user.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/switch-user/03-pilih-user.png" alt="Screen Pilih Switch User" /></a></p>
<p>Silahkan pilih salah satu, kemudian klik <code class="language-plaintext highlighter-rouge">Switch User</code>. Kita akan dibawa ke halaman transaksi seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/switch-user/04-transaksi-user001.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/switch-user/04-transaksi-user001.png" alt="Screen Daftar Transaksi Switch User" /></a></p>
<p>Menu di kanan atas juga berubah menjadi seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/switch-user/05-menu-kembali-admin.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/switch-user/05-menu-kembali-admin.png" alt="Nav Kanan Atas" /></a></p>
<p>Kita bisa klik <code class="language-plaintext highlighter-rouge">Kembali ke Admin</code> sehingga kembali ke halaman sebelumnya yaitu <code class="language-plaintext highlighter-rouge">Daftar Transaksi</code> yang berisi transaksi user <code class="language-plaintext highlighter-rouge">admin</code>.</p>
<h2 id="implementasi-audit-log">Implementasi Audit Log</h2>
<p>Bila kita menyediakan fitur untuk administrator menyamar sebagai user lain, maka sebagai programmer yang bertanggung jawab, kita juga harus memastikan bahwa fitur tersebut tidak disalahgunakan. Untuk itu, minimal kita sediakan fitur untuk mencatat kegiatan administrator selama dia bertindak atas nama user lain.</p>
<p>Di aplikasi web yang dibuat dengan Spring Framework, fitur ini bisa kita implementasikan dengan menggunakan <code class="language-plaintext highlighter-rouge">interceptor</code>. Berikut adalah class <code class="language-plaintext highlighter-rouge">AuditTrailInterceptor</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AuditTrailInterceptor</span> <span class="kd">extends</span> <span class="nc">HandlerInterceptorAdapter</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">PenggunaDao</span> <span class="n">penggunaDao</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">AuditLogDao</span> <span class="n">auditLogDao</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">preHandle</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span>
<span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">,</span>
<span class="nc">Object</span> <span class="n">handler</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"URL yang diakses : {}"</span><span class="o">,</span> <span class="n">request</span><span class="o">.</span><span class="na">getRequestURL</span><span class="o">());</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">request</span><span class="o">.</span><span class="na">getRequestURL</span><span class="o">().</span><span class="na">toString</span><span class="o">().</span><span class="na">contains</span><span class="o">(</span><span class="s">"transaksi"</span><span class="o">))</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Bukan url transaksi"</span><span class="o">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="nc">Authentication</span> <span class="n">currentUser</span> <span class="o">=</span> <span class="nc">SecurityContextHolder</span><span class="o">.</span><span class="na">getContext</span><span class="o">()</span>
<span class="o">.</span><span class="na">getAuthentication</span><span class="o">();</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Current user : {}"</span><span class="o">,</span> <span class="n">currentUser</span><span class="o">);</span>
<span class="nc">Authentication</span> <span class="n">userAsli</span> <span class="o">=</span> <span class="nc">SwitchUserHelper</span><span class="o">.</span><span class="na">userAsli</span><span class="o">(</span><span class="n">currentUser</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">userAsli</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"User asli : {}"</span><span class="o">,</span> <span class="n">userAsli</span><span class="o">);</span>
<span class="nc">AuditLog</span> <span class="n">auditLog</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AuditLog</span><span class="o">();</span>
<span class="n">auditLog</span><span class="o">.</span><span class="na">setKeterangan</span><span class="o">(</span><span class="s">"Mengakses "</span>
<span class="o">+</span><span class="n">request</span><span class="o">.</span><span class="na">getRequestURL</span><span class="o">().</span><span class="na">toString</span><span class="o">()</span>
<span class="o">+</span><span class="s">" sebagai user "</span>
<span class="o">+</span><span class="n">currentUser</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="n">auditLog</span><span class="o">.</span><span class="na">setWaktuKegiatan</span><span class="o">(</span><span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">());</span>
<span class="n">auditLog</span><span class="o">.</span><span class="na">setPenggunaAsli</span><span class="o">(</span>
<span class="n">penggunaDao</span><span class="o">.</span><span class="na">findByUsername</span><span class="o">(</span>
<span class="n">userAsli</span><span class="o">.</span><span class="na">getName</span><span class="o">()).</span><span class="na">get</span><span class="o">());</span>
<span class="n">auditLog</span><span class="o">.</span><span class="na">setPenggunaDipakai</span><span class="o">(</span>
<span class="n">penggunaDao</span><span class="o">.</span><span class="na">findByUsername</span><span class="o">(</span>
<span class="n">currentUser</span><span class="o">.</span><span class="na">getName</span><span class="o">()).</span><span class="na">get</span><span class="o">());</span>
<span class="n">auditLogDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">auditLog</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Untuk mengimplementasikan interceptor dalam Spring, kita harus membuat class yang merupakan turunan dari <code class="language-plaintext highlighter-rouge">HandlerInterceptorAdapter</code>. Ada beberapa method yang bisa kita <em>override</em> di sini, yaitu:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">preHandle</code> : dijalankan sebelum memanggil method yang akan diintercept. Di sini kita bisa melakukan pengecekan dan memutuskan apakah method yang akan diintercept akan dijalankan atau tidak. Bila akan lanjut, maka kita <code class="language-plaintext highlighter-rouge">return true</code>. Bila tidak boleh lanjut, maka kita <code class="language-plaintext highlighter-rouge">return false</code>.</li>
<li><code class="language-plaintext highlighter-rouge">postHandle</code> : dijalankan setelah method yang diintercept dijalankan</li>
<li><code class="language-plaintext highlighter-rouge">afterCompletion</code> : dijalankan setelah view dirender</li>
</ul>
<p>Dokumentasi lengkapnya bisa kita baca <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/HandlerInterceptor.html">di sini</a></p>
<p>Kita cukup mengimplementasikan salah satu method saja. Sebetulnya untuk keperluan audit trail ini, method mana saja tidak masalah. Pada contoh kali ini, kita pilih saja method <code class="language-plaintext highlighter-rouge">preHandle</code>.</p>
<p>Dalam method <code class="language-plaintext highlighter-rouge">preHandle</code>, kita melihat semua request yang masuk, menulis catatan ke log file, dan mengecek apakah request yang masuk dilakukan oleh user asli, atau user admin yang menyamar. Bila request dilakukan oleh admin yang menyamar, maka kita catat requestnya dan kita masukkan ke database.</p>
<p>Class ini kita beri anotasi <code class="language-plaintext highlighter-rouge">@Component</code> agar otomatis diinstankan oleh Spring. Selanjutnya, kita perlu mendaftarkan objectnya ke interceptor Spring agar digunakan. Caranya adalah dengan cara membuat class konfigurasi seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">KonfigurasiInterceptor</span> <span class="kd">implements</span> <span class="nc">WebMvcConfigurer</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">AuditTrailInterceptor</span> <span class="n">auditTrailInterceptor</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addInterceptors</span><span class="o">(</span><span class="nc">InterceptorRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
<span class="n">registry</span><span class="o">.</span><span class="na">addInterceptor</span><span class="o">(</span><span class="n">auditTrailInterceptor</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kita bisa mengetes hasil kerja interceptor ini dengan langkah sebagai berikut:</p>
<ol>
<li>Login ke aplikasi sebagai user <code class="language-plaintext highlighter-rouge">admin</code>.</li>
<li>Klik ganti user di menu kanan atas.</li>
<li>Pilih user yang ingin digunakan.</li>
<li>Buka halaman daftar transaksi</li>
<li>Kembali menjadi user <code class="language-plaintext highlighter-rouge">admin</code></li>
<li>Buka halaman audit trail untuk melihat hasil catatannya. Buka juga log di command line untuk melihat hasil output interceptornya</li>
</ol>
<p>Log output di console terlihat seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2020-03-15 17:02:35.736 INFO 87510 --- [io-8080-exec-10] c.m.e.b.s.b.u.AuditTrailInterceptor : URL yang diakses : http://localhost:8080/transaksi/list
2020-03-15 17:02:35.737 INFO 87510 --- [io-8080-exec-10] c.m.e.b.s.b.u.AuditTrailInterceptor : Current user : org.springframework.security.authentication.UsernamePasswordAuthenticationToken@cfe7b0c: Principal: org.springframework.security.core.userdetails.User@f73a3686: Username: user001; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Pengguna; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff4c9c: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 1C9E8E22CFD9F00F2BECB7677E6890AD; Granted Authorities: Pengguna, Switch User Authority [ROLE_PREVIOUS_ADMINISTRATOR,org.springframework.security.authentication.UsernamePasswordAuthenticationToken@f2c668ee: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Administrator; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: FC021058978AF05D7937A7C205FB09CB; Granted Authorities: Administrator]
2020-03-15 17:02:35.737 INFO 87510 --- [io-8080-exec-10] c.m.e.b.s.b.u.AuditTrailInterceptor : User asli : org.springframework.security.authentication.UsernamePasswordAuthenticationToken@f2c668ee: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Administrator; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: FC021058978AF05D7937A7C205FB09CB; Granted Authorities: Administrator
</code></pre></div></div>
<p>Dan di halaman audit trail seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2020/switch-user/06-audit-trail.png"><img src="https://software.endy.muhardin.com/images/uploads/2020/switch-user/06-audit-trail.png" alt="Audit Trail" /></a></p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah artikel singkat tentang fitur switch user. Seperti biasa, source code lengkap ada <a href="https://github.com/endymuhardin/belajar-switchuser">di Github</a>. Mudah-mudahan bermanfaat…</p>
2019 Masih Debat Database Transaction?2019-08-13T04:14:22+07:00https://software.endy.muhardin.com/programming/masih-database-transaction-lagi<p>Beberapa waktu yang lalu, salah satu seleb programmer di Yutub bikin video yang menyatakan bahwa pengetahuan tentang database transactional itu wajib bagi senior programmer.</p>
<p><code class="language-plaintext highlighter-rouge"><grammar-police></code>
Well, sebelumnya saya koreksi dulu, yang tepat adalah <code class="language-plaintext highlighter-rouge">database transaction</code>, bukan <code class="language-plaintext highlighter-rouge">transactional</code>. Kalopun mau pakai akhiran <code class="language-plaintext highlighter-rouge">al</code>, maka yang pas itu <code class="language-plaintext highlighter-rouge">transactional database</code>.
<code class="language-plaintext highlighter-rouge"></grammar-police></code></p>
<p>Netijen pun bereaksi keras Maklumlah namanya juga netijen, bebas aja. Di antara komentar netijen, ada satu yang tertangkap mata saya. Itupun karena ditag. Berikut komentarnya,</p>
<blockquote>
<p>Yaa kenapa gitu? Ada aja kok senior software developer gatahu dependency injection, github repo, sama continous integration tapi bikin programnya jago dan enak dibaca. Jangan main nge judge gitu lah statement nya</p>
</blockquote>
<p>Sebetulnya saya tidak mau menanggapinya. Sudah pernah saya tulis masalah ini 8 tahun yang lalu <a href="https://software.endy.muhardin.com/java/database-transaction/">di blog ini juga</a>. Silahkan dibaca.</p>
<p>Tapi apa boleh buat, saya sudah ditag, dan sebagai sesama netijen jaman now sudah sepantasnya kalau kita bersikap julid, FOMO, dan YOLO.</p>
<p>Dan lagipula, ini blog sudah lama gak diupdate, jadi mumpung ada bahan, here we go.</p>
<!--more-->
<blockquote>
<p>Kata iklan, “Tua itu pasti, dewasa itu pilihan”</p>
</blockquote>
<p>Berlaku juga di dunia pemrograman. Makin lama kita makin tua. Tidak bisa dihindari. Sedangkan perkembangan teknologi berjalan terus. Pilihan kita mau mengikuti atau tidak.</p>
<p>Dari sekian banyak teknologi baru, memang banyak yang hanya hype saja. Trend sesaat yang setahun dua tahun meredup lagi. Tapi ada juga yang memang bisa meningkatkan kualitas hidup dan produktifitas kita. Beberapa di antaranya malah fatal kalau tidak kita pakai.</p>
<p>Untuk mudahnya, saya klasifikasikan hal-hal yang disebutkan di atas menjadi beberapa kategori berdasarkan konsekueksi kalau kita tidak pakai :</p>
<ul>
<li>Fatal</li>
<li>Rugi besar</li>
<li>Tidak masalah</li>
</ul>
<p>Mari kita bahas satu persatu.</p>
<h2 id="database-transaction">Database Transaction</h2>
<p>Ini termasuk kategori fatal bila tidak dipakai, apalagi tidak paham. Coba lihat pseudocode untuk transfer uang senilai <code class="language-plaintext highlighter-rouge">1.000.000</code> dari rekening <code class="language-plaintext highlighter-rouge">10001</code> ke rekening <code class="language-plaintext highlighter-rouge">20002</code> berikut ini:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">1</span> <span class="k">update</span> <span class="n">rekening</span> <span class="k">set</span> <span class="n">saldo</span> <span class="o">=</span> <span class="n">saldo</span> <span class="o">-</span> <span class="mi">1000000</span> <span class="k">where</span> <span class="n">nomor</span> <span class="o">=</span> <span class="s1">'10001'</span><span class="p">;</span>
<span class="mi">2</span> <span class="k">insert</span> <span class="k">into</span> <span class="n">mutasi</span> <span class="p">(</span><span class="n">id_rekening</span><span class="p">,</span> <span class="n">nilai</span><span class="p">,</span> <span class="n">debet_kredit</span><span class="p">)</span>
<span class="k">values</span> <span class="p">(</span><span class="s1">'10001'</span><span class="p">,</span> <span class="mi">1000000</span><span class="p">,</span> <span class="s1">'kredit'</span><span class="p">);</span>
<span class="mi">3</span> <span class="k">update</span> <span class="n">rekening</span> <span class="k">set</span> <span class="n">saldo</span> <span class="o">=</span> <span class="n">saldo</span> <span class="o">+</span> <span class="mi">1000000</span> <span class="k">where</span> <span class="n">nomor</span> <span class="o">=</span> <span class="s1">'20002'</span><span class="p">;</span>
<span class="mi">4</span> <span class="k">insert</span> <span class="k">into</span> <span class="n">mutasi</span> <span class="p">(</span><span class="n">id_rekening</span><span class="p">,</span> <span class="n">nilai</span><span class="p">,</span> <span class="n">debet_kredit</span><span class="p">)</span>
<span class="k">values</span> <span class="p">(</span><span class="s1">'20002'</span><span class="p">,</span> <span class="mi">1000000</span><span class="p">,</span> <span class="s1">'debet'</span><span class="p">);</span>
</code></pre></div></div>
<p>Database transaction gunanya untuk membuat 4 perintah di atas menjadi satu kesatuan. Bila satu gagal, maka 3 sisanya akan dibatalkan. Ini istilahnya <code class="language-plaintext highlighter-rouge">atomic</code>. Sukses semua, atau gagal semua. Tidak boleh sukses sebagian.</p>
<p>Bila baru jalan nomor 1, terjadi kegagalan (aplikasi crash, harddisk server penuh, mati listrik, jaringan putus, dsb), maka pada waktu aplikasi dibuka, saldo <code class="language-plaintext highlighter-rouge">10001</code> tidak boleh berkurang.</p>
<p>Kalau ada bank yang ketahuan tidak memproses 4 perintah tadi secara <code class="language-plaintext highlighter-rouge">atomic</code>, kira-kira apakah Anda mau menabung di sana?</p>
<p>Contoh di atas baru melibatkan satu tabel database. Bagaimana lagi kalau aplikasi belanja online? Kira-kira begini pseudocodenya:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">penjualan</span><span class="p">;</span>
<span class="k">update</span> <span class="n">stok</span> <span class="k">set</span> <span class="n">jumlah</span> <span class="o">=</span> <span class="n">jumlah</span> <span class="o">-</span> <span class="n">penjualan</span> <span class="k">where</span> <span class="n">produk</span> <span class="o">=</span> <span class="s1">'mainan-gundam'</span><span class="p">;</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">tagihan</span><span class="p">;</span>
</code></pre></div></div>
<p>Ada tiga tabel yang terlibat di sana. Bagaimana kalau kita tidak usah paham, apalagi pakai transaction?</p>
<p>Silahkan dijawab sendiri lah.</p>
<h2 id="transaction-isolation">Transaction Isolation</h2>
<p>Sebetulnya transaction saja belum cukup lho. Masih ada lanjutannya, transaction isolation. Tapi ini panjang lagi urusannya. Nanti insya Allah kalau ada umur dan waktu, saya buatkan videonya aja.</p>
<p>Sama seperti database transaction, urusan isolation ini termasuk yang fatal untuk tidak dipahami. Saya baru saja menemui aplikasi yang selisih uang ratusan juta gara-gara urusan ini. Nah, kalo sudah begini, siapa yang mau nalangi?</p>
<h2 id="version-control">Version Control</h2>
<p>Ini tidak fatal seperti transaction, cuma rugi besar bila tidak pakai. Tanpa version control, konsekuensinya :</p>
<h3 id="kita-tidak-bisa-kerja-bareng-orang-lain">Kita tidak bisa kerja bareng orang lain</h3>
<p>Ada satu file, <code class="language-plaintext highlighter-rouge">proses_order.php</code>, isinya 10 function. Kita mengerjakan <code class="language-plaintext highlighter-rouge">function cek_stok</code>, teman kita mengerjakan <code class="language-plaintext highlighter-rouge">function proses_pembayaran</code>. Kedua function ada di file yang sama, tapi lokasinya berbeda. Satu di atas, satu di bawah.</p>
<p>Tanpa version control, gimana menggabungkannya?</p>
<p>Gimana kalau satu fitur (misal: registrasi user) melibatkan 10 file dan dikerjakan 4 orang?</p>
<p>Good luck …</p>
<h3 id="kita-tidak-bisa-kerja-paralel">Kita tidak bisa kerja paralel</h3>
<p>Aplikasi kita sudah rilis ke production, dipakai orang banyak. Kemudian kita mau lanjut development ke versi berikutnya. Sedang asyik-asyik coding, sudah beberapa fitur dibuat, tiba-tiba ada laporan bug di aplikasi yang jalan di production.</p>
<p>Nah, bagaimana kita memperbaiki bug production? Tentunya harus di versi yang dideploy di production, tidak bisa di versi yang sedang kita kerjakan.</p>
<p>Katakan saja kita punya file zip, berisi versi yang sudah naik production. Kita extract, perbaiki bugnya. Ada 15 file yang terdampak.</p>
<p>Selanjutnya, bagaimana memasang fix di 15 file tersebut ke versi yang sekarang sedang kita kerjakan? Padahal di versi sekarang, sudah ada juga perubahan di 15 file tersebut.</p>
<p>Good luck …</p>
<p>Masih banyak lagi kerugian kalau kita tidak pakai version control. Padahal cukup investasi waktu 1-2 hari <a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbwhs_x2S_Vv9VFRKnoXs8hn">nonton video tutorialnya</a>, sudah bisa pakai.</p>
<h2 id="continuous-delivery">Continuous Delivery</h2>
<p>Okelah, yang satu ini termasuk barang mewah. Kalau version control saja belum pakai, ya udah gak usah bahas CI/CD.</p>
<h2 id="dependency-injection-di">Dependency Injection (DI)</h2>
<p>Mengenai dependency injection sudah pernah saya tulis <a href="https://software.endy.muhardin.com/java/memahami-dependency-injection/">di sini</a> dan ada videonya juga <a href="https://www.youtube.com/watch?v=I7g3pkQaZWs&list=PL9oC_cq7OYbyhdZmCECQqp7OcS8J5QpAo&index=10">di sini</a>. Silahkan disimak dulu biar paham.</p>
<p>DI ini adalah teknik atau tips cara kita mengatur interaksi antar class/object/function/method dalam aplikasi kita. Jaman sekarang, hampir di semua lini pemrograman – server side, client side, frontend, backend, mobile, desktop – banyak mengadopsi teknik DI.</p>
<p>Tidak termasuk fatal bila tidak paham DI. Tapi rugi besar, karena:</p>
<ul>
<li>Sulit memahami framework populer yang banyak dipakai orang. Ini akan mempengaruhi nilai jual kita sebagai programmer.</li>
<li>Ada banyak fitur atau teknik turunan / lanjutan yang dibangun di atas DI, misalnya Aspect Oriented Programming, Object Composition, Configurability, dan sebagainya. Dengan teknik-teknik ini, kode program kita akan semakin rapi dan mudah dimaintain.</li>
</ul>
<h1 id="penutup">Penutup</h1>
<p>Sebagai penutup, mari kita bahas pernyataan ini.</p>
<blockquote>
<p>Ada aja kok senior software developer gatahu dependency injection, github repo, sama continous integration tapi bikin programnya jago dan enak dibaca.</p>
</blockquote>
<p>Mungkin ada, walaupun saya pribadi belum pernah kenal senior programmer seperti ini.</p>
<p>Tapi perlu kita tahu, pemrograman di jaman sekarang berbeda sekali daripada jaman VB6, Delphi, dan C/C++ dulu. Aplikasi yang kita buat di jaman itu:</p>
<ul>
<li>mayoritas (estimasi kasar 50%-80%) bikinan kita sendiri. Bahkan untuk memformat 100000000 menjadi 100.000.000 saja kita buat sendiri functionnya.</li>
<li>pengerjaan aplikasi dikerjakan sendiri. Jangankan update status di smartphone. Lha telponnya aja masih diputar, belum dipencet, apalagi disentuh</li>
</ul>
<p><a href="https://www.amazon.com/Vintage-Antique-Telephone-Fashioned-Landline/dp/B07DDCR6BC"><img src="https://software.endy.muhardin.com/images/uploads/2019/database-transaction/telpon-putar.jpg" alt="Telepon Dial" /></a></p>
<p>Gambar boleh minjam dari <a href="https://www.amazon.com/Vintage-Antique-Telephone-Fashioned-Landline/dp/B07DDCR6BC">Amazon</a></p>
<p>Beda dengan jaman now. Aplikasi yang saya buat di tahun 2019 ini, biasanya ukuran yang dideploy berkisar di 60-80 MB. Kode program yang saya tulis sendiri? Biasanya tidak lebih dari 2-5 MB saja.</p>
<p>Demikian juga, satu aplikasi bisa dikeroyok pengerjaannya 2-10 orang. Berada di lokasi yang berbeda. Jangankan tempatnya, kota atau bahkan negaranya juga berbeda. Belum lagi timezonenya juga berbeda. Programmer #1 sudah tidur ketika programmer #2 baru membuka editor.</p>
<p>Nah, jadi kalo sudah tahun 2019, masih tidak mau paham database transaction, version control, dependency injection, mungkin sebaiknya segera order telpon putar di Amazon ;P</p>
Hapus Partisi dengan Command Line2019-03-23T07:00:00+07:00https://software.endy.muhardin.com/linux/hapus-partisi<p>Meneruskan kebiasaan pada waktu menggunakan Ubuntu, kadang saya masih melakukan format ulang terhadap laptop, yang sudah 4 tahun ini menggunakan MacOS. Cara format ulangnya mirip, yaitu:</p>
<ul>
<li>
<p>Unduh image installer sistem operasi. Untuk MacOs, kita harus membuatnya sendiri menggunakan laptop/komputer Mac juga :D. Caranya bisa di-google, tidak akan saya tulis karena tiap rilis perintahnya agak berbeda. Lagipula, biasanya ada yang sudah membuatkan scriptnya.</p>
</li>
<li>
<p>Tulis image tersebut ke flashdisk. Biasanya saya pakai perintah <code class="language-plaintext highlighter-rouge">dd</code> saja di commandline. <code class="language-plaintext highlighter-rouge">dd if=/file/image.iso of=/dev/diskN bs=1m</code>. Ganti nama device <code class="language-plaintext highlighter-rouge">diskN</code> sesuai yang terdeteksi di sistem operasi.</p>
</li>
<li>
<p>Booting dari flashdisk dengan cara menekan tombol <code class="language-plaintext highlighter-rouge">Option (⌥)</code> sambil menyalakan komputer.</p>
</li>
<li>
<p>Next .. next .. selesai.</p>
</li>
</ul>
<p>Nah masalahnya adalah, flashdisk bekas menginstal tersebut agak susah dibersihkan partisinya. Sudah dihapus menggunakan aplikasi <code class="language-plaintext highlighter-rouge">Disk Utility</code>, <code class="language-plaintext highlighter-rouge">diskutil</code> command line, tetap menyisakan partisi boot.</p>
<p>Ini terutama disebabkan karena filesystem Mac tidak lazim ditemui di Windows dan Linux, sehingga untuk menghapusnya tidak bisa menggunakan aplikasi GUI biasa. Hal ini juga berlaku untuk filesystem yang aneh, seperti misalnya ZFS.</p>
<p>Untuk itu, google dan stack overflow tidak kekurangan jawaban. Ternyata kita cukup menghapus 3 blok pertama di flashdisk tersebut, karena tabel partisinya ada di situ. Bila partisi kita banyak (misalnya 10), yang harus dihapus 5 blok.</p>
<p>Perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dd if=/dev/zero of=/dev/diskN bs=512 count=3
</code></pre></div></div>
<p>Demikianlah ini menjadi catatan buat saya pribadi, supaya kalau besok-besok instal ulang, tidak perlu repot lagi mencari command untuk membersihkan flashdisk. Semoga bermanfaat untuk pembaca sekalian.</p>
Live Streaming dengan Nginx RTMP Module2018-11-12T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/live-stream-nginx-rtmp<p>Di era milenial seperti sekarang ini, segala kegiatan harus dipublish supaya eksis. Gak cukup dengan posting foto dan rekaman video, harus instan real time live show. Para raksasa sosmed berlomba-lomba menyediakan fasilitas tayangan langsung seperti Instagram Stories, Facebook Live, Youtube Live, dan masih banyak yang lainnya.</p>
<p>Karena banyak platform berbeda, tapi tujuannya sama, maka sayapun mencari aplikasi untuk membantu publikasi untuk melakukan tayangan langsung. Pengennya satu kali pasang kamera, langsung live di berbagai sosmed. Cari punya cari, ada beberapa alternatif untuk mengabstraksi tayangan langsung multi platform ini:</p>
<ul>
<li>Menggunakan aplikasi broadcaster yang mendukung multi platform, misalnya <a href="https://www.vmix.com/">VMix</a></li>
<li>Menggunakan layanan cloud seperti <a href="https://switchboard.live/">Switchboard</a>, <a href="https://about.grabyo.com/">grabyo</a> atau <a href="https://restream.io/">ReStream</a></li>
<li>Menggunakan relay server, yang berbayar contohnya <a href="https://www.wowza.com/products/streaming-engine">Wowza</a>. Yang open source bisa pakai <a href="http://red5.org/">Red5</a> atau <a href="https://github.com/sergey-dryabzhinsky/nginx-rtmp-module">Nginx RTMP Module</a></li>
</ul>
<p>Pada artikel kali ini, kita akan membahas setup Nginx RTMP Module dengan Raspberry Pi.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-raspi-resource-usage.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-raspi-resource-usage.png" alt="Raspi Resource Usage" /></a></p>
<!--more-->
<p>Nginx RTMP Module merupakan tambahan modul di atas webserver Nginx yang sudah terkenal itu. Saya pilih ini karena setupnya yang relatif mudah dan kebutuhan resourcenya yang minim. Bila kita tidak melakukan transcoding (mengubah resolusi misalnya dari 4k menjadi 1080p, 720p, dsb), maka perangkat sekelas Raspberry Pi sudah cukup. Tanpa transcoding, berarti tugasnya dia hanya menerima aliran data streaming dan meneruskannya ke berbagai platform yang kita tentukan (Facebook, Youtube).</p>
<p>Seperti bisa kita lihat pada screenshot di atas, pada saat streaming ke Facebook dan YouTube sekaligus, pemakaian CPU hanya 2% dan RAM hanya 0.4%.</p>
<p>Ada beberapa langkah setupnya, yaitu:</p>
<ol>
<li>Instalasi Raspbian di Raspberry Pi.</li>
<li>Kompilasi Nginx dan Modul RTMP</li>
<li>Konfigurasi upstream</li>
<li>Mulai live streaming</li>
</ol>
<h2 id="instalasi-raspbian">Instalasi Raspbian</h2>
<p>Instalasi Raspberry Pi sudah pernah saya bahas dalam <a href="https://software.endy.muhardin.com/linux/raspi-hardening/">artikel sebelumnya</a>. Jadi tidak akan kita bahas lagi di sini.</p>
<p>Sebetulnya pakai Linux apa saja bisa, tidak harus Debian, Ubuntu, ataupun distro tertentu. Instalasinya juga tidak harus di Raspberry Pi. Bisa juga di PC biasa ataupun VPS seperti Digital Ocean.</p>
<h2 id="kompilasi-nginx-dan-modul-rtmp">Kompilasi Nginx dan Modul RTMP</h2>
<p>Kita perlu mengunduh source code Nginx. Lakukan perintah berikut di command line.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://nginx.org/download/nginx-1.15.6.tar.gz
</code></pre></div></div>
<p>Versi Nginx silahkan disesuaikan dengan yang terbaru. Pada saat artikel ini ditulis, yang terbaru adalah <code class="language-plaintext highlighter-rouge">1.15.6</code>.</p>
<p>Kemudian, kita unduh juga source code <code class="language-plaintext highlighter-rouge">rtmp-module</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/dev.zip
</code></pre></div></div>
<p>Kita extract keduanya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tar zxvf nginx-1.15.6.tar.gz
unzip dev.zip
</code></pre></div></div>
<p>Untuk melakukan kompilasi, kita membutuhkan kompiler bahasa <code class="language-plaintext highlighter-rouge">C/C++</code> dan perlengkapan tambahannya. Di keluarga Debian sudah disediakan paketnya, bernama <code class="language-plaintext highlighter-rouge">build-essentials</code>. Kita install bersama beberapa library tambahan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install build-essential libpcre3 libpcre3-dev libssl-dev -y
</code></pre></div></div>
<p>Sekarang kita sudah bisa melakukan kompilasi. Masuk ke folder source code Nginx dan lakukan kompilasi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure --with-http_ssl_module --add-module=../nginx-rtmp-module-dev
make
sudo make install
</code></pre></div></div>
<p>Bila tidak ada pesan error, maka instalasi berhasil. Nginx akan terinstal di <code class="language-plaintext highlighter-rouge">/usr/local/nginx</code>. Kita bisa jalankan dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /usr/local/nginx/sbin/nginx
</code></pre></div></div>
<p>Kemudian kita test dengan browse ke <code class="language-plaintext highlighter-rouge">http://<ip-server-raspberry>/</code>. Seharusnya kita akan melihat layar selamat datang Nginx.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/00-nginx-welcome.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/00-nginx-welcome.png" alt="Welcome Nginx" /></a></p>
<h2 id="instalasi-ubuntu-1804">Instalasi Ubuntu 18.04</h2>
<p>Di Ubuntu sudah ada paket modul RTMP untuk Nginx, sehingga kita tidak perlu lagi kompilasi source code. Cukup install saja langsung seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install nginx libnginx-mod-rtmp -y
</code></pre></div></div>
<p>Bila kita ingin streaming ke Facebook dan Instagram, kita juga harus menginstal paket <code class="language-plaintext highlighter-rouge">stunnel</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install stunnel4 -y
</code></pre></div></div>
<p>Konfigurasi Nginx dan Stunnel akan kita bahas di bawah.</p>
<h2 id="konfigurasi-youtube">Konfigurasi Youtube</h2>
<p>Ada dua tujuan live streaming yang populer, yaitu Youtube dan Facebook.</p>
<p>Kita akan setup Youtube dulu. Login ke Youtube, kemudian buka <a href="https://www.youtube.com/live_dashboard?ar=1">halaman setting Live Streaming</a>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-live-streaming.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-live-streaming.png" alt="Youtube Live Streaming Page" /></a></p>
<p>Ada dua pilihan metode streaming di Youtube, yaitu Live Streaming dan <a href="https://www.youtube.com/my_live_events?o=U&ar=1">Event</a>. Bila kita mau spontan tayang, maka kita bisa gunakan Live Streaming. Langsung saja copy <code class="language-plaintext highlighter-rouge">Server URL</code> dan <code class="language-plaintext highlighter-rouge">Stream name/key</code> dan pasang di aplikasi broadcaster kita.</p>
<p>Bila sesi livenya terjadwal, kita bisa menggunakan Event. Di menu Event, kita bisa mengatur sesi live dengan lebih baik, misalnya kita bisa jadwalkan terlebih dulu sehingga para subscriber kita bisa menyiapkan waktu untuk menonton.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-create-event.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-create-event.png" alt="Create Event" /></a></p>
<p>Untuk membuat Event, kita klik tab Event di kiri. Kemudian isi nama event dan waktu tayang. Begitu kita klik <code class="language-plaintext highlighter-rouge">Create Event</code>, subscriber kita akan diberikan notifikasi.</p>
<p>Selanjutnya, kita akan menentukan setting bitrate dan resolusi. Semakin besar bitrate/resolusi, semakin baik kualitas audio dan video. Tapi bandwidth yang dibutuhkan juga semakin besar. Biasanya saya menggunakan <code class="language-plaintext highlighter-rouge">480p</code> saja untuk acara training/seminar/diskusi. Untuk jenis event seperti ini, kualitas audio jauh lebih penting daripada gambar.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-pilihan-resolusi.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-pilihan-resolusi.png" alt="Pilihan Resolusi" /></a></p>
<p>Bila resolusi yang kita inginkan tidak ada, maka kita bisa membuat yang baru. Cukup create event, lalu pilih resolusi yang ingin kita gunakan.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/04-create-resolusi.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/04-create-resolusi.png" alt="Membuat resolusi baru" /></a></p>
<p>Setelah kita memilih resolusi, kita akan diberikan setting untuk aplikasi encoder/broadcaster. Sama seperti sebelumnya, kita butuh nilai <code class="language-plaintext highlighter-rouge">Stream URL</code> dan <code class="language-plaintext highlighter-rouge">Stream Name/Key</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/05-setting-encoder.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/05-setting-encoder.png" alt="Setting Encoder" /></a></p>
<p><code class="language-plaintext highlighter-rouge">Stream URL</code> Youtube biasanya adalah <code class="language-plaintext highlighter-rouge">rtmp://a.rtmp.youtube.com/live2/</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/06-persiapan-streaming.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/06-persiapan-streaming.png" alt="Preview Streaming" /></a></p>
<p>Berikutnya, kita akan disajikan layar persiapan event. Di sini kita bisa mulai menyalakan aplikasi encoder kita. Hasilnya akan tampil di halaman tersebut. Kita bisa mengetes kualitas streaming kita di layar ini. Pada titik ini, tayangan kita belum dipublish secara umum. Hanya kita yang bisa melihatnya.</p>
<p>Setelah kita puas dengan kualitas video tayangan, kita bisa klik <code class="language-plaintext highlighter-rouge">Start Streaming</code>. Barulah event live kita akan bisa dilihat orang banyak.</p>
<h2 id="konfigurasi-instagram">Konfigurasi Instagram</h2>
<p>Fasilitas live ke Instagram dari laptop/PC sebetulnya tidak disediakan secara resmi. Instagram tidak memberikan alamat URL RTMP dan Stream Key yang bisa kita pasang di aplikasi streamer. Kalau kita cari di Google, ada beberapa tutorial, akan tetapi mayoritas di antaranya sudah tidak valid lagi.</p>
<p>Saya menemukan ada satu teknik yang bisa dijalankan pada saat artikel ini ditulis, yaitu menggunakan aplikasi <a href="https://yellowduck.tv">YellowDuck</a>. Itupun tidak berhasil 100%, untuk setup pertama kali, saya selalu menemui kegagalan. Baru setelah dicoba di hari berikutnya, aplikasi ini bisa jalan.</p>
<p>Walaupun demikian, tidak menutup kemungkinan aplikasi inipun untuk diblokir oleh Instagram di masa yang akan datang.</p>
<p>Pada prinsipnya, cara kerja aplikasi ini adalah dia akan berpura-pura menjadi aplikasi Instagram. Kita masukkan username dan password Instagram kita dan klik login. Dia akan mencoba login ke Instagram, menyalakan fitur live, mengambil URL RTMP dan Stream Key, kemudian menampilkannya agar bisa kita gunakan. Sebetulnya metode ini memiliki resiko keamanan, karena kita memberikan username dan password Instagram kita ke aplikasi tersebut. Walaupun dia sudah berjanji tidak akan menyimpan, tapi ya tetap saja pertanyaannya apakah kita percaya sama dia atau tidak :D.</p>
<p>Anyway, kita lanjutkan saja.</p>
<p>Aplikasinya sendiri mudah digunakan. Kita tinggal unduh dan install aplikasinya. Kemudian kita jalankan. Dia akan menampilkan halaman login.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-yellowduck-login.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-yellowduck-login.png" alt="Halaman Login YellowDuck" /></a></p>
<p>Kita masukkan username dan password Instagram kita. Dia akan berusaha login. Biasanya ketika baru pertama digunakan, Instagram akan curiga, dan meminta konfirmasi kepada kita di aplikasi mobile yang aslinya. Oleh karena itu, YellowDuck akan memberi tahu kita agar kita mengijinkan dia login. Petunjuknya seperti ini.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-yellowduck-permission.jpeg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-yellowduck-permission.jpeg" alt="Halaman Permission YellowDuck" /></a></p>
<p>Pada langkah ini, sering terjadi kegagalan, walaupun kita sudah ikuti petunjuknya. Biasanya tidak akan sukses walaupun dicoba berkali-kali. Biarkan saja, coba lagi besoknya. Karena dia tidak akan segera sukses. Pengalaman saya, di hari berikutnya baru dia bisa login. Hasilnya seperti ini.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-yellowduck-success.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-yellowduck-success.png" alt="Halaman Sukses YellowDuck" /></a></p>
<p>Selanjutnya, kita tinggal copy saja RTMP URL dan Stream Key untuk dipakai di langkah berikutnya.</p>
<h2 id="konfigurasi-facebook">Konfigurasi Facebook</h2>
<p>Di Facebook, kita bisa langsung pergi ke halaman <a href="https://www.facebook.com/live/create">Create Live Stream</a>. Klik tombol <code class="language-plaintext highlighter-rouge">Create</code>, dan kemudian kita akan masuk ke halaman persiapan live streaming</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-facebook-live-create.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-facebook-live-create.png" alt="Persiapan Live Streaming" /></a></p>
<p>Seperti biasa, kita butuh <code class="language-plaintext highlighter-rouge">Stream URL</code> dan <code class="language-plaintext highlighter-rouge">Stream Key</code>. Kita juga perlu mengisi judul dan deskripsi acara. Settingan privasi tayangan juga bisa diatur.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-facebook-encoder-setting.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-facebook-encoder-setting.png" alt="Konfigurasi Encoder" /></a></p>
<p><code class="language-plaintext highlighter-rouge">Stream URL</code> Facebook biasanya adalah <code class="language-plaintext highlighter-rouge">rtmp://live-api-s.facebook.com:80/rtmp/</code></p>
<p><strong>UPDATE !!!</strong>
Sejak Mei 2019, Facebook mengganti protokol koneksi Live Streaming menggunakan SSL. Sehingga URLnya menjadi <code class="language-plaintext highlighter-rouge">rtmps://live-api-s.facebook.com:443/rtmp/</code>. Akibatnya, Nginx RTMP Module tidak bisa lagi langsung terkoneksi dengan Facebook.</p>
<p>Solusinya adalah menggunakan aplikasi <code class="language-plaintext highlighter-rouge">stunnel</code> untuk menyediakan koneksi SSL ke Facebook, sehingga Nginx RTMP Module tidak perlu mengurus SSL.</p>
<h2 id="konfigurasi-stunnel">Konfigurasi STunnel</h2>
<p>Stunnel dapat diinstal menggunakan perintah <code class="language-plaintext highlighter-rouge">apt install stunnel4 -y</code>. Setelah itu, kita buatkan konfigurasinya di file <code class="language-plaintext highlighter-rouge">/etc/stunnel/stunnel.conf</code> sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>setuid = stunnel4
setgid = stunnel4
pid=/tmp/stunnel.pid
output = /var/log/stunnel4/stunnel.log
include = /etc/stunnel/conf.d
</code></pre></div></div>
<p>Kita buat konfigurasi koneksi ke Facebook di file <code class="language-plaintext highlighter-rouge">/etc/stunnel/conf.d/fb.conf</code> sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[fb-live]
client = yes
accept = 127.0.0.1:8888
connect = live-api-s.facebook.com:443
verifyChain = no
</code></pre></div></div>
<p>Untuk konfigurasi Instagram, kita buat di file <code class="language-plaintext highlighter-rouge">/etc/stunnel/conf.d/ig.conf</code> sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ig-live]
client = yes
accept = 127.0.0.1:9999
connect = live-upload.instagram.com:443
verifyChain = no
</code></pre></div></div>
<p>Lalu, enable <code class="language-plaintext highlighter-rouge">stunnel</code> dengan cara mengedit file <code class="language-plaintext highlighter-rouge">/etc/default/stunnel4</code> menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ENABLE=1
</code></pre></div></div>
<p>Nyalakan <code class="language-plaintext highlighter-rouge">stunnel</code> tiap kali boot.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl enable stunnel4.service
</code></pre></div></div>
<h2 id="konfigurasi-nginx-rtmp-module">Konfigurasi Nginx RTMP Module</h2>
<p>Dengan Nginx RTMP Module, kita bisa mempublikasikan stream kita ke banyak tujuan. Di artikel ini, kita akan gunakan dua saja, yaitu Youtube dan Facebook. Konfigurasinya sebagai berikut, ditulis di file <code class="language-plaintext highlighter-rouge">/usr/local/nginx/conf/nginx.conf</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
allow publish 127.0.0.1;
deny publish all;
push rtmp://a.rtmp.youtube.com/live2/abcd-abcd-abcd-abcd;
push rtmp://localhost:8888/rtmp/12345678909876543?s_ps=9&s_sw=9&s_vt=abc-d&a=QwertYasDf321Hjk;
}
}
}
</code></pre></div></div>
<p>Tujuan stream kita ada di baris yang ada perintah <code class="language-plaintext highlighter-rouge">push</code>. Formatnya adalah <code class="language-plaintext highlighter-rouge">push rtmp://<stream-url>/<stream-key></code></p>
<p>Nilai ini kita dapatkan dari konfigurasi YouTube dan Facebook di atas. Untuk Youtube, kita push langsung ke tujuan. Sedangkan untuk Facebook, kita push ke <code class="language-plaintext highlighter-rouge">stunnel</code> di port <code class="language-plaintext highlighter-rouge">8888</code> untuk selanjutnya diteruskan ke Facebook.</p>
<p>Kita juga perlu membatasi alamat IP aplikasi yang boleh stream ke sana. Bila tidak dibatasi, maka orang lain bisa publish ke sana dan tayang di channel YouTube kita dan timeline Facebook kita. Tentu ini tidak kita inginkan. Pembatasannya ada di baris berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">allow publish <ip-yang-boleh-publish></code></li>
<li><code class="language-plaintext highlighter-rouge">deny publish all</code></li>
</ul>
<h3 id="resize-resolusi-video">Resize Resolusi Video</h3>
<p>Bila kita ingin melakukan transcoding, misalnya mengubah stream yang resolusi awalnya <code class="language-plaintext highlighter-rouge">1080p</code> menjadi <code class="language-plaintext highlighter-rouge">360p</code>, kita bisa jalankan konversi dengan <code class="language-plaintext highlighter-rouge">ffmpeg</code> atau <code class="language-plaintext highlighter-rouge">avconv</code>. Tambahkan perintah <code class="language-plaintext highlighter-rouge">exec</code> sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>application live {
live on;
record off;
exec ffmpeg -i rtmp://localhost/live/$name -threads 1 -c:v libx264 -profile:v baseline -b:v 350K -s 640x360 -f flv -c:a aac -ac 1 -strict -2 -b:a 56k rtmp://localhost/live360p/$name;
}
</code></pre></div></div>
<p>Kemudian url <code class="language-plaintext highlighter-rouge">rtmp://localhost/live360p/$name</code> tersebut kita publish ulang sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>application live360p {
live on;
record off;
}
</code></pre></div></div>
<p>Untuk mengubah bitrate video, ubah nilai setelah <code class="language-plaintext highlighter-rouge">-b:v</code>. Kualitas audio bisa disesuaikan dengan nilai setelah opsi <code class="language-plaintext highlighter-rouge">-b:a</code>. Resolusi diubah dengan opsi <code class="language-plaintext highlighter-rouge">-s</code>.</p>
<p>Dengan kombinasi opsi tersebut, kita bisa mempublikasikan tayangan kita dalam beberapa pilihan resolusi dan kualitas. Jadi penonton yang menggunakan smartphone bisa memilih resolusi kecil, dan penonton di rumah dengan TV layar lebar bisa memilih resolusi maksimal.</p>
<h3 id="konversi-vertikal-untuk-instagram">Konversi Vertikal untuk Instagram</h3>
<p>Apabila kita ingin live ke Instagram, kita harus sesuaikan dulu format videonya agar menjadi vertikal.</p>
<p>Ada dua pilihan, kita tetap tampilkan videonya secara horizontal, tetapi kita tambahkan <em>padding</em> atas dan bawah. Bisa pakai blur, bisa pakai hitam. Saya biasanya pilih hitam untuk menghemat kerja CPU.</p>
<p>Berikut konfigurasi untuk menambahkan padding hitam.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>application live {
live on;
record off;
exec ffmpeg -i rtmp://localhost/live/$name -threads 1 -c:v libx264 -profile:v baseline -vf 'scale=1024:1280:force_original_aspect_ratio=decrease,pad=1024:1280:(ow-iw)/2:(oh-ih)/2,setsar=1' -f flv -c:a aac -ac 1 -strict -2 -b:v 350K -b:a 56k rtmp://localhost/liveIG/$name;
}
application liveIG {
live on;
record off;
}
</code></pre></div></div>
<p>Atau bila ingin merotasi videonya agar tetap lebar, tapi berorientasi vertikal, bisa gunakan konfigurasi yang ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>application live {
live on;
record off;
exec ffmpeg -i rtmp://localhost/live/$name -threads 1 -c:v libx264 -profile:v baseline -vf 'transpose=1,scale=1024:1280:force_original_aspect_ratio=decrease,pad=1024:1280:(ow-iw)/2:(oh-ih)/2,setsar=1' -f flv -c:a aac -ac 1 -strict -2 -b:v 350K -b:a 56k rtmp://localhost/liveIG/$name;
}
application liveIG {
live on;
record off;
}
</code></pre></div></div>
<p>Konfigurasi lengkapnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
# Konversi vertikal untuk Instagram
exec ffmpeg -i rtmp://localhost/live/$name -threads 1 -c:v libx264 -profile:v baseline -vf 'transpose=1,scale=1024:1280:force_original_aspect_ratio=decrease,pad=1024:1280:(ow-iw)/2:(oh-ih)/2,setsar=1' -f flv -c:a aac -ac 1 -strict -2 -b:v 350K -b:a 56k rtmp://localhost/liveIG/$name;
# Push Youtube
push rtmp://a.rtmp.youtube.com/live2/<stream-key>;
# Push Facebook
push rtmp://localhost:8888/rtmp/<stream-key>;
}
# Stream Instagram
application liveIG {
live on;
record off;
# Push IG Live
push rtmp://localhost:9999/rtmp/<stream-key>;
}
}
}
</code></pre></div></div>
<p>Sebelum dijalankan, test dulu konfigurasi kita dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /usr/local/nginx/sbin/nginx -t
</code></pre></div></div>
<p>Bila tidak ada error, kita bisa start dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /usr/local/nginx/sbin/nginx
</code></pre></div></div>
<p>Untuk menyetop Nginx, kita jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /usr/local/nginx/sbin/nginx -s stop
</code></pre></div></div>
<p>Bila sudah oke, kita bisa daftarkan dia menjadi <code class="language-plaintext highlighter-rouge">systemd</code> service supaya otomatis hidup pada waktu komputer dinyalakan/direstart. Buat konfigurasi di file <code class="language-plaintext highlighter-rouge">/etc/systemd/system/nginx.service</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=nginx - high performance web server
Documentation=https://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf
ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
[Install]
WantedBy=multi-user.target
</code></pre></div></div>
<p>Untuk menyalakan service, jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl start nginx.service
</code></pre></div></div>
<p>Supaya dia jalan otomatis tiap komputer dinyalakan, kita enable servicenya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl enable nginx.service
</code></pre></div></div>
<h2 id="konfigurasi-streaming-obs">Konfigurasi Streaming OBS</h2>
<p><a href="https://obsproject.com/">OBS (Open Broadcaster Software) Project</a> adalah aplikasi encoder/broadcaster open source yang sudah terbukti kehandalannya. Aplikasi ini sudah amat cukup untuk melakukan live streaming.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-obs-pilihan-stream-service.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-obs-pilihan-stream-service.png" alt="Layanan OBS" /></a></p>
<p>Di antara fiturnya adalah:</p>
<ul>
<li>Bisa streaming ke Youtube, Facebook, dan lainnya</li>
<li>Bisa multiple input (kamera, audio, capture aplikasi, gambar/foto, video, tulisan, dan sebagainya)</li>
<li>dan banyak lainnya</li>
</ul>
<p>Saking banyak dan lengkap fiturnya, perlu satu artikel khusus untuk membahas OBS. Sedangkan di sini kita mau fokus membahas live streaming.</p>
<p>Walaupun demikian, ada satu keterbatasan OBS, yaitu dia hanya bisa broadcast ke satu tujuan. Tidak bisa misalnya ke Youtube dan Facebook sekaligus. Bila kita ingin melakukan broadcast ke beberapa tujuan seperti gambar di bawah, kita harus pakai aplikasi lain seperti misalnya <code class="language-plaintext highlighter-rouge">Vmix</code> atau menggunakan layanan cloud seperti <code class="language-plaintext highlighter-rouge">reStream</code>, <code class="language-plaintext highlighter-rouge">SwitchBoard</code>, atau lainnya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-direct-streaming.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-direct-streaming.jpg" alt="Direct Streaming" /></a></p>
<p>Untuk itu, kita akan menggunakan relay server dengan <code class="language-plaintext highlighter-rouge">Nginx</code> yang sudah kita setup di atas. Sehingga topologinya menjadi seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-nginx-rtmp-streaming.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-nginx-rtmp-streaming.png" alt="Nginx RTMP Streaming" /></a></p>
<p>Untuk menghubungkan OBS dengan Nginx, masuk ke menu <code class="language-plaintext highlighter-rouge">Settings</code>, kemudian masuk ke menu <code class="language-plaintext highlighter-rouge">Stream</code>. Kita bisa pasang <code class="language-plaintext highlighter-rouge">Stream URL</code> dan <code class="language-plaintext highlighter-rouge">Stream Key</code> di sana.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-obs-stream-setting.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-obs-stream-setting.png" alt="Konfigurasi Stream OBS" /></a></p>
<p>Kita masukkan nilai berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Stream URL</code> : <code class="language-plaintext highlighter-rouge">rtmp://<ip-address-nginx-server>/live</code></li>
<li><code class="language-plaintext highlighter-rouge">Stream Key</code> : masukkan nilai apa saja, bebas. Misalnya kita berikan key <code class="language-plaintext highlighter-rouge">coba-coba-streaming</code></li>
</ul>
<p>Setelah itu kita bisa mulai streaming dengan menekan tombol <code class="language-plaintext highlighter-rouge">Start Streaming</code>.</p>
<h2 id="test-streaming">Test Streaming</h2>
<p>Nginx RTMP Module selain mempublish tayangan kita ke tujuan yang disebutkan dalam konfigurasi <code class="language-plaintext highlighter-rouge">push</code>, juga akan menyediakan URL streaming langsung. URL ini nantinya bisa digunakan orang lain yang ingin me-relay tayangan kita. Kita juga bisa gunakan URL ini untuk mengetes tayangan kita menggunakan aplikasi <a href="https://www.videolan.org/vlc/features.html">VLC Player</a> yang gratis dan open source.</p>
<p>Masuk ke menu <code class="language-plaintext highlighter-rouge">File > Open Network</code>, lalu masukkan URL <code class="language-plaintext highlighter-rouge">rtmp://<ip-nginx-rtmp>/live/<stream-key></code> seperti screenshot berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-vlc-open-stream.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-vlc-open-stream.png" alt="VLC Open Stream" /></a></p>
<p>Seharusnya kita bisa menonton live streaming yang dikirim oleh OBS.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-vlc-view-stream.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-vlc-view-stream.png" alt="VLC View Stream" /></a></p>
<h2 id="docker">Docker</h2>
<p>Bila setiap kali mau melakukan live streaming kita harus melakukan konfigurasi, tentunya ini sangat melelahkan. Agar lebih praktis, kita bisa menjalankan setup ini menggunakan Docker. Apa itu docker dan bagaimana cara kerjanya tidak dibahas pada artikel ini. Silahkan baca <a href="https://software.endy.muhardin.com/linux/intro-docker/">artikel berikut untuk memahami apa itu docker</a>.</p>
<p>Kita akan menjalankan dua Docker container sekaligus, yang satu menjalankan <code class="language-plaintext highlighter-rouge">stunnel</code>, satunya lagi menjalankan <code class="language-plaintext highlighter-rouge">nginx-rtmp-module</code>. Caranya adalah menggunakan <code class="language-plaintext highlighter-rouge">docker-compose</code>. File konfigurasinya kita buat dalam file bernama <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> yang isinya sebagai berikut:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3"</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">stunnel-proxy-fb</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">tstrohmeier/stunnel-client:latest</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ACCEPT=8888</span>
<span class="pi">-</span> <span class="s">CONNECT=live-api-s.facebook.com:443</span>
<span class="na">stunnel-proxy-ig</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">tstrohmeier/stunnel-client:latest</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ACCEPT=9999</span>
<span class="pi">-</span> <span class="s">CONNECT=live-upload.instagram.com:443</span>
<span class="na">nginx-rtmp-streamer</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">jasonrivers/nginx-rtmp</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">RTMP_PUSH_URLS=rtmp://a.rtmp.youtube.com/live2/${YOUTUBE_STREAM_KEY},rtmp://stunnel-proxy-fb:8888/rtmp/${FACEBOOK_STREAM_KEY},rtmp://stunnel-proxy-ig:9999/rtmp/${INSTAGRAM_STREAM_KEY}</span>
<span class="na">depends_on</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">stunnel-proxy-fb</span>
<span class="pi">-</span> <span class="s">stunnel-proxy-ig</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">1935:1935"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">8080:8080"</span>
</code></pre></div></div>
<p>File tersebut membutuhkan konfigurasi untuk mengisi variabel <code class="language-plaintext highlighter-rouge">${YOUTUBE_STREAM_KEY}</code>, <code class="language-plaintext highlighter-rouge">${FACEBOOK_STREAM_KEY}</code>, dan <code class="language-plaintext highlighter-rouge">${INSTAGRAM_STREAM_KEY}</code>. Kedua nilai ini sengaja dikeluarkan dari file <code class="language-plaintext highlighter-rouge">docker-compose</code> agar bisa diubah-ubah sesuai akun yang akan digunakan untuk live.</p>
<p>Konfigurasinya kita buat dalam file yang bernama <code class="language-plaintext highlighter-rouge">.env</code>. Isinya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>YOUTUBE_STREAM_KEY=abcd-abcd-abcd-abcd
FACEBOOK_STREAM_KEY=12345678909876543?s_ps=9&s_sw=9&s_vt=abc-d&a=QwertYasDf321Hjk
INSTAGRAM_STREAM_KEY=xyz-xyz-xyz
</code></pre></div></div>
<p>File <code class="language-plaintext highlighter-rouge">.env</code> dan file <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> diletakkan dalam folder yang sama. Setelah itu jalankan dengan perintah <code class="language-plaintext highlighter-rouge">docker-compose up -d</code>. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating network "tmp_default" with the default driver
Creating tmp_stunnel-proxy_1 ... done
Creating tmp_nginx-rtmp-streamer_1 ... done
Attaching to tmp_stunnel-proxy_1, tmp_nginx-rtmp-streamer_1
nginx-rtmp-streamer_1 | Creating config
nginx-rtmp-streamer_1 | Creating stream live
nginx-rtmp-streamer_1 | Pushing stream to rtmp://a.rtmp.youtube.com/live2/abcd-abcd-abcd-abcd
nginx-rtmp-streamer_1 | Pushing stream to rtmp://stunnel-proxy-fb:8888/rtmp/112345678909876543?s_ps=9&s_sw=9&s_vt=abc-d&a=QwertYasDf321Hjk
nginx-rtmp-streamer_1 | Pushing stream to rtmp://stunnel-proxy-ig:9999/rtmp/112345678909876543?s_ps=9&s_sw=9&s_vt=abc-d&a=QwertYasDf321Hjk
nginx-rtmp-streamer_1 | Creating stream testing
stunnel-proxy_1 | 2020.02.05 05:37:48 LOG5[ui]: stunnel 5.46 on x86_64-alpine-linux-musl platform
stunnel-proxy_1 | 2020.02.05 05:37:48 LOG5[ui]: Compiled with LibreSSL 2.7.3
stunnel-proxy_1 | 2020.02.05 05:37:48 LOG5[ui]: Running with LibreSSL 2.7.4
stunnel-proxy_1 | 2020.02.05 05:37:48 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP,SNI
stunnel-proxy_1 | 2020.02.05 05:37:48 LOG5[ui]: Reading configuration from file /etc/stunnel/stunnel.conf
stunnel-proxy_1 | 2020.02.05 05:37:48 LOG5[ui]: UTF-8 byte order mark not detected
stunnel-proxy_1 | 2020.02.05 05:37:48 LOG4[ui]: Service [stunnelHttpsToHttpclient] needs authentication to prevent MITM attacks
stunnel-proxy_1 | 2020.02.05 05:37:48 LOG5[ui]: Configuration successful
stunnel-proxy_1 | 2020.02.05 05:38:00 LOG5[0]: Service [stunnelHttpsToHttpclient] accepted connection from 172.19.0.3:50088
stunnel-proxy_1 | 2020.02.05 05:38:00 LOG5[0]: s_connect: connected 31.13.92.6:443
stunnel-proxy_1 | 2020.02.05 05:38:00 LOG5[0]: Service [stunnelHttpsToHttpclient] connected remote server from 172.19.0.2:41612
</code></pre></div></div>
<p>Di aplikasi OBS, masukkan setting seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Streaming Service: Custom
Server: rtmp://<your server ip>/live
Play Path/Stream Key: mystream
</code></pre></div></div>
<p>Setelah selesai, untuk mematikannya, ketik perintah <code class="language-plaintext highlighter-rouge">docker-compose down</code>.</p>
<h3 id="live-streaming-drone">Live Streaming Drone</h3>
<p>Metode docker ini juga bisa digunakan untuk membuat live streaming dari drone Dji dan kamera GoPro.</p>
<p>Jalankan Docker container dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -d --rm -p 1935:1935 -p 8080:8080 jasonrivers/nginx-rtmp
</code></pre></div></div>
<p>Komputer kita akan menjalankan RTMP server di port <code class="language-plaintext highlighter-rouge">1935</code> dan siap menerima koneksi. Kita harus cari tahu dulu alamat IP komputer kita, misalnya <code class="language-plaintext highlighter-rouge">192.168.100.4</code>.</p>
<p>Bila kita menggunakan GoPro, buka aplikasinya di hape. Kemudian masuk ke menu <code class="language-plaintext highlighter-rouge">Live</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-gopro-live.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-gopro-live.jpg" alt="GoPro Menu Live" /></a></p>
<p>Selanjutnya, pilih menu <code class="language-plaintext highlighter-rouge">RTMP</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-gopro-rtmp.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-gopro-rtmp.jpg" alt="GoPro RTMP" /></a></p>
<p>Masukkan alamat komputer kita tadi, yaitu <code class="language-plaintext highlighter-rouge">192.168.100.4</code> berikut url dan stream key yang kita pilih. Misalnya <code class="language-plaintext highlighter-rouge">rtmp://192.168.100.4/live/gopro</code>. Setelah itu klik <code class="language-plaintext highlighter-rouge">Continue</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-gopro-continue.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-gopro-continue.jpg" alt="GoPro Continue" /></a></p>
<p>Klik <code class="language-plaintext highlighter-rouge">Go Live</code>. GoPro akan segera mengirim data ke komputer.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/05-gopro-live.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/05-gopro-live.jpg" alt="GoPro Live" /></a></p>
<p>Kita bisa pantau koneksi dan kestabilan pengiriman data.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/06-gopro-koneksi.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/06-gopro-koneksi.jpg" alt="GoPro Koneksi" /></a></p>
<p>Untuk drone Dji, caranya mirip. Masuk ke menu <code class="language-plaintext highlighter-rouge">Transmission</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-dji-transmission.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/01-dji-transmission.jpg" alt="Dji Transmission" /></a></p>
<p>Kemudian klik <code class="language-plaintext highlighter-rouge">Live Streaming Platforms</code>. Cuma ada satu pilihan di sana, yaitu <code class="language-plaintext highlighter-rouge">RTMP</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-dji-rtmp-menu.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/02-dji-rtmp-menu.jpg" alt="Dji RTMP Menu" /></a></p>
<p>Selanjutnya, masukkan alamat komputer kita dengan stream keynya. Misalnya <code class="language-plaintext highlighter-rouge">rtmp://192.168.100.4/live/mini2</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-dji-rtmp-url.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/03-dji-rtmp-url.jpg" alt="Dji RTMP URL" /></a></p>
<p>Klik OK, selanjutnya kita tinggal menerima data di komputer dengan OBS atau VLC seperti dijelaskan di atas.</p>
<p>Buat pengguna MacOS yang lebih suka tampilan grafis, bisa coba <a href="https://github.com/sallar/mac-local-rtmp-server">Mac Local RTMP Server</a> atau <a href="https://help.elgato.com/hc/en-us/articles/360031363132-OBS-Link-Setup">OBS Link</a> dari Elgato.</p>
<h2 id="penutup">Penutup</h2>
<p>Live Streaming merupakan fasilitas jaman now yang sangat bermanfaat. Layanannya gratis, setupnya tidak sulit, aplikasinya gratis, pokoknya tinggal pakai. Bahkan seandainya kita hanya bermodalkan smartphone, kita bisa langsung live. Akan tetapi, untuk mendapatkan hasil yang lebih profesional, kita perlu menggunakan aplikasi yang lebih canggih seperti OBS. Di situ kita bisa menambahkan logo di kanan atas, nama pembicara di bawah (lower third), running text, menjalankan iklan, dan sebagainya. Hasilnya bisa dilihat di <a href="https://youtu.be/HQ-BG0pyz8A">event Monday Forum Tazkia</a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/07-hasil-streaming.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/live-streaming/07-hasil-streaming.png" alt="YouTube Hasil Streaming" /></a></p>
<p>Dengan sedikit tambahan Nginx RTMP Module, kita bisa mempublikasikannya ke banyak platform sekaligus.</p>
<p>Selamat mencoba, semoga bermanfaat …</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="https://obsproject.com/forum/resources/how-to-set-up-your-own-private-rtmp-server-using-nginx.50/">https://obsproject.com/forum/resources/how-to-set-up-your-own-private-rtmp-server-using-nginx.50/</a></li>
<li><a href="https://github.com/tiangolo/nginx-rtmp-docker/blob/master/README.md">https://github.com/tiangolo/nginx-rtmp-docker/blob/master/README.md</a></li>
<li><a href="https://www.leaseweb.com/labs/2013/11/streaming-video-demand-nginx-rtmp-module/">https://www.leaseweb.com/labs/2013/11/streaming-video-demand-nginx-rtmp-module/</a></li>
<li><a href="https://www.vultr.com/docs/setup-nginx-rtmp-on-centos-7">https://www.vultr.com/docs/setup-nginx-rtmp-on-centos-7</a></li>
<li><a href="https://dev.to/lax/rtmps-relay-with-stunnel-12d3">https://dev.to/lax/rtmps-relay-with-stunnel-12d3</a></li>
<li><a href="https://sites.google.com/view/facebook-rtmp-to-rtmps/home">https://sites.google.com/view/facebook-rtmp-to-rtmps/home</a></li>
<li><a href="https://hub.docker.com/r/tstrohmeier/stunnel-client/">https://hub.docker.com/r/tstrohmeier/stunnel-client/</a></li>
<li><a href="https://github.com/arut/nginx-rtmp-module/issues/1397">https://github.com/arut/nginx-rtmp-module/issues/1397</a></li>
<li><a href="https://sites.google.com/view/facebook-rtmp-to-rtmps/home">https://sites.google.com/view/facebook-rtmp-to-rtmps/home</a></li>
<li><a href="https://www.openwritings.net/pg/ffmpeg/ffmpeg-add-logo-video">https://www.openwritings.net/pg/ffmpeg/ffmpeg-add-logo-video</a></li>
</ul>
Implementasi Single Sign On dengan Google2018-10-16T07:00:00+07:00https://software.endy.muhardin.com/java/spring-boot-google-sso<p>Jaman sekarang eranya integrasi antar aplikasi. Aplikasi A ingin mengakses data di Aplikasi B. Apalagi dengan mewabahnya arsitektur microservice, bahkan sesama aplikasi yang kita buat juga ingin berkomunikasi antar aplikasi.</p>
<p>Dengan sekian banyak aplikasi, tentunya akan sangat mengganggu user kalau dia harus login berkali-kali di tiap aplikasi tersebut. Untuk itu kita harus membuat semacam login server, di mana semua user akan login di sana dan semua aplikasi akan mengecek di sana apakah user tersebut sudah login atau belum.</p>
<p>Biasanya, hal ini kita menggunakan standar protokol OAuth 2.0 dan atau OpenID Connect. Lebih detail tentang protokol ini bisa ditonton <a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbyXj9NPqM2iedlHQMeChGJp">di video saya di Youtube</a>.</p>
<p>Dalam Spring Security 4 dan Spring Boot 1, kita mendapatkan fitur untuk membuat <code class="language-plaintext highlighter-rouge">Authorization Server</code>, <code class="language-plaintext highlighter-rouge">Resource Server</code>, dan <code class="language-plaintext highlighter-rouge">Client Application</code>. Akan tetapi, di Spring Security 5, mereka berencana untuk melakukan perombakan besar-besaran. Di Spring Security versi 5.0, para pengembang Spring sudah menyediakan dukungan <code class="language-plaintext highlighter-rouge">Client Application</code> yang baru. Ini sudah masuk dalam Spring Boot 2.0.</p>
<p>Dukungan terhadap <code class="language-plaintext highlighter-rouge">Resource Server</code> yang baru direncanakan akan launching di Spring Security versi 5.1 dan dibundel dalam Spring Boot 2.1. Pada saat artikel ini ditulis, Spring Security 5.1 dan Spring Boot 2.1 belum dirilis.</p>
<p>Pada artikel kali ini, kita akan mengimplementasikan <code class="language-plaintext highlighter-rouge">Client Application</code> (atau <code class="language-plaintext highlighter-rouge">Relying Party</code> dalam istilah OpenID Connect) dengan fitur login dengan Google. Kita juga akan membuat tabel di aplikasi kita yang memuat setting permission. Kita akan mapping username dari Google dengan permission yang kita kelola di aplikasi kita sendiri.</p>
<!--more-->
<h2 id="registrasi-aplikasi-ke-google">Registrasi Aplikasi ke Google</h2>
<p>Agar aplikasi kita bisa ikut login ke Google, kita harus mendaftar dulu dengan cara membuat project di <a href="https://console.developers.google.com/">Google Developer Console</a>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/01-create-client-app-project.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/01-create-client-app-project.png" alt="Create Project" /></a></p>
<p>Kemudian masuk ke tab <code class="language-plaintext highlighter-rouge">Credential</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/02-tab-credentials.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/02-tab-credentials.png" alt="Tab Credential" /></a></p>
<p>Kita membuat credential untuk mendapatkan <code class="language-plaintext highlighter-rouge">client-id</code> dan <code class="language-plaintext highlighter-rouge">client-secret</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/03-create-oauth-clientid.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/03-create-oauth-clientid.png" alt="Create Credential" /></a></p>
<p>Setelah login, Google akan menampilkan consent screen untuk menanyakan apakah user mengijinkan aplikasi kita untuk mengakses data pribadi kita di google.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/04-configure-consent-screen.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/04-configure-consent-screen.png" alt="Konfigurasi Consent Screen" /></a></p>
<p>Kita harus mengisi nama aplikasi dan scope informasi yang ingin kita dapatkan dari Google. Biasanya untuk proses OpenID Connect, kita butuh scope <code class="language-plaintext highlighter-rouge">email</code>, <code class="language-plaintext highlighter-rouge">profile</code>, dan <code class="language-plaintext highlighter-rouge">openid</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/05-consent-screen-form-1.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/05-consent-screen-form-1.png" alt="Form Consent Screen 1" /></a></p>
<p>Kita juga harus mengisi callback URL. Google akan melakukan redirect ke URL ini setelah user memberikan consent.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/06-consent-screen-form-2.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/06-consent-screen-form-2.png" alt="Form Consent Screen 2" /></a></p>
<p>Setelah kita isi semua, kita akan mendapatkan <code class="language-plaintext highlighter-rouge">client-id</code> dan <code class="language-plaintext highlighter-rouge">client-secret</code>. Copy nilainya, kita akan menggunakannya di langkah berikutnya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/07-client-id-secret.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/07-client-id-secret.png" alt="Client ID dan Secret" /></a></p>
<h2 id="fitur-aplikasi">Fitur Aplikasi</h2>
<p>Kita akan membuat aplikasi internet banking versi dummy. Fiturnya terdiri dari:</p>
<ul>
<li>Dashboard / Home Page</li>
<li>Lihat Rekening</li>
<li>Lihat Mutasi</li>
<li>Transfer</li>
</ul>
<p>Untuk menyederhanakan kode program, agar bisa fokus ke login dan ijin akses, kita hanya akan membuatkan halaman HTML kosong untuk masing-masing fitur.</p>
<p>Tampilannya kira-kira seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/01-create-client-app-project.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/01-create-client-app-project.png" alt="Tampilan Aplikasi" /></a></p>
<p>Yang diimplementasikan dengan Spring Controller berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Controller</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">HomeController</span> <span class="o">{</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"home"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">home</span><span class="o">(){}</span>
<span class="nd">@PreAuthorize</span><span class="o">(</span><span class="s">"hasAuthority('VIEW_REKENING')"</span><span class="o">)</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"rekening"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">daftarRekening</span><span class="o">(){}</span>
<span class="nd">@PreAuthorize</span><span class="o">(</span><span class="s">"hasAuthority('VIEW_MUTASI')"</span><span class="o">)</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"mutasi"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">mutasiRekening</span><span class="o">(){}</span>
<span class="nd">@PreAuthorize</span><span class="o">(</span><span class="s">"hasAuthority('EDIT_TRANSFER')"</span><span class="o">)</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"transfer"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">transfer</span><span class="o">(){}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>dan screen HTML berikut</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span> <span class="na">layout:decorate=</span><span class="s">"~{layout.html}"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">></span>
<span class="nt"><title></span>Transfer Uang<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><section</span> <span class="na">layout:fragment=</span><span class="s">"content"</span><span class="nt">></span>
<span class="nt"><h2></span>Transfer Uang<span class="nt"></h2></span>
<span class="nt"></section></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>dengan layout berikut</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">></span>
<span class="nt"><title></span>Aplikasi Internet Banking<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><h1></span>Aplikasi Internet Banking<span class="nt"></h1></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"rightnav"</span><span class="nt">></span>
Welcome, <span class="nt"><span</span> <span class="na">th:text=</span><span class="s">"${#authentication.principal.fullName}"</span><span class="nt">></span>current user<span class="nt"></span></span>
<span class="nt"><form</span> <span class="na">method=</span><span class="s">"post"</span> <span class="na">th:action=</span><span class="s">"@{/logout}"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"Logout"</span><span class="nt">></span>
<span class="nt"></form></span>
<span class="nt"></div></span>
<span class="nt"><ul</span> <span class="na">class=</span><span class="s">"nav"</span><span class="nt">></span>
<span class="nt"><li></span>
<span class="nt"><a</span> <span class="na">th:href=</span><span class="s">"@{/home}"</span><span class="nt">></span>Home<span class="nt"></a></span>
<span class="nt"></li></span>
<span class="nt"><li></span>
<span class="nt"><a</span> <span class="na">th:href=</span><span class="s">"@{/rekening}"</span> <span class="na">sec:authorize=</span><span class="s">"hasAuthority('VIEW_REKENING')"</span><span class="nt">></span>Daftar Rekening<span class="nt"></a></span>
<span class="nt"></li></span>
<span class="nt"><li></span>
<span class="nt"><a</span> <span class="na">th:href=</span><span class="s">"@{/mutasi}"</span> <span class="na">sec:authorize=</span><span class="s">"hasAuthority('VIEW_MUTASI')"</span><span class="nt">></span>Mutasi Rekening<span class="nt"></a></span>
<span class="nt"></li></span>
<span class="nt"><li></span>
<span class="nt"><a</span> <span class="na">th:href=</span><span class="s">"@{/transfer}"</span> <span class="na">sec:authorize=</span><span class="s">"hasAuthority('EDIT_TRANSFER')"</span><span class="nt">></span>Transfer<span class="nt"></a></span>
<span class="nt"></li></span>
<span class="nt"></ul></span>
<span class="nt"><hr</span> <span class="nt">/></span>
<span class="nt"><div></span>
Authorities :
<span class="nt"><ul></span>
<span class="nt"><li</span> <span class="na">th:each=</span><span class="s">"authority : ${#authentication.authorities}"</span> <span class="na">th:text=</span><span class="s">"${authority}"</span><span class="nt">></span>View Rekening<span class="nt"></li></span>
<span class="nt"></ul></span>
<span class="nt"></div></span>
<span class="nt"><hr</span> <span class="nt">/></span>
<span class="nt"><section</span> <span class="na">layout:fragment=</span><span class="s">"content"</span><span class="nt">></span>
<span class="nt"><p></span>Page content goes here<span class="nt"></p></span>
<span class="nt"></section></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>Pada template di atas, kita menggunakan <a href="https://github.com/thymeleaf/thymeleaf-extras-springsecurity">Spring Security Thymeleaf Dialect</a> sehingga kita bisa dengan mudah <strong>show/hide</strong> link atau elemen HTML dengan tag <code class="language-plaintext highlighter-rouge">sec:authorize</code> seperti ini <code class="language-plaintext highlighter-rouge"><a th:href="@{/rekening}" sec:authorize="hasAuthority('VIEW_REKENING')">Daftar Rekening</a></code></p>
<h2 id="skema-database">Skema Database</h2>
<p>Di aplikasi kita, seperti bisa dilihat pada template dan controller, kita menggunakan beberapa permission/authority sebagai berikut:</p>
<ul>
<li>VIEW_REKENING</li>
<li>VIEW_MUTASI</li>
<li>EDIT_TRANSFER</li>
</ul>
<p>Permission tersebut kita simpan di database aplikasi kita dengan skema seperti ini</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">s_permission</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">permission_label</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">permission_value</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">UNIQUE</span> <span class="p">(</span><span class="n">permission_value</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">s_role</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">description</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">name</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">UNIQUE</span> <span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">s_role_permission</span> <span class="p">(</span>
<span class="n">id_role</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">id_permission</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id_role</span><span class="p">,</span> <span class="n">id_permission</span><span class="p">),</span>
<span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id_permission</span><span class="p">)</span> <span class="k">REFERENCES</span> <span class="n">s_permission</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id_role</span><span class="p">)</span> <span class="k">REFERENCES</span> <span class="n">s_role</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">s_user</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">varchar</span> <span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">username</span> <span class="nb">varchar</span> <span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">id_role</span> <span class="nb">varchar</span> <span class="p">(</span><span class="mi">36</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">foreign</span> <span class="k">key</span> <span class="p">(</span><span class="n">id_role</span><span class="p">)</span> <span class="k">references</span> <span class="n">s_role</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">unique</span> <span class="p">(</span><span class="n">username</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Kemudian kita isi datanya sebagai berikut</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_permission</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">permission_value</span><span class="p">,</span> <span class="n">permission_label</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'viewrekening'</span><span class="p">,</span> <span class="s1">'VIEW_REKENING'</span><span class="p">,</span> <span class="s1">'Lihat Data Rekening'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'viewmutasi'</span><span class="p">,</span> <span class="s1">'VIEW_MUTASI'</span><span class="p">,</span> <span class="s1">'Lihat Data Mutasi'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'edittransfer'</span><span class="p">,</span> <span class="s1">'EDIT_TRANSFER'</span><span class="p">,</span> <span class="s1">'Input Transfer'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_role</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'staff'</span><span class="p">,</span> <span class="s1">'STAFF'</span><span class="p">,</span> <span class="s1">'Staff'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'manager'</span><span class="p">,</span> <span class="s1">'MANAGER'</span><span class="p">,</span> <span class="s1">'Manager'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_role_permission</span> <span class="p">(</span><span class="n">id_role</span><span class="p">,</span> <span class="n">id_permission</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'staff'</span><span class="p">,</span> <span class="s1">'viewrekening'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'staff'</span><span class="p">,</span> <span class="s1">'viewmutasi'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'manager'</span><span class="p">,</span> <span class="s1">'viewrekening'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'manager'</span><span class="p">,</span> <span class="s1">'viewmutasi'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'manager'</span><span class="p">,</span> <span class="s1">'edittransfer'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_user</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">id_role</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'u001'</span><span class="p">,</span> <span class="s1">'endy@artivisi.com'</span><span class="p">,</span> <span class="s1">'staff'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_user</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">id_role</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'u002'</span><span class="p">,</span> <span class="s1">'endy.muhardin@gmail.com'</span><span class="p">,</span> <span class="s1">'manager'</span><span class="p">);</span>
</code></pre></div></div>
<p>Seperti kita lihat pada skema dan data di atas, kita tidak menyimpan data password user. Ini karena pemeriksaan password akan kita delegasikan ke Google sebagai OpenID Connect Provider.</p>
<h2 id="entity-class-dan-dao">Entity Class dan DAO</h2>
<p>Skema database di atas kita buatkan entity classnya agar lebih mudah di-query dengan JPA.</p>
<ul>
<li>Class User</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span> <span class="nd">@Table</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"s_user"</span><span class="o">)</span> <span class="nd">@Data</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">User</span> <span class="o">{</span>
<span class="nd">@Id</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">username</span><span class="o">;</span>
<span class="nd">@ManyToOne</span> <span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"id_role"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Role</span> <span class="n">role</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
<li>Class Role</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span> <span class="nd">@Table</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"s_role"</span><span class="o">)</span> <span class="nd">@Data</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Role</span> <span class="o">{</span>
<span class="nd">@Id</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">description</span><span class="o">;</span>
<span class="nd">@ManyToMany</span><span class="o">(</span><span class="n">fetch</span> <span class="o">=</span> <span class="nc">FetchType</span><span class="o">.</span><span class="na">EAGER</span><span class="o">)</span>
<span class="nd">@JoinTable</span><span class="o">(</span>
<span class="n">name</span> <span class="o">=</span> <span class="s">"s_role_permission"</span><span class="o">,</span>
<span class="n">joinColumns</span> <span class="o">=</span> <span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"id_role"</span><span class="o">),</span>
<span class="n">inverseJoinColumns</span> <span class="o">=</span> <span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"id_permission"</span><span class="o">)</span>
<span class="o">)</span>
<span class="kd">private</span> <span class="nc">Set</span><span class="o"><</span><span class="nc">Permission</span><span class="o">></span> <span class="n">permissions</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashSet</span><span class="o"><>();</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
<li>Class Permission</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span> <span class="nd">@Table</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"s_permission"</span><span class="o">)</span> <span class="nd">@Data</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Permission</span> <span class="o">{</span>
<span class="nd">@Id</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"permission_label"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">label</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"permission_value"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kita cuma butuh satu DAO saja untuk kebutuhan login dan ijin akses ini, yaitu <code class="language-plaintext highlighter-rouge">UserDao</code> sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">UserDao</span> <span class="kd">extends</span> <span class="nc">PagingAndSortingRepository</span><span class="o"><</span><span class="nc">User</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="o">{</span>
<span class="nc">User</span> <span class="nf">findByUsername</span><span class="o">(</span><span class="nc">String</span> <span class="n">username</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="dependensi-maven">Dependensi Maven</h2>
<p>Untuk mengaktifkan single-sign on dengan Google ini, kita harus menambahkan dua dependensi berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.security<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-security-oauth2-client<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.security<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-security-oauth2-jose<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Selain itu adalah dependensi Spring Boot biasa</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-security<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-thymeleaf<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>nz.net.ultraq.thymeleaf<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>thymeleaf-layout-dialect<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.thymeleaf.extras<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>thymeleaf-extras-springsecurity5<span class="nt"></artifactId></span>
<span class="nt"><version></span>3.0.3.RELEASE<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-web<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Dan beberapa dependensi untuk urusan database</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-data-jpa<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.flywaydb<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flyway-core<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.projectlombok<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>lombok<span class="nt"></artifactId></span>
<span class="nt"><optional></span>true<span class="nt"></optional></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.h2database<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>h2<span class="nt"></artifactId></span>
<span class="nt"><scope></span>runtime<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<h2 id="konfigurasi-client-application">Konfigurasi Client Application</h2>
<p>Berikutnya, kita buatkan konfigurasi security untuk aplikasi kita. Sebetulnya, cukup dengan memasang <code class="language-plaintext highlighter-rouge">client-id</code> dan <code class="language-plaintext highlighter-rouge">client-secret</code> dan mengikutkan dependensi <code class="language-plaintext highlighter-rouge">spring-security-oauth2-client</code> dan <code class="language-plaintext highlighter-rouge">spring-security-oauth2-jose</code> saja kita sudah bisa login dengan Google. Konfigurasinya di <code class="language-plaintext highlighter-rouge">application.properties</code> sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spring.security.oauth2.client.registration.google.client-id=266648357609-p7agra2jcbbo360bl95lrg3pu3vsubdo.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret=wA4blZ_SU71yeRjAXKxIBKBe
</code></pre></div></div>
<p>Akan tetapi, karena kita ingin menggunakan permission dari tabel database, maka kita harus menambahkan konfigurasi lagi.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@EnableWebSecurity</span>
<span class="nd">@EnableGlobalMethodSecurity</span><span class="o">(</span><span class="n">prePostEnabled</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">KonfigurasiSecurity</span> <span class="kd">extends</span> <span class="nc">WebSecurityConfigurerAdapter</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">UserDao</span> <span class="n">userDao</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span>
<span class="o">.</span><span class="na">authorizeRequests</span><span class="o">()</span>
<span class="o">.</span><span class="na">anyRequest</span><span class="o">().</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">().</span><span class="na">logout</span><span class="o">().</span><span class="na">permitAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">().</span><span class="na">oauth2Login</span><span class="o">()</span>
<span class="o">.</span><span class="na">userInfoEndpoint</span><span class="o">()</span>
<span class="o">.</span><span class="na">userAuthoritiesMapper</span><span class="o">(</span><span class="n">authoritiesMapper</span><span class="o">())</span>
<span class="o">.</span><span class="na">and</span><span class="o">().</span><span class="na">defaultSuccessUrl</span><span class="o">(</span><span class="s">"/home"</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">GrantedAuthoritiesMapper</span> <span class="nf">authoritiesMapper</span><span class="o">(){</span>
<span class="k">return</span> <span class="o">(</span><span class="n">authorities</span><span class="o">)</span> <span class="o">-></span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">emailAttrName</span> <span class="o">=</span> <span class="s">"email"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">email</span> <span class="o">=</span> <span class="n">authorities</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="nc">OAuth2UserAuthority</span><span class="o">.</span><span class="na">class</span><span class="o">::</span><span class="n">isInstance</span><span class="o">)</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nc">OAuth2UserAuthority</span><span class="o">.</span><span class="na">class</span><span class="o">::</span><span class="n">cast</span><span class="o">)</span>
<span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">userAuthority</span> <span class="o">-></span> <span class="n">userAuthority</span><span class="o">.</span><span class="na">getAttributes</span><span class="o">().</span><span class="na">containsKey</span><span class="o">(</span><span class="n">emailAttrName</span><span class="o">))</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">userAuthority</span> <span class="o">-></span> <span class="n">userAuthority</span><span class="o">.</span><span class="na">getAttributes</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">emailAttrName</span><span class="o">).</span><span class="na">toString</span><span class="o">())</span>
<span class="o">.</span><span class="na">findFirst</span><span class="o">()</span>
<span class="o">.</span><span class="na">orElse</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">email</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">authorities</span><span class="o">;</span> <span class="c1">// data email tidak ada di userInfo dari Google</span>
<span class="o">}</span>
<span class="nc">User</span> <span class="n">user</span> <span class="o">=</span> <span class="n">userDao</span><span class="o">.</span><span class="na">findByUsername</span><span class="o">(</span><span class="n">email</span><span class="o">);</span>
<span class="k">if</span><span class="o">(</span><span class="n">user</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">authorities</span><span class="o">;</span> <span class="c1">// email user ini belum terdaftar di database</span>
<span class="o">}</span>
<span class="nc">Set</span><span class="o"><</span><span class="nc">Permission</span><span class="o">></span> <span class="n">userAuthorities</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="na">getRole</span><span class="o">().</span><span class="na">getPermissions</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">userAuthorities</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">authorities</span><span class="o">;</span> <span class="c1">// authorities defaultnya ROLE_USER</span>
<span class="o">}</span>
<span class="k">return</span> <span class="nc">Stream</span><span class="o">.</span><span class="na">concat</span><span class="o">(</span>
<span class="n">authorities</span><span class="o">.</span><span class="na">stream</span><span class="o">(),</span>
<span class="n">userAuthorities</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Permission:</span><span class="o">:</span><span class="n">getValue</span><span class="o">)</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">SimpleGrantedAuthority:</span><span class="o">:</span><span class="k">new</span><span class="o">)</span>
<span class="o">).</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toCollection</span><span class="o">(</span><span class="nl">ArrayList:</span><span class="o">:</span><span class="k">new</span><span class="o">));</span>
<span class="o">};</span>
<span class="o">}</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">SpringSecurityDialect</span> <span class="nf">springSecurityDialect</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">SpringSecurityDialect</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Mapping untuk permission dari database dengan username dari google diaktifkan dengan baris berikut <code class="language-plaintext highlighter-rouge">oauth2Login().userInfoEndpoint().userAuthoritiesMapper(authoritiesMapper())</code>. Tanpa baris tersebut, user yang login dengan Google akan mendapatkan authority default yaitu <code class="language-plaintext highlighter-rouge">ROLE_USER</code>. Kita ingin menambahkan permission bagi user sesuai isi tabel relasi <code class="language-plaintext highlighter-rouge">user-role-permission</code>.</p>
<h2 id="test-aplikasi">Test Aplikasi</h2>
<p>Untuk menjalankan aplikasi, masuk ke foldernya, kemudian jalankan aplikasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mvn clean spring-boot:run
</code></pre></div></div>
<p>Browse ke <code class="language-plaintext highlighter-rouge">http://localhost:8080</code>, kita akan mendapati link login yang otomatis dibuatkan oleh Spring Boot + Security.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/09-login-page-aplikasi.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/09-login-page-aplikasi.png" alt="Login Screen Aplikasi" /></a></p>
<p>Klik link Google. Bila kita belum login ke layanan Google (Gmail, Youtube, dsb) maka kita akan dimintai login.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/10-login-page-google.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/10-login-page-google.png" alt="Login Screen Google" /></a></p>
<p>Tapi bila kita sudah login, apalagi pakai beberapa akun, maka kita akan disajikan pilihan mau pakai akun yang mana.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/google-sso/11-consent-screen.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/google-sso/11-consent-screen.png" alt="Pilihan Google Account" /></a></p>
<p>Setelah kita login, maka aplikasi akan melakukan flow OAuth <code class="language-plaintext highlighter-rouge">authorization-code</code>, kemudian akan mendapatkan <code class="language-plaintext highlighter-rouge">email</code> user yang berhasil login dari Google. Kemudian aplikasi akan melakukan mapping dari nilai <code class="language-plaintext highlighter-rouge">email</code> tersebut menjadi daftar <code class="language-plaintext highlighter-rouge">String</code> berisi <code class="language-plaintext highlighter-rouge">permisson</code> untuk user yang memiliki email tersebut. Selanjutnya, daftar permission akan diaplikasikan sesuai dengan tampilan screen dan ijin akses ke url tertentu.</p>
<p>Selamat mencoba, semoga bermanfaat. Source code lengkap ada <a href="https://github.com/endymuhardin/belajar-google-sso">di Github</a></p>
Deployment Microservice Kere Hore Bagian 62018-03-09T07:00:00+07:00https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-6<p>Pada <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-1/">artikel terdahulu</a>, kita menggunakan Nginx sebagai reverse proxy. Semua request diterima Nginx dalam bentuk terenkripsi (SSL/TLS), didekripsi, kemudian diteruskan ke masing-masing backend.</p>
<p>Sebetulnya, selain Nginx masih banyak alternatif lain yang bisa dipakai sebagai reverse proxy, diantaranya:</p>
<ul>
<li><a href="http://www.haproxy.org/">HAProxy</a></li>
<li>dan yang paling senior, <a href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html">Apache HTTPD</a></li>
</ul>
<p>Selain reverse proxy biasa, jaman sekarang banyak juga yang menawarkan fitur-fitur lain seperti rate limiting, caching, logging, routing, authentication, dan sebagainya. Beberapa produk yang tersedia diantaranya:</p>
<ul>
<li><a href="https://github.com/Netflix/zuul">Netflix Zuul</a></li>
<li><a href="https://traefik.io/">Traefik</a></li>
<li><a href="https://getkong.org/">Kong</a></li>
<li><a href="https://tyk.io/">Tyk</a></li>
<li><a href="https://www.envoyproxy.io/">Envoy</a></li>
</ul>
<p>Pada artikel ini, kita akan bahas alternatif tradisional dulu, yaitu HAProxy. Seringkali kita hanya membutuhkan solusi sederhana, ringan, dan cepat. Tidak perlu aksesoris macam-macam.</p>
<p>HAProxy adalah aplikasi yang mengkhususkan diri menjadi reverse proxy. Dia bukan webserver seperti Nginx atau Apache HTTPD. Dengan demikian, ukurannya lebih kecil dan ringan. Dia juga memiliki mode <code class="language-plaintext highlighter-rouge">tcp</code> atau <code class="language-plaintext highlighter-rouge">Layer 4</code> buat aplikasi lain yang tidak menggunakan protokol <code class="language-plaintext highlighter-rouge">http</code>.</p>
<p>Kali ini, kita hanya akan menggunakan fitur berikut dari HAProxy:</p>
<ul>
<li>Reverse Proxy</li>
<li>SSL Termination</li>
</ul>
<!--more-->
<p>Tidak seperti Apache HTTPD dan Nginx, LetsEncrypt belum mendukung otomasi pengelolaan sertifikat SSL dengan HAProxy. Untuk itu, kita perlu melakukan sendiri konfigurasi dan otomasinya.</p>
<p>Berikut adalah skema proses permintaan dan perpanjangan sertifikat dengan menggunakan HAProxy.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/25-letsencrypt-haproxy.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/25-letsencrypt-haproxy.jpg" alt="Skema HAProxy" /></a></p>
<p>Kita meminta sertifikat SSL dari LetsEncrypt menggunakan aplikasi yang disebut <code class="language-plaintext highlighter-rouge">certbot</code>. Pada saat dijalankan, prosesnya yang terjadi kira-kira seperti ini:</p>
<ol>
<li>Certbot menjalankan web server di port yang kita tentukan, misalnya port 8888.</li>
<li>Certbot mengirim request permintaan sertifikat ke server LetsEncrypt, misalnya untuk nama domain <code class="language-plaintext highlighter-rouge">app1.artivisi.id</code>.</li>
<li>Server LetsEncrypt akan melakukan request ke <code class="language-plaintext highlighter-rouge">app1.artivisi.id</code> ke port 80 (untuk challenge <code class="language-plaintext highlighter-rouge">http-01</code>) atau port 443 (untuk challenge <code class="language-plaintext highlighter-rouge">tls-sni-01</code>). Port 80 dan 443 ini tidak bisa kita ubah, sudah wajib seperti itu dari LetsEncrypt. Url yang diminta adalah <code class="language-plaintext highlighter-rouge">.well-known/acme-challenge/random-string-disini/</code>. Jadi, LetsEncrypt akan melakukan http request ke <code class="language-plaintext highlighter-rouge">http://app1.artivisi.id/.well-known/acme-challenge/5ArBUB6d-r2lJaJ26NQFxG1zlkoR6GG5TD-Az11vcd8/</code>.</li>
<li>Kita konfigurasi HAProxy agar meneruskan request yang mengarah ke <code class="language-plaintext highlighter-rouge">/.well-known/acme-challenge/*</code> ke port <code class="language-plaintext highlighter-rouge">8888</code>. Di sana Certbot sudah menunggu dan menyediakan URL tersebut. Kita gunakan wildcard <code class="language-plaintext highlighter-rouge">*</code> supaya apapun random string yang digenerate kita tidak perlu mengubah konfigurasi lagi.</li>
</ol>
<p>Konfigurasi untuk langkah <code class="language-plaintext highlighter-rouge">4</code> di HAProxy sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>frontend http-in
bind 0.0.0.0:80
acl letsencrypt-acl path_beg /.well-known/acme-challenge/
use_backend letsencrypt-backend if letsencrypt-acl
backend letsencrypt-backend
server letsencrypt localhost:8888
</code></pre></div></div>
<p>Setelah HAProxy dikonfigurasi seperti di atas, kita jalankan proses permintaan sertifikat dengan Certbot. Certbot akan menjalankan internal web server di port <code class="language-plaintext highlighter-rouge">8888</code>. Perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certbot certonly --standalone --non-interactive --agree-tos --email endy@muhardin.com --http-01-port=8888 -d app.artivisi.id
</code></pre></div></div>
<p>Setelah sertifikat didapatkan, kita gabungkan <code class="language-plaintext highlighter-rouge">fullchain.pem</code> dan <code class="language-plaintext highlighter-rouge">privkey.pem</code> menjadi satu file, misalnya kita beri nama <code class="language-plaintext highlighter-rouge">haproxy.pem</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /etc/letsencrypt/live/app1.artivisi.id
cat fullchain.pem privkey.pem > haproxy.pem
</code></pre></div></div>
<p>Kita daftarkan semua sertifikat ke dalam satu file, misalnya kita beri nama <code class="language-plaintext highlighter-rouge">/etc/haproxy/daftar-sertifikat-ssl.txt</code>. Isinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/letsencrypt/live/app1.artivisi.id/haproxy.pem app1.artivisi.id
/etc/letsencrypt/live/app2.artivisi.id/haproxy.pem app2.artivisi.id
/etc/letsencrypt/live/app3.artivisi.id/haproxy.pem app3.artivisi.id
</code></pre></div></div>
<p>Selanjutnya, kita tinggal pasang daftar file sertifikat tersebut dalam konfigurasi haproxy di file <code class="language-plaintext highlighter-rouge">/etc/haproxy/haproxy.cfg</code>. Isinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
ssl-default-bind-options no-sslv3 no-tls-tickets
ssl-default-bind-ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
tune.ssl.default-dh-param 2048
defaults
log global
mode http
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http-in
bind 0.0.0.0:80
acl letsencrypt-acl path_beg /.well-known/acme-challenge/
use_backend letsencrypt-backend if letsencrypt-acl
frontend https-in
bind 0.0.0.0:443 ssl crt-list /etc/haproxy/daftar-sertifikat-ssl.txt
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"
http-response set-header X-Frame-Options DENY
http-response set-header X-Content-Type-Options nosniff
option httplog
use_backend backend_app1 if { ssl_fc_sni -i app1.artivisi.id }
use_backend backend_app2 if { ssl_fc_sni -i app2.artivisi.id }
use_backend backend_app3 if { ssl_fc_sni -i app3.artivisi.id }
backend backend_app1
redirect scheme https code 301 if !{ ssl_fc }
server app1 localhost:10001 check
backend backend_app2
redirect scheme https code 301 if !{ ssl_fc }
server app2 localhost:10002 check
backend backend_app3
redirect scheme https code 301 if !{ ssl_fc }
server app3 localhost:10003 check
</code></pre></div></div>
<p>Konfigurasi di atas akan menghasilkan hasil akhir yang sama dengan konfigurasi Nginx kita di <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-1/">artikel pertama</a>.</p>
<h2 id="renewal">Renewal</h2>
<p>Sertifikat LetsEncrypt hanya berlaku 3 bulan. Untuk itu kita harus setup pembaruan (renewal) otomatis supaya tidak capek renew sertifikat setiap saat.</p>
<p>Berikut perintah untuk mengetes proses perpanjangan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certbot renew --dry-run
</code></pre></div></div>
<p>Nantinya kita bisa hilangkan opsi <code class="language-plaintext highlighter-rouge">--dry-run</code> untuk menjalankan proses perpanjangan yang asli.</p>
<p>Untuk mengotomasi, kita bisa membuat script sederhana untuk melakukan perpanjangan, kemudian menggabungkan public key dan private key agar siap dipakai oleh HAProxy. Kita buatkan script <code class="language-plaintext highlighter-rouge">/opt/update-certs.sh</code> yang isinya seperti ini</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="c"># lakukan renewal</span>
certbot renew
<span class="c"># gabungkan script</span>
bash <span class="nt">-c</span> <span class="s2">"cat /etc/letsencrypt/live/app1.artivisi.id/fullchain.pem /etc/letsencrypt/live/app1.artivisi.id/privkey.pem > /etc/letsencrypt/live/app1.artivisi.id/haproxy.pem"</span>
<span class="c"># restart haproxy</span>
service haproxy restart
</code></pre></div></div>
<p>Terakhir, kita daftarkan script tersebut supaya dijalankan sebulan sekali. Berikut konfigurasi cron</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 0 1 * * root bash /opt/update-certs.sh
</code></pre></div></div>
<p>Selamat mencoba …</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="https://serversforhackers.com/c/letsencrypt-with-haproxy">https://serversforhackers.com/c/letsencrypt-with-haproxy</a></li>
<li><a href="https://gist.github.com/thisismitch/7c91e9b2b63f837a0c4b">https://gist.github.com/thisismitch/7c91e9b2b63f837a0c4b</a></li>
<li><a href="https://skarlso.github.io/2017/02/15/how-to-https-with-hugo-letsencrypt-haproxy/">https://skarlso.github.io/2017/02/15/how-to-https-with-hugo-letsencrypt-haproxy/</a></li>
<li><a href="https://ops.tips/blog/tls-certificates-haproxy-letsencrypt/">https://ops.tips/blog/tls-certificates-haproxy-letsencrypt/</a></li>
<li><a href="https://fly.io/articles/load-balancing-https-with-lets-encrypt/">https://fly.io/articles/load-balancing-https-with-lets-encrypt/</a></li>
<li><a href="https://blog.georgejose.com/moving-my-http-website-to-https-using-letsencrypt-haproxy-and-docker-deb56ff6be9b">https://blog.georgejose.com/moving-my-http-website-to-https-using-letsencrypt-haproxy-and-docker-deb56ff6be9b</a></li>
<li><a href="https://poweruphosting.com/blog/secure-certbot-haproxy/">https://poweruphosting.com/blog/secure-certbot-haproxy/</a></li>
</ul>
Deployment Microservice Kere Hore Bagian 52018-03-08T07:00:00+07:00https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-5<p>Pada <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-1/">artikel yang lalu</a>, kita sudah membahas tentang penggunaan Nginx sebagai Front Proxy, <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-2/">memasang aplikasi Java</a>, dan <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-4/">aplikasi Node JS</a>. Kali ini, kita akan instal aplikasi dengan bahasa pemrograman Ruby dan framework Ruby on Rails.</p>
<!--more-->
<h2 id="instalasi-ruby">Instalasi Ruby</h2>
<p>Ada banyak tutorial instalasi Ruby di internet. Sebagian besar mengajarkan:</p>
<ul>
<li>instalasi dari source</li>
<li>menggunakan version manager, <code class="language-plaintext highlighter-rouge">rvm</code> atau <code class="language-plaintext highlighter-rouge">rbenv</code></li>
</ul>
<p>Ini disebabkan karena release management dan versioning Ruby yang kacau. Silahkan baca <a href="http://www.lucas-nussbaum.net/blog/?p=617">artikel ini</a> untuk memahami seperti apa yang dimaksud kacau.</p>
<p>Saya tidak terlalu suka melakukan instalasi dari source di mesin production. Untuk instalasi dari source code, kita harus menginstal compiler dan aksesorisnya. Ini menambah resiko security. Seharusnya yang diinstal di server production hanyalah sebatas keperluan menjalankan aplikasi saja.</p>
<p>Oleh karena itu, coba kita cek dulu versi Ruby yang dipaketkan Ubuntu.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># apt-cache show ruby
Package: ruby
Priority: optional
Section: interpreters
Installed-Size: 36
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Antonio Terceiro <terceiro@debian.org>
Architecture: all
Source: ruby-defaults
Version: 1:2.3.0+1
Replaces: irb, rdoc, rubygems
Provides: irb, rdoc, rubygems
Depends: ruby2.3
Suggests: ri, ruby-dev
Conflicts: ruby-activesupport-2.3, ruby-activesupport-3.2
Breaks: apt-listbugs (<< 0.1.6), rbenv (<= 0.4.0-1), ruby-debian (<< 0.3.8+b3), ruby-switch (<= 0.1.0)
Filename: pool/main/r/ruby-defaults/ruby_2.3.0+1_all.deb
Size: 5530
MD5sum: da532a3b540c86b75e53271dcc5e9084
SHA1: 08927ac768f3d922884df2b0b6aa49b9372b03f5
SHA256: 5bf2907339443b092800e1b471612f8024c015434bdd14aa1dcae2a39b9bd7a9
Description-en: Interpreter of object-oriented scripting language Ruby (default version)
Ruby is the interpreted scripting language for quick and easy
object-oriented programming. It has many features to process text
files and to do system management tasks (as in perl). It is simple,
straight-forward, and extensible.
.
This package is a dependency package, which depends on Debian's default Ruby
version (currently v2.3).
Description-md5: 9d5a30084f79740130777ebb18a9beb9
Homepage: http://www.ruby-lang.org/
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Origin: Ubuntu
Supported: 5y
Task: kubuntu-desktop, kubuntu-full, edubuntu-desktop-gnome, ubuntustudio-fonts
</code></pre></div></div>
<p>Versi <code class="language-plaintext highlighter-rouge">2.3</code> sudah memenuhi persyaratan Rails yang minta versi Ruby minimal <code class="language-plaintext highlighter-rouge">2.2.2</code>. Mari kita instal saja bawaan Ubuntu. Tidak perlu pakai version manager. Selain itu, install juga SQLite supaya kita bisa generate project Rails baru.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install ruby sqlite3 libsqlite3-dev
</code></pre></div></div>
<h2 id="instalasi-rails">Instalasi Rails</h2>
<p>Kita upgrade dulu <code class="language-plaintext highlighter-rouge">rubygems</code> supaya menjadi versi terbaru</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem update --system
</code></pre></div></div>
<p>Kemudian, kita install <code class="language-plaintext highlighter-rouge">Rails</code> dan teman-temannya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install rails bundler bindex sqlite3
</code></pre></div></div>
<p>Biasanya kita akan mendapati error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: Error installing rails:
ERROR: Failed to build gem native extension.
current directory: /var/lib/gems/2.3.0/gems/nokogiri-1.8.2/ext/nokogiri
/usr/bin/ruby2.3 -r ./siteconf20180308-14769-vfwrqo.rb extconf.rb
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h
extconf failed, exit code 1
Gem files will remain installed in /var/lib/gems/2.3.0/gems/nokogiri-1.8.2 for inspection.
Results logged to /var/lib/gems/2.3.0/extensions/x86_64-linux/2.3.0/nokogiri-1.8.2/gem_make.out
</code></pre></div></div>
<p>Ini disebabkan karena ada dependensi yang kurang. Ternyata kita tidak bisa menginstall Rails tanpa menginstal compiler C <code class="language-plaintext highlighter-rouge">:(</code></p>
<p>Mari kita install dulu tambahannya,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install gcc make libxslt1-dev libxml2-dev zlib1g-dev
</code></pre></div></div>
<h2 id="membuat-aplikasi-rails">Membuat Aplikasi Rails</h2>
<p>Selanjutnya kita bisa membuat aplikasi Rails. Kita buat saja di folder <code class="language-plaintext highlighter-rouge">/var/lib/app3</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /var/lib
rails new app3
</code></pre></div></div>
<p>Setelah itu, kita bisa coba jalankan aplikasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /var/lib/app3
bin/rails server
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=> Booting Puma
=> Rails 5.1.5 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.3 (ruby 2.3.1-p112), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
</code></pre></div></div>
<p>Kita bisa coba browse ke <code class="language-plaintext highlighter-rouge">http://app3.artivisi.id:3000</code> dan menemui tampilan seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/22-halo-rails.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/22-halo-rails.png" alt="Hello Rails" /></a></p>
<p>Tampilan di atas hanya bisa tampil kalau aplikasinya berjalan di mode <code class="language-plaintext highlighter-rouge">development</code>. Kita perlu membuat controller betulan supaya bisa diakses di mode <code class="language-plaintext highlighter-rouge">production</code>.</p>
<p>Buat class dan method controller untuk menangani request dengan perintah scaffold.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails generate controller welcome index
</code></pre></div></div>
<p>Rails akan membuat file baru di <code class="language-plaintext highlighter-rouge">app/controllers/welcome_controller.rb</code> dan <code class="language-plaintext highlighter-rouge">app/views/welcome/index.html.erb</code>. Untuk menyederhanakan tutorial ini, isi file tersebut tidak perlu kita edit. Biarkan saja apa adanya.</p>
<p>Berikutnya, kita edit konfigurasi routing supaya akses ke root atau url <code class="language-plaintext highlighter-rouge">/</code> ditangani oleh method <code class="language-plaintext highlighter-rouge">index</code> dalam <code class="language-plaintext highlighter-rouge">WelcomeController</code>. Edit sehingga isinya menjadi seperti ini</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="n">root</span> <span class="s2">"welcome#index"</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Selanjutnya, kita akan menjalankan aplikasi tersebut di environment production. Untuk itu kita perlu membuat secret key dulu. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /var/lib/app3
RAILS_ENV=production rake secret
</code></pre></div></div>
<p>Dia akan mengeluarkan random value seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>60dd81d05ccd922b51ea6bbfd34311f82ab24440c69047cade5c46073af96b0dd1e8e74adab6f793a7a8a64963f2ba7b456b9fd8670bbc9f267ec87fd77903a5
</code></pre></div></div>
<p>Nilai ini akan kita gunakan di konfigurasi service nantinya.</p>
<p>Berikutnya, kita juga perlu melakukan compile terhadap file-file asset (JavaScript dan CSS)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RAILS_ENV=production rake assets:precompile
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>I, [2018-03-08T08:09:36.113902 #2030] INFO -- : Writing /var/lib/app3/public/assets/application-4a1d9d80b89c980f5f64004484cb2e515409eb7565c72a78447d2c6be5636082.js
I, [2018-03-08T08:09:36.114869 #2030] INFO -- : Writing /var/lib/app3/public/assets/application-4a1d9d80b89c980f5f64004484cb2e515409eb7565c72a78447d2c6be5636082.js.gz
I, [2018-03-08T08:09:36.133673 #2030] INFO -- : Writing /var/lib/app3/public/assets/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css
I, [2018-03-08T08:09:36.133995 #2030] INFO -- : Writing /var/lib/app3/public/assets/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css.gz
</code></pre></div></div>
<h2 id="konfigurasi-puma-di-systemd">Konfigurasi Puma di Systemd</h2>
<p>Aplikasi Rails kita dijalankan oleh webserver <code class="language-plaintext highlighter-rouge">Puma</code>. Sebetulnya ada beberapa webserver yang bisa juga digunakan sebagai alternatif, misalnya:</p>
<ul>
<li><a href="https://www.phusionpassenger.com/">Passenger</a></li>
<li><a href="https://bogomips.org/unicorn/">Unicorn</a></li>
<li><a href="https://github.com/mongrel/mongrel">Mongrel</a></li>
</ul>
<p>Tapi nampaknya di tahun 2018 ini, yang sedang naik daun adalah Puma. Perbandingannya bisa dibaca di <a href="http://blog.scoutapp.com/articles/2017/02/10/which-ruby-app-server-is-right-for-you">artikel ini</a>.</p>
<p>Untuk menjalankan Puma sebagai system service, kita buat konfigurasinya di file <code class="language-plaintext highlighter-rouge">/etc/systemd/system/app3.service</code> yang isinya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Aplikasi App3
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/var/lib/app3
Environment=RAILS_ENV=production PORT=10003 SECRET_KEY_BASE=60dd81d05ccd922b51ea6bbfd34311f82ab24440c69047cade5c46073af96b0dd1e8e74adab6f793a7a8a64963f2ba7b456b9fd8670bbc9f267ec87fd77903a5
ExecStart=/usr/local/bin/bundle exec --keep-file-descriptors puma
Restart=always
[Install]
WantedBy=multi-user.target
</code></pre></div></div>
<p>Pada konfigurasi di atas, kita memasang environment variable <code class="language-plaintext highlighter-rouge">SECRET_KEY_BASE</code> sesuai yang kita generate pada langkah sebelumnya. Bila ini tidak ada, maka kita akan mendapatkan pesan error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RuntimeError: Missing `secret_key_base` for 'production' environment, set this value in `config/secrets.yml`
</code></pre></div></div>
<p>Jalankan perintah berikut bila kita mengedit file <code class="language-plaintext highlighter-rouge">/etc/systemd/system/app3.service</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl daemon-reload
</code></pre></div></div>
<p>Dan perintah berikut untuk mengaktifkan service supaya start otomatis pada saat booting.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl enable app3
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Created symlink from /etc/systemd/system/multi-user.target.wants/app3.service to /etc/systemd/system/app3.service.
</code></pre></div></div>
<p>Sekarang, kita sudah bisa menjalankan servicenya dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service app3 start
</code></pre></div></div>
<p>Browse ke <code class="language-plaintext highlighter-rouge">http://app3.artivisi.id:10003</code> dan kita akan dapatkan tampilan seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/23-rails-no-https.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/23-rails-no-https.png" alt="Tampilan production tanpa https" /></a></p>
<p>Selanjutnya, kita akan aktifkan konfigurasi reverse proxy di Nginx supaya aplikasi ini bisa diakses dengan <code class="language-plaintext highlighter-rouge">https</code> di port standar, yaitu <code class="language-plaintext highlighter-rouge">443</code>. Bukan lagi port <code class="language-plaintext highlighter-rouge">10003</code>. Setelah reverse proxy diaktifkan, kita bisa mengaksesnya dengan alamat yang terlihat normal, yaitu <code class="language-plaintext highlighter-rouge">https://app3.artivisi.id</code>.</p>
<h2 id="konfigurasi-reverse-proxy-nginx">Konfigurasi Reverse Proxy Nginx</h2>
<p>Konfigurasinya tidak jauh berbeda dengan penjelasan di artikel sebelumnya. Cukup edit blok <code class="language-plaintext highlighter-rouge">location</code> dalam file konfigurasi <code class="language-plaintext highlighter-rouge">/etc/nginx/sites-available/app3.artivisi.id</code> sehingga menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
server_name app3.artivisi.id;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/app3.artivisi.id/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app3.artivisi.id/privkey.pem;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/app3.artivisi.id/html;
index index.php index.html;
location / {
proxy_pass http://localhost:10003;
}
}
server {
if ($host = app3.artivisi.id) {
return 301 https://$host$request_uri;
}
listen 80;
listen [::]:80;
server_name app3.artivisi.id;
return 404;
}
</code></pre></div></div>
<p>Restart nginx dengan perintah <code class="language-plaintext highlighter-rouge">service nginx restart</code> dan browse ke <code class="language-plaintext highlighter-rouge">https://app3.artivisi.id</code>. Hasilnya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/24-rails-https.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/24-rails-https.png" alt="Tampilan production https" /></a></p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah rangkaian artikel mengenai deployment aplikasi microservice yang dilakukan di satu server dengan satu IP public. Dengan cara ini, kita bisa menghemat biaya dengan cara menggunakan satu server untuk banyak aplikasi sekaligus.</p>
<p>Walaupun demikian, jangan lupa untuk melakukan backup dan replikasi agar bila satu server ini mengalami masalah, kegiatan bisnis tetap bisa berjalan dengan lancar.</p>
Deployment Microservice Kere Hore Bagian 42018-02-26T07:00:00+07:00https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-4<p>Pada <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-1/">artikel yang lalu</a>, kita sudah membahas tentang penggunaan Nginx sebagai Front Proxy, <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-2/">memasang aplikasi Java</a>, dan <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-3/">aplikasi Wordpress berbasis PHP</a>. Kali ini kita akan lanjutkan memasang aplikasi berbasis NodeJS dengan framework ExpressJS.</p>
<!--more-->
<p>Sebagai contoh, kita akan membuat aplikasi sederhana menggunakan framework ExpressJS. Kemudian kita akan jalankan sebagai service di port <code class="language-plaintext highlighter-rouge">10002</code>. Terakhir, kita akan setting Nginx untuk meneruskan request yang menuju <code class="language-plaintext highlighter-rouge">app2.artivisi.id</code> ke <code class="language-plaintext highlighter-rouge">localhost:10002</code>.</p>
<p>Berikut langkah-langkahnya:</p>
<ul>
<li>Instalasi NodeJS</li>
<li>Membuat Aplikasi ExpressJS</li>
<li>Menjalankan Aplikasi sebagai Service dengan Systemd</li>
<li>Konfigurasi Nginx untuk memforward request</li>
</ul>
<h2 id="instalasi-nodejs">Instalasi NodeJS</h2>
<p>Untuk distro berbasis Debian dan Ubuntu, developer NodeJS sudah menyediakan script installernya. Langkah-langkahnya dijelaskan di <a href="https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions">website resminya</a></p>
<p>Setelah login ke server, jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
</code></pre></div></div>
<p>Berikutnya, kita buat aplikasi sederhana dengan ExpressJS</p>
<h2 id="aplikasi-expressjs">Aplikasi ExpressJS</h2>
<p>Pertama, kita buat dulu folder projectnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir aplikasijs
cd aplikasijs
</code></pre></div></div>
<p>Kemudian, inisialisasi projectnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm init
</code></pre></div></div>
<p>Dia akan menampilkan beberapa pertanyaan yang harus kita jawab. Seperti ini tampilannya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (aplikasijs)
version: (1.0.0)
description: Aplikasi JavaScript
entry point: (index.js) halo.js
test command:
git repository:
keywords:
author: Endy Muhardin
license: (ISC) ASL
Sorry, license should be a valid SPDX license expression (without "LicenseRef"), "UNLICENSED", or "SEE LICENSE IN <filename>".
license: (ISC) Apache-2.0
About to write to /Users/endymuhardin/tmp/aplikasijs/package.json:
{
"name": "aplikasijs",
"version": "1.0.0",
"description": "Aplikasi JavaScript",
"main": "halo.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Endy Muhardin",
"license": "Apache-2.0"
}
Is this ok? (yes)
</code></pre></div></div>
<p>Berikutnya, install ExpressJS</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install express --save
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install express --save
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN aplikasijs@1.0.0 No repository field.
+ express@4.16.2
added 49 packages in 10.044s
</code></pre></div></div>
<p>Selanjutnya, Hello World dulu. Copy paste kode program berikut, yang diambil dari <a href="https://expressjs.com/en/starter/hello-world.html">dokumentasi ExpressJS</a>. Pasang di file <code class="language-plaintext highlighter-rouge">halo.js</code> sesuai yang kita sebutkan pada waktu inisialisasi project.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">()</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">Hello World!</span><span class="dl">'</span><span class="p">))</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">10002</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Example app listening on port 3000!</span><span class="dl">'</span><span class="p">))</span>
</code></pre></div></div>
<p>Jalankan aplikasinya dari command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node halo.js
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Example app listening on port 10002s!
</code></pre></div></div>
<p>Dan kita bisa browse ke <code class="language-plaintext highlighter-rouge">http://localhost:10002</code>, hasilnya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/18-halo-express-local.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/18-halo-express-local.png" alt="Halo Express" /></a></p>
<p>Folder project kita isinya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/19-folder-project.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/19-folder-project.png" alt="Isi folder project" /></a></p>
<p>File-file berikut akan kita upload ke server untuk dideploy:</p>
<ul>
<li>halo.js</li>
<li>package.json</li>
<li>package-lock.json</li>
</ul>
<p>Sedangkan folder <code class="language-plaintext highlighter-rouge">node_modules</code> tidak perlu, karena bisa digenerate pada waktu build. Jadi kita hapus dulu supaya bisa di-<code class="language-plaintext highlighter-rouge">rsync</code> dengan mudah.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm -rf node_modules
</code></pre></div></div>
<p>Selanjutnya, kita upload ke server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -avzP ./ root@app2.artivisi.id:/var/lib/aplikasijs/
</code></pre></div></div>
<p>Kemudian kita login ke server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh root@app2.artivisi.id
</code></pre></div></div>
<p>Masuk ke folder aplikasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /var/lib/aplikasijs
</code></pre></div></div>
<p>Kemudian instal semua dependensi aplikasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install
</code></pre></div></div>
<p>Test jalankan lagi di server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm halo.js
</code></pre></div></div>
<p>Dan pastikan aplikasi sudah bisa diakses di http://app2.artivisi.id:10002</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/20-halo-express-server-10002.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/20-halo-express-server-10002.png" alt="Aplikasi jalan di server 10002" /></a></p>
<h2 id="menjalankan-aplikasi-dengan-systemd">Menjalankan Aplikasi dengan Systemd</h2>
<p>Bila kita jalankan aplikasi dengan perintah <code class="language-plaintext highlighter-rouge">node halo.js</code> seperti di atas,
Ada beberapa aplikasi tambahan untuk menjalankan aplikasi NodeJS sebagai service, diantaranya:</p>
<ul>
<li>PM2</li>
<li>Forever</li>
<li>Nodemon</li>
</ul>
<p>Pada saat artikel ini ditulis, yang paling mainstream adalah PM2, seperti dijelaskan pada <a href="https://ifelse.io/2015/09/02/running-node.js-apps-in-production/">artikel ini</a>. Walaupun demikian, melihat kegemaran komunitas JavaScript untuk me-rewrite sesuatu berulang-ulang, tidak menutup kemungkinan besok PM2 sudah digusur aplikasi lain. Jadi sementara ini, kita akan gunakan PM2. Bila Anda membaca artikel ini setahun kemudian, silahkan googling lagi untuk mencari solusi deployment yang lebih up-to-date.</p>
<p>Kita install dulu <code class="language-plaintext highlighter-rouge">PM2</code> di server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo npm install -g pm2
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm WARN registry Unexpected warning for https://registry.npmjs.org/: Miscellaneous Warning EINTEGRITY: sha1-QFUCsAfzGcP0cXXER0UnMA8qta0= integrity checksum failed when using sha1: wanted sha1-QFUCsAfzGcP0cXXER0UnMA8qta0= but got sha512-zr6QQnzLt3Ja0t0XI8gws2kn7zV2p0l/D3kreNvS6hFZhVU5g+uY/30l42jbgt0XGcNBEmBDGJR71J692V92tA==. (260 bytes)
npm WARN registry Using stale package data from https://registry.npmjs.org/ due to a request error during revalidation.
/usr/bin/pm2 -> /usr/lib/node_modules/pm2/bin/pm2
/usr/bin/pm2-dev -> /usr/lib/node_modules/pm2/bin/pm2-dev
/usr/bin/pm2-docker -> /usr/lib/node_modules/pm2/bin/pm2-docker
/usr/bin/pm2-runtime -> /usr/lib/node_modules/pm2/bin/pm2-runtime
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.1.3 (node_modules/pm2/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
+ pm2@2.10.1
added 243 packages in 12.232s
</code></pre></div></div>
<p>Berikutnya, jalankan aplikasi kita dengan PM2</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pm2 start halo.js
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
-------------
__/\\\\\\\\\\\\\____/\\\\____________/\\\\____/\\\\\\\\\_____
_\/\\\/////////\\\_\/\\\\\\________/\\\\\\__/\\\///////\\\___
_\/\\\_______\/\\\_\/\\\//\\\____/\\\//\\\_\///______\//\\\__
_\/\\\\\\\\\\\\\/__\/\\\\///\\\/\\\/_\/\\\___________/\\\/___
_\/\\\/////////____\/\\\__\///\\\/___\/\\\________/\\\//_____
_\/\\\_____________\/\\\____\///_____\/\\\_____/\\\//________
_\/\\\_____________\/\\\_____________\/\\\___/\\\/___________
_\/\\\_____________\/\\\_____________\/\\\__/\\\\\\\\\\\\\\\_
_\///______________\///______________\///__\///////////////__
Community Edition
Production Process Manager for Node.js applications
with a built-in Load Balancer.
Start and Daemonize any application:
$ pm2 start app.js
Load Balance 4 instances of api.js:
$ pm2 start api.js -i 4
Monitor in production:
$ pm2 monitor
Make pm2 auto-boot at server restart:
$ pm2 startup
To go further checkout:
http://pm2.io/
-------------
[PM2] Spawning PM2 daemon with pm2_home=/root/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /var/lib/aplikasijs/halo.js in fork_mode (1 instance)
[PM2] Done.
┌──────────┬────┬──────┬───────┬────────┬─────────┬────────┬─────┬───────────┬──────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │
├──────────┼────┼──────┼───────┼────────┼─────────┼────────┼─────┼───────────┼──────┼──────────┤
│ halo │ 0 │ fork │ 24481 │ online │ 0 │ 0s │ 1% │ 23.1 MB │ root │ disabled │
└──────────┴────┴──────┴───────┴────────┴─────────┴────────┴─────┴───────────┴──────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app
</code></pre></div></div>
<p>Lalu, kita daftarkan PM2 ke <code class="language-plaintext highlighter-rouge">systemd</code> supaya langsung jalan waktu booting.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pm2 startup systemd
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[PM2] Init System found: systemd
Platform systemd
Template
[Unit]
Description=PM2 process manager
Documentation=https://pm2.keymetrics.io/
After=network.target
[Service]
Type=forking
User=root
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/usr/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PM2_HOME=/root/.pm2
PIDFile=/root/.pm2/pm2.pid
ExecStart=/usr/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/lib/node_modules/pm2/bin/pm2 kill
[Install]
WantedBy=multi-user.target
Target path
/etc/systemd/system/pm2-root.service
Command list
[ 'systemctl enable pm2-root',
'systemctl start pm2-root',
'systemctl daemon-reload',
'systemctl status pm2-root' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
[PM2] Making script booting at startup...
>>> Executing systemctl enable pm2-root
Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service.
[DONE]
>>> Executing systemctl start pm2-root
[DONE]
>>> Executing systemctl daemon-reload
[DONE]
>>> Executing systemctl status pm2-root
● pm2-root.service - PM2 process manager
Loaded: loaded (/etc/systemd/system/pm2-root.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2018-02-26 09:57:47 UTC; 135ms ago
Docs: https://pm2.keymetrics.io/
Main PID: 24471 (PM2 v2.10.1: Go)
CGroup: /system.slice/pm2-root.service
‣ 24471 PM2 v2.10.1: God Daemon (/root/.pm2)
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: [PM2] Restoring processes located in /root/.pm2/dump.pm2
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: [PM2][ERROR] Failed to read dump file in /root/.pm2/dump.pm2.bak
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: [PM2][ERROR] No processes saved; DUMP file doesn't exist
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: ┌──────────┬────┬──────┬───────┬────────┬─────────┬────────┬─────┬───────────┬──────┬──────────┐
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: │ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: ├──────────┼────┼──────┼───────┼────────┼─────────┼────────┼─────┼───────────┼──────┼──────────┤
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: │ halo │ 0 │ fork │ 24481 │ online │ 0 │ 53s │ 0% │ 37.6 MB │ root │ disabled │
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: └──────────┴────┴──────┴───────┴────────┴─────────┴────────┴─────┴───────────┴──────┴──────────┘
Feb 26 09:57:47 apps.artivisi.id pm2[24536]: Use `pm2 show <id|name>` to get more details about an app
Feb 26 09:57:47 apps.artivisi.id systemd[1]: Started PM2 process manager.
[DONE]
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save
[PM2] Remove init script via:
$ pm2 unstartup systemd
</code></pre></div></div>
<p>Test reboot servernya, untuk memastikan aplikasinya otomatis jalan.</p>
<h2 id="konfigurasi-reverse-proxy-nginx">Konfigurasi Reverse Proxy Nginx</h2>
<p>Caranya sama dengan konfigurasi reverse proxy aplikasi Spring Boot di <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-2/">artikel terdahulu</a>.</p>
<p>Kita tinggal edit baris <code class="language-plaintext highlighter-rouge">location</code> sehingga menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location / {
proxy_pass http://localhost:10001;
}
</code></pre></div></div>
<p>Isi file lengkapnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
server_name app2.artivisi.id;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/app2.artivisi.id/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app2.artivisi.id/privkey.pem;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/app2.artivisi.id/html;
index index.php index.html;
location / {
proxy_pass http://localhost:10002;
}
}
server {
if ($host = app2.artivisi.id) {
return 301 https://$host$request_uri;
}
listen 80;
listen [::]:80;
server_name app2.artivisi.id;
return 404;
}
</code></pre></div></div>
<p>Restart nginx, kemudian ketik <code class="language-plaintext highlighter-rouge">app2.artivisi.id</code> di browser. Harusnya browser akan melakukan redirect ke <code class="language-plaintext highlighter-rouge">https://app2.artivisi.id</code> dan menampilkan tampilan aplikasi kita.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/21-halo-express-server-final.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/21-halo-express-server-final.png" alt="https://app2.artivisi.id" /></a></p>
<h2 id="penutup">Penutup</h2>
<p>Demikian cara deployment aplikasi NodeJS dibalik Nginx. Pada <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-5/">artikel berikut</a> kita akan setup aplikasi Ruby dengan framework Rails di domain <code class="language-plaintext highlighter-rouge">app3.artivisi.id</code>. Stay tuned …</p>
Deployment Microservice Kere Hore Bagian 32018-02-14T07:00:00+07:00https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-3<p>Pada <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-1/">artikel yang lalu</a>, kita sudah membahas tentang penggunaan Nginx sebagai Front Proxy, dan <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-2/">memasang aplikasi Java di belakang proxy</a> tersebut. Kali ini kita akan memasang aplikasi PHP langsung di Nginx. Agar tidak membuang waktu membuat aplikasi, kita akan gunakan aplikasi yang sudah ada dan banyak digunakan orang, yaitu Wordpress. Aplikasi ini bisa diunduh <a href="https://wordpress.org">di websitenya</a> secara gratis. Petunjuk instalasi juga sudah disediakan <a href="https://codex.wordpress.org/Installing_WordPress">di dokumentasinya</a>. Tinggal kita ikuti saja.</p>
<!--more-->
<h2 id="persiapan-server">Persiapan Server</h2>
<p>Wordpress berjalan di atas PHP, sehingga kita perlu install PHP dulu. Kita gunakan versi 7.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install php-fpm php-mysql php-curl php-gd php-mbstring php-mcrypt php-xml php-xmlrpc
</code></pre></div></div>
<p>Kemudian, ada setting yang harus kita disable supaya lebih secure. Edit file <code class="language-plaintext highlighter-rouge">/etc/php/7.0/fpm/php.ini</code> dan disable <code class="language-plaintext highlighter-rouge">cgi.fix_pathinfo</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cgi.fix_pathinfo=0
</code></pre></div></div>
<p>Setelah itu, restart PHP.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service php7.0-fpm restart
</code></pre></div></div>
<p>Kita perlu memberi tahu Nginx agar memproses file PHP. Tanpa konfigurasi ini, begitu kita akses file PHP, misalnya <code class="language-plaintext highlighter-rouge">https://wp.artivisi.id/coba.php</code> maka file tersebut akan ter-unduh, bukannya dijalankan. Sebagai contoh, kita buat dulu file <code class="language-plaintext highlighter-rouge">coba.php</code> berisi sebagai berikut</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="nb">phpinfo</span><span class="p">();</span>
</code></pre></div></div>
<p>Simpan file tersebut di <code class="language-plaintext highlighter-rouge">/var/www/wp.artivisi.id/html</code>.</p>
<p>Bila kita akses di alamat <code class="language-plaintext highlighter-rouge">https://wp.artivisi.id/coba.php</code>, file tersebut akan ter-unduh seperti kita unduh file <code class="language-plaintext highlighter-rouge">zip</code>. Supaya diproses oleh Nginx, tambahkan konfigurasi berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}
</code></pre></div></div>
<p>Sehingga lengkapnya menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
server_name wp.artivisi.id;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/wp.artivisi.id/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/wp.artivisi.id/privkey.pem;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/wp.artivisi.id/html;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}
}
server {
if ($host = wp.artivisi.id) {
return 301 https://$host$request_uri;
}
listen 80;
listen [::]:80;
server_name wp.artivisi.id;
return 404;
}
</code></pre></div></div>
<p>Reload Nginx</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service nginx reload
</code></pre></div></div>
<p>Dan harusnya kita sudah bisa menjalankan file <code class="language-plaintext highlighter-rouge">coba.php</code> tadi.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/12-phpinfo.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/12-phpinfo.png" alt="PHP Info" /></a></p>
<h2 id="persiapan-database">Persiapan Database</h2>
<p>Kita perlu membuatkan database dan user untuk mengakses database tersebut. Login dulu ke MySQL command prompt</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.21-0ubuntu0.16.04.1 (Ubuntu)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
</code></pre></div></div>
<p>Buatkan databasenya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql> create database wordpressdb;
Query OK, 1 row affected (0.00 sec)
</code></pre></div></div>
<p>Lalu kita buatkan usernya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql> grant all on wordpressdb.* to wordpressdbuser@localhost identified by 'abCdqwErty2213';
Query OK, 0 rows affected, 1 warning (0.01 sec)
</code></pre></div></div>
<p>Database sudah siap digunakan.</p>
<h2 id="instalasi-wordpress">Instalasi Wordpress</h2>
<p>Sebelum mulai menginstall Wordpress, ada beberapa konfigurasi Nginx yang perlu disesuaikan. Edit file <code class="language-plaintext highlighter-rouge">/etc/nginx/sites-enabled/wp.artivisi.id</code> dan tambahkan baris berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { log_not_found off; access_log off; allow all; }
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
</code></pre></div></div>
<p>Kemudian, edit juga blok <code class="language-plaintext highlighter-rouge">location /</code> menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location / {
try_files $uri $uri/ /index.php$is_args$args;
}
</code></pre></div></div>
<p>Jangan lupa restart Nginx agar konfigurasi tersebut diproses.</p>
<p>Selanjutnya, kita unduh aplikasi Wordpress. Taruh saja di folder <code class="language-plaintext highlighter-rouge">/tmp</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /tmp
curl -O https://wordpress.org/latest.tar.gz
</code></pre></div></div>
<p>Kemudian, kita extract isinya dan pindahkan ke folder virtual host kita</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tar xzvf latest.tar.gz
mv wordpress/* /var/www/wp.artivisi.id/html/
</code></pre></div></div>
<p>Pindah ke folder tersebut, lalu rename contoh file konfigurasi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /var/www/wp.artivisi.id/html/
mv wp-config-sample.php wp-config.php
</code></pre></div></div>
<p>Edit file konfigurasi <code class="language-plaintext highlighter-rouge">wp-config.php</code>, isikan informasi koneksi database.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ** MySQL settings - You can get this info from your web host ** //</span>
<span class="cd">/** The name of the database for WordPress */</span>
<span class="nb">define</span><span class="p">(</span><span class="s1">'DB_NAME'</span><span class="p">,</span> <span class="s1">'wordpressdb'</span><span class="p">);</span>
<span class="cd">/** MySQL database username */</span>
<span class="nb">define</span><span class="p">(</span><span class="s1">'DB_USER'</span><span class="p">,</span> <span class="s1">'wordpressdbuser'</span><span class="p">);</span>
<span class="cd">/** MySQL database password */</span>
<span class="nb">define</span><span class="p">(</span><span class="s1">'DB_PASSWORD'</span><span class="p">,</span> <span class="s1">'abCdqwErty2213'</span><span class="p">);</span>
<span class="cd">/** MySQL hostname */</span>
<span class="nb">define</span><span class="p">(</span><span class="s1">'DB_HOST'</span><span class="p">,</span> <span class="s1">'localhost'</span><span class="p">);</span>
<span class="cd">/** Database Charset to use in creating database tables. */</span>
<span class="nb">define</span><span class="p">(</span><span class="s1">'DB_CHARSET'</span><span class="p">,</span> <span class="s1">'utf8'</span><span class="p">);</span>
</code></pre></div></div>
<p>Kalau sudah, kita bisa langsung browse ke <code class="language-plaintext highlighter-rouge">https://wp.artivisi.id</code> untuk melanjutkan proses instalasi.</p>
<p>Di sana kita sudah ditunggu oleh layar instalasi. Kita akan diminta memasukkan nama website, username, password, dan email.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/13-layar-instalasi-wp.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/13-layar-instalasi-wp.png" alt="Layar instalasi wordpress" /></a></p>
<p>Klik <code class="language-plaintext highlighter-rouge">Install</code>, dan Wordpress sudah siap digunakan.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/14-instalasi-sukses.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/14-instalasi-sukses.png" alt="Instalasi Sukses" /></a></p>
<p>Klik tombol Login</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/15-login-wp.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/15-login-wp.png" alt="Halaman Login" /></a></p>
<p>Kita sudah bisa login ke halaman admin</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/16-admin-wp.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/16-admin-wp.png" alt="Halaman Admin" /></a></p>
<p>Kita juga bisa langsung melihat halaman depan website kita</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/17-frontpage-wp.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/17-frontpage-wp.png" alt="Tampilan website" /></a></p>
<h2 id="penutup">Penutup</h2>
<p>Sampai di sini, kita sudah bisa menjalankan aplikasi Java dan PHP dalam satu host. Tunggu <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-4/">artikel berikutnya untuk cara menambah aplikasi NodeJS</a>.</p>
Deployment Microservice Kere Hore Bagian 22018-02-13T07:00:00+07:00https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-2<p>Pada <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-1/">artikel sebelumnya</a>, kita telah menyiapkan front proxy yang sudah berfungsi dengan baik. Kali ini, kita akan membuat aplikasi Java sederhana dengan Spring Boot yang kemudian akan kita pasang di VPS kita kemarin dengan nama domain <code class="language-plaintext highlighter-rouge">app1.artivisi.id</code>.</p>
<!--more-->
<h2 id="setup-project">Setup Project</h2>
<p>Setup project Spring Boot sudah sering dibahas di blog ini. Silahkan lihat-lihat bagian arsip untuk lebih detailnya. Beberapa hal yang harus diperhatikan mengenai deployment terutama adalah mengaktifkan systemd startup script agar aplikasi kita bisa diinstal sebagai service.</p>
<p>Buat konfigurasi <code class="language-plaintext highlighter-rouge">executable</code> dengan nilai <code class="language-plaintext highlighter-rouge">true</code> berikut di <code class="language-plaintext highlighter-rouge">pom.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><build></span>
<span class="nt"><plugins></span>
<span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><configuration></span>
<span class="nt"><executable></span>true<span class="nt"></executable></span>
<span class="nt"></configuration></span>
<span class="nt"></plugin></span>
<span class="nt"></plugins></span>
<span class="nt"></build></span>
</code></pre></div></div>
<p>Setelah itu, kita build aplikasinya, dan upload ke server.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn clean package -DskipTests
scp target/*jar root@app1.artivisi.id:/root/
</code></pre></div></div>
<p>File aplikasi sudah siap deploy di folder <code class="language-plaintext highlighter-rouge">/root</code> di server kita. Selanjutnya, kita akan melanjutkan pekerjaan di server. SSH dulu ke servernya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh root@app1.artivisi.id
</code></pre></div></div>
<h2 id="persiapan-database-mysql">Persiapan Database MySQL</h2>
<p>Biasanya, aplikasi menyimpan datanya di database. Sebagai contoh, kita gunakan database MySQL yang banyak dipakai orang sedunia. Install dulu databasenya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install mysql-server -y
</code></pre></div></div>
<p>Kita akan diminta memasukkan password untuk user <code class="language-plaintext highlighter-rouge">root</code>. Masukkan apa yang biasa digunakan. Lalu klik Ok.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/09-mysql-root.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/09-mysql-root.png" alt="Setup Root Password" /></a></p>
<p>Selanjutnya, login ke MySQL dengan user <code class="language-plaintext highlighter-rouge">root</code> yang sudah kita isikan passwordnya barusan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.21-0ubuntu0.16.04.1 (Ubuntu)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
</code></pre></div></div>
<p>Kita buatkan database dengan nama <code class="language-plaintext highlighter-rouge">appspringdb</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql> create database appspringdb;
Query OK, 1 row affected (0.00 sec)
</code></pre></div></div>
<p>Lalu kita buatkan usernya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql> grant all on appspringdb.* to appspringdbuser@localhost identified by 'abCdqwErty2213';
Query OK, 0 rows affected, 1 warning (0.01 sec)
</code></pre></div></div>
<p>Database sudah siap digunakan.</p>
<h2 id="deployment-aplikasi-spring-boot">Deployment Aplikasi Spring Boot</h2>
<p>Sebelum mendeploy aplikasi, kita pastikan dulu <code class="language-plaintext highlighter-rouge">Java SDK</code> dan <code class="language-plaintext highlighter-rouge">haveged</code> sudah terinstal</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install openjdk-8-jdk-headless haveged -y
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">haveged</code> adalah generator entropi yang dibutuhkan untuk menghasilkan random number. Bila ini lupa dipasang, aplikasi kita akan seolah hang pada saat membutuhkan random number.</p>
<p>Kebiasaan orang dalam mendeploy aplikasi berbeda-beda. Ada yang menaruhnya di folder <code class="language-plaintext highlighter-rouge">/home</code>, <code class="language-plaintext highlighter-rouge">/opt</code>, dan sebagainya. Saya sendiri biasanya menaruh aplikasi di folder <code class="language-plaintext highlighter-rouge">/var/lib/</code>. Untuk itu, kita buatkan folder untuk aplikasinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /var/lib/appspringsaya
</code></pre></div></div>
<p>Kemudian kita pindahkan file <code class="language-plaintext highlighter-rouge">*.jar</code> aplikasi kita yang sudah diupload tadi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mv /root/*jar /var/lib/appspringsaya/
</code></pre></div></div>
<p>Biasanya saya membuat symlink dari nama file aplikasi yang ada nomor versinya (misalnya <code class="language-plaintext highlighter-rouge">appspringsaya-1.0.0-RELEASE.jar</code>) menjadi nama file yang polos (misalnya <code class="language-plaintext highlighter-rouge">appspringsaya.jar</code>). Dengan cara ini, bila ternyata versi yang baru dideploy ada bug critical, kita bisa dengan mudah mematikan aplikasi dan mengganti symlink tersebut untuk mengarah ke versi lama.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ln -s /var/lib/appspringsaya/appspringsaya-1.0.0-RELEASE.jar /var/lib/appspringsaya/appspringsaya.jar
</code></pre></div></div>
<p>Konfigurasi production biasanya berbeda dengan konfigurasi development di laptop kita. Kita bisa membuat file <code class="language-plaintext highlighter-rouge">application.properties</code> di folder yang sama dengan file aplikasi. Spring Boot secara otomatis memprioritaskan file yang di folder ini dibandingkan file konfigurasi yang ada dalam jar, yaitu yang kita pakai selama development dan deployment di laptop. Jadi kita bisa membundel <code class="language-plaintext highlighter-rouge">application.properties</code> yang lengkap dalam <code class="language-plaintext highlighter-rouge">jar</code>, dan hanya mengganti nilai yang berbeda saja di <code class="language-plaintext highlighter-rouge">application.properties</code> dalam folder.</p>
<p>Berikut isi <code class="language-plaintext highlighter-rouge">application.properties</code>, biasanya berisi konfigurasi database yang pastinya berbeda dengan pada waktu mengetes di laptop.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Port Production
server.port = 10001
# Konfigurasi Koneksi Database
spring.datasource.url=jdbc:mysql://localhost/appspringdb
spring.datasource.username=appspringdbuser
spring.datasource.password=abCdqwErty2213
</code></pre></div></div>
<p>Kita juga setup portnya agar aplikasi berjalan di <code class="language-plaintext highlighter-rouge">10001</code>.</p>
<p>Selanjutnya, kita bisa test jalankan aplikasinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar appspringsaya.jar
</code></pre></div></div>
<p>Kita bisa coba browse ke <code class="language-plaintext highlighter-rouge">http://app1.artivisi.id:10001</code> untuk memastikan aplikasi kita berjalan dengan baik. Harusnya dia mengeluarkan tampilan seperti ini.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/10-app-spring-standalone.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/10-app-spring-standalone.png" alt="Aplikasi Spring Boot 10001" /></a></p>
<p>Perhatikan alamat URLnya, kita masih menggunakan port <code class="language-plaintext highlighter-rouge">10001</code> dan tidak menggunakan <code class="language-plaintext highlighter-rouge">https</code>.</p>
<h2 id="membuat-user-os-untuk-aplikasi">Membuat User OS untuk Aplikasi</h2>
<p>Pada langkah pengetesan di atas, kita menjalankan aplikasi dengan user <code class="language-plaintext highlighter-rouge">root</code>. Cara seperti ini kurang aman, karena bila ada kelemahan security pada aplikasi kita, orang jahat bisa langsung mengambil alih sistem, karena dia memiliki akses user <code class="language-plaintext highlighter-rouge">root</code> yang memiliki ijin tidak terbatas.</p>
<p>Untuk itu, kita akan membuat user khusus untuk menjalankan aplikasi ini. Misalnya kita akan membuat user dengan nama <code class="language-plaintext highlighter-rouge">aplikasi</code> yang folder <code class="language-plaintext highlighter-rouge">home</code>nya adalah folder tempat aplikasi diinstal, yaitu di <code class="language-plaintext highlighter-rouge">/var/lib/appspringsaya</code>. Berikut perintahnya, jalankan sebagai <code class="language-plaintext highlighter-rouge">root</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>useradd -d /var/lib/appspringsaya -s /bin/bash --system aplikasi
</code></pre></div></div>
<p>Setelah itu, kita ganti kepemilikan file dan folder aplikasi, yang tadinya dimiliki root, menjadi miliknya user <code class="language-plaintext highlighter-rouge">aplikasi</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chown -R aplikasi:aplikasi /var/lib/appspringsaya
</code></pre></div></div>
<p>User dan kepemilikan file/folder sudah selesai, selanjutnya kita konfigurasi system service.</p>
<h2 id="setup-systemd-service">Setup Systemd Service</h2>
<p>Bila kita jalankan dengan perintah <code class="language-plaintext highlighter-rouge">java -jar</code>, aplikasi kita akan mati bila kita logout, apalagi kalau server kita restart. Untuk itu kita perlu menginstalnya menjadi service agar bisa jalan otomatis pada waktu server dinyalakan.</p>
<p>Registrasi service yang mainstream di Ubuntu dan CentOS adalah <code class="language-plaintext highlighter-rouge">systemd</code>. Pendaftarannya dilakukan dengan membuat file <code class="language-plaintext highlighter-rouge">/etc/systemd/system/appspringsaya.service</code> dengan isi sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Aplikasi Spring Saya
After=syslog.target
[Service]
User=aplikasi
Environment=SPRING_PROFILES_ACTIVE=production
ExecStart=/var/lib/appspringsaya/appspringsaya.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
</code></pre></div></div>
<p>Lalu enable dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl enable appspringsaya.service
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Created symlink from /etc/systemd/system/multi-user.target.wants/appspringsaya.service to /etc/systemd/system/appspringsaya.service.
</code></pre></div></div>
<p>Kita bisa jalankan servicenya dengan perintah :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service appspringsaya start
</code></pre></div></div>
<p>Log bisa dipantau dengan perintah berikut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tail -f /var/log/syslog
</code></pre></div></div>
<p>Kita bisa test restart VPS dan pastikan aplikasi kita jalan dengan baik walaupun habis restart.</p>
<h2 id="konfigurasi-reverse-proxy-nginx">Konfigurasi Reverse Proxy Nginx</h2>
<p>Tentunya kita tidak mau mengakses aplikasi kita tanpa <code class="language-plaintext highlighter-rouge">https</code> dan di port yang tidak lazim. Untuk itu, kita akan buat konfigurasi di Nginx agar semua request ke <code class="language-plaintext highlighter-rouge">https://app1.artivisi.id</code> diarahkan ke <code class="language-plaintext highlighter-rouge">http://localhost:10001</code>. Nantinya port <code class="language-plaintext highlighter-rouge">10001</code> akan kita tutup di firewall agar tidak bisa diakses langsung oleh user.</p>
<p>Buka konfigurasi <code class="language-plaintext highlighter-rouge">/etc/nginx/sites-enabled/app1.artivisi.id</code> dan tambahkan baris berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>proxy_pass http://localhost:10001;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
</code></pre></div></div>
<p>Sehingga menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
server_name app1.artivisi.id;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/app1.artivisi.id/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app1.artivisi.id/privkey.pem;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/app1.artivisi.id/html;
index index.php index.html;
location / {
proxy_pass http://localhost:10001;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
}
}
server {
if ($host = app1.artivisi.id) {
return 301 https://$host$request_uri;
}
listen 80;
listen [::]:80;
server_name app1.artivisi.id;
return 404;
}
</code></pre></div></div>
<p>Coba akses ke <code class="language-plaintext highlighter-rouge">https://app1.artivisi.id</code>. Seharusnya kita akan mendapatkan tampilan aplikasi Spring Boot</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/11-spring-behind-nginx.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/11-spring-behind-nginx.png" alt="Spring Boot dibalik Nginx" /></a></p>
<h2 id="penutup">Penutup</h2>
<p>Demikian cara konfigurasi Spring Boot dibalik Nginx. Pada artikel berikut kita akan <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-3/">setup Wordpress</a> di domain <code class="language-plaintext highlighter-rouge">wp.artivisi.id</code>. Stay tuned …</p>
Deployment Microservice Kere Hore Bagian 12018-02-12T07:00:00+07:00https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-1<p>Arsitektur Microservices saat ini sedang ngetren. Kami di ArtiVisi juga sudah mengadopsi arsitektur ini dalam beberapa projec terakhir. Dan bahkan sudah mengadakan trainingnya. Dilihat dari sisi development dan deployment, microservice memang memungkinkan organisasi untuk bisa membuat aplikasi sesuai kebutuhan dengan cepat. Proses yang dulunya memakan waktu berbulan-bulan dari konsep sampai go-live, sekarang bisa dipercepat menjadi beberapa minggu saja.</p>
<p>Salah satu ciri khas dari arsitektur microservices adalah ada banyak aplikasi kecil-kecil yang saling berkomunikasi satu dengan lainnya. Dengan demikian, kita dituntut untuk banyak mendeploy aplikasi. Masing-masing aplikasi juga bisa dibuat dengan teknologi yang berbeda-beda sesuai dengan kebutuhan dan trend masa kini.</p>
<p>Kalau mau diikuti idealnya, satu VPS berisi satu aplikasi. Atau satu docker container berisi satu aplikasi, dan satu VPS bisa berisi banyak docker container. Akan tetapi tentu saja kita tidak berada di dunia yang serba ideal. Ada kalanya kita harus berkompromi dengan budget, sehingga satu VPS harus rela menghosting banyak aplikasi sekaligus.</p>
<p>Pada artikel kali ini, kita akan mendeploy beberapa aplikasi microservices dalam satu host karena keterbatasan budget. Walaupun kantong kere, hanya mampu sewa satu VPS, tapi bisa tetap hore dengan arsitektur jaman now :D</p>
<p>Satu VPS biasanya hanya punya satu IP public. Dengan keterbatasan ini, maka kita perlu sedikit berakrobat supaya semua aplikasi kita bisa berbagai pakai port-port penting seperti misalnya port HTTPS (443). Sebagai studi kasus, pada artikel ini kita akan mendeploy beberapa aplikasi yang dibuat dengan bahasa pemrograman dan framework berbeda, yaitu Java dengan Spring Boot, NodeJS dengan Express, Ruby dengan Rails, dan tidak ketinggalan aplikasi sejuta umat Wordpress yang dibuat dengan PHP. Kita ingin mengakses keempatnya dengan alamat sebagai berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">https://app1.artivisi.id</code> : Aplikasi Java dengan Spring Boot</li>
<li><code class="language-plaintext highlighter-rouge">https://app2.artivisi.id</code> : Aplikasi NodeJS dengan ExpressJS</li>
<li><code class="language-plaintext highlighter-rouge">https://app3.artivisi.id</code> : Aplikasi Ruby dengan RubyOnRails</li>
<li><code class="language-plaintext highlighter-rouge">https://wp.artivisi.id</code> : Aplikasi PHP dengan Wordpress</li>
</ul>
<p>Keempat aplikasi akan dihosting/dijalankan di satu mesin yang sama. Saya akan gunakan VPS termurah yang disediakan <a href="https://m.do.co/c/c5449509c33a">Digital Ocean</a>, seharga $5 sebulan.</p>
<p>Pada bagian pertama ini, kita akan membahas cara setup VPS dan Nginx sebagai Front Proxy. Artikel selanjutnya akan membahas implementasi masing-masing backend.</p>
<!--more-->
<p>Tentunya tidak mungkin keempat aplikasi kita install di port 443 semua. Oleh karena itu kita harus menggunakan front proxy. Ada beberapa pilihan:</p>
<ul>
<li>Apache HTTPD</li>
<li>Nginx</li>
<li>HAProxy</li>
<li>Netflix Zuul</li>
<li>dan sebagainya</li>
</ul>
<p>Kemudian, kita harus memutuskan di mana kita ingin memasang sertifikat HTTPS. Bisa di masing-masing aplikasi, bisa juga disatukan di front proxy. Saya lebih suka setting di front proxy, supaya pengelolaannya lebih mudah.</p>
<p>Jadi, arsitektur yang kita akan buat kira-kira seperti ini:</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/00-microservice-deployment.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/00-microservice-deployment.jpg" alt="Microservice Deployment" /></a></p>
<p>Walaupun di gambar terlihat ada 5 server, aslinya itu semua akan kita deploy ke satu tempat.</p>
<p>Ada beberapa istilah yang perlu diketahui dulu untuk membuat deployment seperti di atas:</p>
<ul>
<li>Virtual Host : Ini adalah istilah untuk masing-masing nama domain dan aplikasi yang dihosting di satu tempat. Pada waktu user mengakses alamat tertentu, misalnya <code class="language-plaintext highlighter-rouge">app1.artivisi.id</code>, maka akan diteruskan ke aplikasi A. Sedangkan bila alamat yang diketik di browser client <code class="language-plaintext highlighter-rouge">wp.artivisi.id</code>, maka yang akan dijalankan adalah aplikasi Wordpress.</li>
<li>SNI : server name indication. Ini adalah fitur webserver agar bisa menghosting lebih dari satu situs SSL. Seperti sudah dibahas di artikel terdahulu, setiap nama domain butuh satu sertifikat. Jadi pada kasus kita di atas, kita akan punya 4 pasang public key dan private key. Satu pasang untuk masing-masing domain. Webserver harus bisa memilih sertifikat mana yang akan dipakai pada waktu ada request yang datang ke nama domain tertentu. Fitur ini disebut dengan SNI.</li>
<li>SSL Termination : di mana koneksi HTTPS berakhir. Atau bisa juga disebut siapa yang memegang sertifikat SSL. Yang akan kita coba di sini semua SSL termination akan dilakukan di front proxy. Alternatif lain, SSL termination bisa dilakukan di masing-masing aplikasi.</li>
</ul>
<p>Strategi kita untuk mengimplementasikan gambar di atas adalah sebagai berikut:</p>
<ul>
<li>Nginx akan dipilih sebagai Front Proxy, karena paling populer, mainstream, sehingga tutorialnya banyak di internet.</li>
<li>Aplikasi Wordpress akan dihosting langsung oleh Nginx dengan modul PHP 7</li>
<li>Aplikasi <code class="language-plaintext highlighter-rouge">app1.artivisi.id</code>, <code class="language-plaintext highlighter-rouge">app2.artivisi.id</code>, dan <code class="language-plaintext highlighter-rouge">app3.artivisi.id</code> akan berjalan di port <code class="language-plaintext highlighter-rouge">10001</code>, <code class="language-plaintext highlighter-rouge">10002</code>, dan <code class="language-plaintext highlighter-rouge">10003</code>.</li>
<li>Kita akan menulis konfigurasi reverse proxy agar Nginx meneruskan request ke <code class="language-plaintext highlighter-rouge">https://app1.artivisi.id</code> ke <code class="language-plaintext highlighter-rouge">http://localhost:10001</code> dan seterusnya untuk aplikasi lainnya.</li>
</ul>
<h2 id="membuat-vps">Membuat VPS</h2>
<p>Langkah pertama tentu kita siapkan dulu VPSnya. Saya biasanya menggunakan provider <a href="https://m.do.co/c/c5449509c33a">Digital Ocean</a> yang murah, meriah, cepat, dan mudah digunakan. Dari sini kita mendapatkan alamat IP public server kita yaitu <code class="language-plaintext highlighter-rouge">159.65.3.157</code>.</p>
<h2 id="setup-dns">Setup DNS</h2>
<p>Masukkan alamat IP tersebut ke konfigurasi DNS server. Pastikan masing-masing hostname yang kita inginkan sudah didaftarkan agar mengarah ke IP tersebut.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/01-dns-config.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/01-dns-config.png" alt="DNS Config" /></a></p>
<h2 id="instalasi-dan-konfigurasi-virtualhost-dengan-nginx">Instalasi dan Konfigurasi VirtualHost dengan Nginx</h2>
<p>Login ke server VPS sebagai root, kemudian lakukan instalasi Nginx</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install nginx -y
</code></pre></div></div>
<h3 id="folder-virtual-host">Folder Virtual Host</h3>
<p>Selanjutnya, kita membuat placeholder dulu untuk masing-masing domain. Tujuannya supaya kita bisa mengkonfigurasi masing-masing domain dan mengetes konfigurasinya tanpa harus mendeploy aplikasi aslinya. Deployment aplikasi biasanya cukup ribet, sehingga kita ingin meminimasi kepusingan pada saat error.</p>
<p>Kita buat 4 folder di <code class="language-plaintext highlighter-rouge">/var/www</code> sesuai nama masing-masing domain.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p /var/www/{app1.artivisi.id,app2.artivisi.id,app3.artivisi.id,wp.artivisi.id}/html
</code></pre></div></div>
<p>Hasilnya sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -l /var/www/
total 20
drwxr-xr-x 3 root root 4096 Feb 12 07:44 app1.artivisi.id
drwxr-xr-x 3 root root 4096 Feb 12 07:44 app2.artivisi.id
drwxr-xr-x 3 root root 4096 Feb 12 07:44 app3.artivisi.id
drwxr-xr-x 2 root root 4096 Feb 12 07:42 html
drwxr-xr-x 3 root root 4096 Feb 12 07:44 wp.artivisi.id
</code></pre></div></div>
<p>Selanjutnya, kita buat placeholder halaman <code class="language-plaintext highlighter-rouge">index.html</code> agar keliatan bedanya pada waktu dibrowse. Isinya kira-kira seperti ini:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><html></span>
<span class="nt"><head><title></span>app1.artivisi.id<span class="nt"></title></head></span>
<span class="nt"><body><h1></span>app1.artivisi.id<span class="nt"></h1></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>Simpan di <code class="language-plaintext highlighter-rouge">/var/www/app1.artivisi.id/html/index.html</code> dan lakukan hal yang sama untuk semua domain.</p>
<h3 id="konfigurasi-virtual-host">Konfigurasi Virtual Host</h3>
<p>Kita akan membuat konfigurasi untuk masing-masing domain agar dilayani oleh folder yang sudah kita siapkan pada langkah sebelumnya. Berikut isi file konfigurasi untuk <code class="language-plaintext highlighter-rouge">app1.artivisi.id</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
listen 80;
listen [::]:80;
root /var/www/app1.artivisi.id/html;
index index.html index.htm index.nginx-debian.html;
server_name app1.artivisi.id;
location / {
try_files $uri $uri/ =404;
}
}
</code></pre></div></div>
<p>Simpan filenya di <code class="language-plaintext highlighter-rouge">/etc/nginx/sites-available/app1.artivisi.id</code>. Kemudian kita buat symlink ke <code class="language-plaintext highlighter-rouge">/etc/nginx/sites-enabled/</code> untuk mengaktifkannya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ln -s /etc/nginx/sites-available/app1.artivisi.id /etc/nginx/sites-enabled/
</code></pre></div></div>
<p>Periksa file konfigurasinya untuk memastikan tidak ada salah ketik</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nginx -t
</code></pre></div></div>
<p>Hasilnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
</code></pre></div></div>
<p>Selanjutnya, kita restart Nginx</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service nginx restart
</code></pre></div></div>
<p>Lalu kita test browse ke <code class="language-plaintext highlighter-rouge">http://app1.artivisi.id</code>, harusnya tampil seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/02-app1.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/02-app1.png" alt="Tampilan app1.artivisi.id" /></a></p>
<p>Kalau sudah berhasil, lakukan hal yang sama untuk semua domain yang lain. Pastikan semua domain bisa diakses dengan benar.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/03-semua-app.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/03-semua-app.png" alt="Tampilan semua app" /></a></p>
<h2 id="https-dengan-letsencrypt">HTTPS dengan LetsEncrypt</h2>
<p>Semua domain kita sudah bisa diakses dengan protokol <code class="language-plaintext highlighter-rouge">http</code>. Sekarang saatnya kita amankan dengan <code class="language-plaintext highlighter-rouge">https</code>. Kita akan menggunakan sertifikat gratisan dari LetsEncrypt. Proses pengambilan sertifikat sudah diotomasi dengan aplikasi yang bernama <code class="language-plaintext highlighter-rouge">certbot</code>. Kita install dulu <code class="language-plaintext highlighter-rouge">certbot</code>nya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>snap install core
snap refresh core
snap install --classic certbot
</code></pre></div></div>
<p>Setelah terinstal, kita tinggal menjalankan <code class="language-plaintext highlighter-rouge">certbot</code>. Dia akan otomatis membaca konfigurasi virtualhost kita di Nginx, dan menawarkan untuk membuatkan sertifikat SSL untuk tiap domain.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certbot --nginx
</code></pre></div></div>
<p>Dia akan menanyakan beberapa pertanyaan, seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): it@tazkia.ac.id
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: a
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: n
Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: app1.artivisi.id
2: app2.artivisi.id
3: app3.artivisi.id
4: wp.artivisi.id
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for app1.artivisi.id
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/app1.artivisi.id
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/app1.artivisi.id
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://app1.artivisi.id
You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=app1.artivisi.id
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/app1.artivisi.id/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/app1.artivisi.id/privkey.pem
Your cert will expire on 2019-11-03. To obtain a new or tweaked
version of this certificate in the future, simply run certbot again
with the "certonly" option. To non-interactively renew *all* of
your certificates, run "certbot renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
</code></pre></div></div>
<p>Hasilnya bisa kita lihat di <code class="language-plaintext highlighter-rouge">/etc/letsencrypt/live</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -l /etc/letsencrypt/live/
total 16
drwxr-xr-x 2 root root 4096 Feb 12 09:35 app1.artivisi.id
drwxr-xr-x 2 root root 4096 Feb 12 09:36 app2.artivisi.id
drwxr-xr-x 2 root root 4096 Feb 12 09:38 app3.artivisi.id
drwxr-xr-x 2 root root 4096 Feb 12 09:43 wp.artivisi.id
</code></pre></div></div>
<p>Certbot ini juga menginstal script untuk melakukan perpanjangan secara otomatis. Scriptnya ada di dalam folder <code class="language-plaintext highlighter-rouge">/etc/cron.d</code>. Dia akan mengecek apakah ada sertifikat yang mau expire. Kalau ada, maka akan dilakukan perpanjangan otomatis.</p>
<h3 id="konfigurasi-https">Konfigurasi HTTPS</h3>
<p>Bila kita memilih opsi 2 pada waktu menjalankan certbot seperti di atas, dia akan menambahkan konfigurasi secara otomatis. Kita tidak perlu lagi mengedit sendiri. Hasilnya seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
root /var/www/app1.artivisi.id/html;
index index.html index.htm index.nginx-debian.html;
server_name app1.artivisi.id;
location / {
try_files $uri $uri/ =404;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/app1.artivisi.id/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/app1.artivisi.id/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = app1.artivisi.id) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name app1.artivisi.id;
return 404; # managed by Certbot
}
</code></pre></div></div>
<p>Test dulu apakah konfigurasinya sudah oke. Setelah berjalan dengan baik, kita test juga ke domain lainnya. Tinggal copy paste saja, dan edit nama domainnya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/04-semua-app-https.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/04-semua-app-https.png" alt="HTTPS semua domain sudah oke" /></a></p>
<h3 id="test-ssl-labs">Test SSL Labs</h3>
<p>Setelah semua sertifikat terpasang sempurna, kita coba dengan layanan auditor konfigurasi SSL, yaitu <a href="https://www.ssllabs.com/ssltest/">SSL Labs</a>. Layanan ini akan memberi tahu kita kalau ada kekurangan dalam konfigurasi https kita.</p>
<p>Buka <a href="https://www.ssllabs.com/ssltest/">websitenya</a> kemudian masukkan nama domain kita di sana.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/05-ssllabs-homepage.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/05-ssllabs-homepage.png" alt="SSL Labs Homepage" /></a></p>
<p>Setelah itu, kita biarkan dia bekerja</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/06-ssllabs-in-progress.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/06-ssllabs-in-progress.png" alt="SSL Labs in progress" /></a></p>
<p>Berikut hasilnya, not bad at all :D</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/07-hasil-ssllabs.png"><img src="https://software.endy.muhardin.com/images/uploads/2018/msa-deployment/07-hasil-ssllabs.png" alt="Hasil SSL Labs Test" /></a></p>
<h2 id="penutup">Penutup</h2>
<p>Sampai di sini, kita telah berhasil melakukan:</p>
<ul>
<li>Pembuatan VPS / VM / Host yang akan menjalankan aplikasi</li>
<li>Konfigurasi nama domain agar mengarah ke VPS</li>
<li>Setup Virtual Host dengan Nginx, sehingga semua nama domain bisa dilayani</li>
<li>Pembuatan sertifikat SSL gratis dengan LetsEncrypt dan Certbot</li>
<li>Konfigurasi masing-masing domain sehingga bisa diakses secara aman melalui <code class="language-plaintext highlighter-rouge">https</code></li>
</ul>
<p>Pada <a href="https://software.endy.muhardin.com/devops/deployment-microservice-kere-hore-2/">artikel selanjutnya</a>, kita akan membuat aplikasi web sederhana dengan Spring Boot untuk dideploy ke VPS ini. Stay tuned …</p>
Membatalkan Perubahan File dengan Git2017-11-24T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/membatalkan-perubahan-file-git<p>Salah satu keuntungan menggunakan version control adalah kita bisa dengan mudah mengembalikan kondisi file ke masa lalu. Hal ini dibutuhkan bila ternyata ada anggota tim membuat perubahan yang ternyata tidak bisa digunakan. Contohnya, di Java kita menggunakan library FlywayDB untuk mengurus migrasi database. File migrasi ini sekali sudah dijalankan maka tidak boleh diedit lagi. Bila diedit, maka akan keluar error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Caused by: org.flywaydb.core.api.FlywayException: Validate failed. Migration Checksum mismatch for migration 1.0.0.2017111302
-> Applied to database : -1966400148
-> Resolved locally : -1305984306
</code></pre></div></div>
<p>Bila hal ini terjadi, maka kita ingin mengembalikan file migrasi tersebut ke kondisi sebelum diedit. Berikut langkah-langkahnya:</p>
<!--more-->
<p>Dari pesan error di atas, kita bisa lihat bahwa file yang bermasalah adalah file migrasi <code class="language-plaintext highlighter-rouge">1.0.0.2017111302</code>. Untuk lebih tepatnya filenya adalah <code class="language-plaintext highlighter-rouge">src/main/resources/db/migration/V1.0.0.2017111302__Data_Security.sql</code></p>
<p>Pertama, kita cari dulu di mana commit yang memuat perubahan terhadap file tadi. Kita bisa lihat dari history perubahan filenya. Kita bisa gunakan perintah <code class="language-plaintext highlighter-rouge">git annotate <namafile></code>. Outputnya seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>daebd685 ( gifar 2017-11-14 15:43:23 +0700 1)INSERT INTO s_permission (id, permission_value, permission_label) VALUES
daebd685 ( gifar 2017-11-14 15:43:23 +0700 2) ('editmaster', 'EDIT_MASTER', 'Edit Master'),
daebd685 ( gifar 2017-11-14 15:43:23 +0700 3) ('viewmaster', 'VIEW_MASTER', 'View Master'),
28d37d0c (Endy Muhardin 2017-11-16 13:24:36 +0700 4) ('editpendaftar', 'EDIT_PENDAFTAR', 'Edit Peserta'),
ebd1fe35 ( Haffizh 2017-11-23 15:03:19 +0700 5) ('viewpendaftar', 'VIEW_PENDAFTAR', 'View Peserta'),
ebd1fe35 ( Haffizh 2017-11-23 15:03:19 +0700 6) ('editfinance', 'EDIT_FINANCE', 'Edit Finance'),
ebd1fe35 ( Haffizh 2017-11-23 15:03:19 +0700 7) ('viewfinance', 'VIEW_FINANCE', 'View Finance');
daebd685 ( gifar 2017-11-14 15:43:23 +0700 8)
f745c93d ( gifar 2017-11-13 15:52:00 +0700 9)INSERT INTO s_role (id, description, name) VALUES
28d37d0c (Endy Muhardin 2017-11-16 13:24:36 +0700 10) ('pendaftar', 'PENDAFTAR', 'Peserta'),
ebd1fe35 ( Haffizh 2017-11-23 15:03:19 +0700 11) ('humas', 'HUMAS', 'Humas'),
ebd1fe35 ( Haffizh 2017-11-23 15:03:19 +0700 12) ('finance','FINANCE','Finance');
</code></pre></div></div>
<p>Kita bisa lihat bahwa perubahan terakhir dilakukan oleh <code class="language-plaintext highlighter-rouge">Haffizh</code> pada waktu dan tanggal <code class="language-plaintext highlighter-rouge">2017-11-23 15:03:19 +0700</code> dengan commit id <code class="language-plaintext highlighter-rouge">ebd1fe35</code>. Kita ingin membatalkan perubahan tersebut sehingga kondisi file menjadi seperti sebelum commit <code class="language-plaintext highlighter-rouge">ebd1fe35</code> terjadi.</p>
<p>Sebetulnya perubahannya tidak salah, yaitu menambahkan role dan permission untuk <code class="language-plaintext highlighter-rouge">FINANCE</code> agar fitur-fitur bagian keuangan bisa diproteksi dengan baik. Tetapi harusnya perubahan ini ditulis di file migrasi terpisah, misalnya <code class="language-plaintext highlighter-rouge">V1.2.0.2017112401__Role_Finance.sql</code>. Jadi sebelum dibatalkan kita harus pindahkan dulu script migrasinya.</p>
<p>Langkah kedua, kita buat dulu file baru untuk menampung perubahan tersebut, isinya kira-kira seperti ini:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_permission</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">permission_value</span><span class="p">,</span> <span class="n">permission_label</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'editfinance'</span><span class="p">,</span> <span class="s1">'EDIT_FINANCE'</span><span class="p">,</span> <span class="s1">'Edit Finance'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'viewfinance'</span><span class="p">,</span> <span class="s1">'VIEW_FINANCE'</span><span class="p">,</span> <span class="s1">'View Finance'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_role</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'finance'</span><span class="p">,</span><span class="s1">'FINANCE'</span><span class="p">,</span><span class="s1">'Finance'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_role_permission</span> <span class="p">(</span><span class="n">id_role</span><span class="p">,</span> <span class="n">id_permission</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'finance'</span><span class="p">,</span> <span class="s1">'viewfinance'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'finance'</span><span class="p">,</span> <span class="s1">'editfinance'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_user</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">active</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">id_role</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'u003'</span><span class="p">,</span> <span class="k">true</span><span class="p">,</span> <span class="s1">'finance'</span><span class="p">,</span> <span class="s1">'finance'</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">s_user_password</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">id_user</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span> <span class="k">VALUES</span>
<span class="p">(</span><span class="s1">'up003'</span><span class="p">,</span> <span class="s1">'u003'</span><span class="p">,</span> <span class="s1">'$2a$17$Mhfv.hlqIybDHWqAaTMU/.PKi8RDntt6xe9pTMGQLfnW3phTlhROm'</span><span class="p">);</span>
</code></pre></div></div>
<p>Isi file ini bisa dengan mudah kita dapatkan dari perintah <code class="language-plaintext highlighter-rouge">annotate</code> tadi. Berikut tampilannya di IDE.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-revert/annotate-before.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-revert/annotate-before.png" alt="IDEA Annotate" /></a></p>
<p>Kita tinggal copy paste baris yang ada tulisannya <code class="language-plaintext highlighter-rouge">Yesterday Haffizh</code> ke file baru.</p>
<p>Langkah ketiga, backup dulu kondisi database existing, supaya kita bisa test perbaikan ini nantinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pg_dump registrasidb > kondisi-db-sebelum-revert.dmp
</code></pre></div></div>
<p>Selanjutnya, kita kembalikan file <code class="language-plaintext highlighter-rouge">V1.0.0.2017111302__Data_Security.sql</code> ke kondisi <strong>satu commit sebelum</strong> commit <code class="language-plaintext highlighter-rouge">ebd1fe35</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout ebd1fe35~1 -- src/main/resources/db/migration/V1.0.0.2017111302__Data_Security.sql
</code></pre></div></div>
<p>Tulisan <code class="language-plaintext highlighter-rouge">xxx~1</code> artinya 1 commit sebelum <code class="language-plaintext highlighter-rouge">xxx</code>. Hasilnya bisa kita lihat di editor ataupun perintah <code class="language-plaintext highlighter-rouge">git annotate</code> di command line seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-revert/annotate-after.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-revert/annotate-after.png" alt="IDEA Annotate Setelah Revert" /></a></p>
<p>Selanjutnya kita test lagi jalankan aplikasinya. Seharusnya sudah tidak ada error lagi. Berikut outputnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>o.f.core.internal.command.DbMigrate : Migrating schema "public" to version 1.2.0.2017112401 - Role Finance
o.f.core.internal.command.DbMigrate : Migrating schema "public" to version 1.2.0.2017112402 - Skema Tagihan
o.f.core.internal.command.DbMigrate : Successfully applied 2 migrations to schema "public" (execution time 00:00.193s).
</code></pre></div></div>
<p>Terakhir, kita bisa commit perbaikannya. Cek dulu file mana saja yang berubah</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-revert/git-status-before.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-revert/git-status-before.png" alt="Git Status" /></a></p>
<p>Ingat, tadi kita bisa membatalkan perubahan dengan mudah karena commitnya rapih. Oleh karena itu sekarang kita juga harusnya commit file yang berkaitan dengan perbaikan script migrasi ini saja. File yang relevan hanya 2 : <code class="language-plaintext highlighter-rouge">V1.0.0.2017111302__Data_Security.sql</code> dan <code class="language-plaintext highlighter-rouge">V1.2.0.2017112401__Role_Finance.sql</code>. Yang lain jangan diikutkan.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-revert/git-status-after.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-revert/git-status-after.png" alt="Git Status Setelah Add" /></a></p>
<p>OK, sekarang commit dan push.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git commit -m "undo perubahan script migrasi"
git pull --rebase
git push
</code></pre></div></div>
<p>Demikianlah cara membatalkan perubahan terhadap file tertentu menggunakan Git. Semoga bermanfaat.</p>
Mengirim Email dengan GMail API2017-11-13T07:00:00+07:00https://software.endy.muhardin.com/java/mengirim-email-gmail-api<p>Selama ini, bila kita membuat aplikasi yang ada fitur kirim emailnya (reset password, pengumuman, newsletter, dan sebagainya), biasanya kita menggunakan protokol SMTP. Akan tetapi, beberapa tahun belakangan ini, layanan SaaS (software as a service) bermunculan seperti cendawan di musim hujan. Sebagian besar di antaranya tidak lagi menggunakan protokol SMTP, tapi menyediakan API di atas protokol HTTP.</p>
<p>Ada banyak penyedia jasa layanan email, diantaranya:</p>
<ul>
<li>SendGrid</li>
<li>MailerLite</li>
<li>Amazon</li>
<li>GMail</li>
</ul>
<p>Pada artikel ini kita akan membahas yang paling populer saja, yaitu GMail. GMail menyediakan dua pilihan bila kita ingin mengirim email dari aplikasi kita : SMTP atau HTTP API. Kita akan gunakan HTTP API.</p>
<p>Berikut langkah-langkahnya:</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#membuat-project-google-developer" id="markdown-toc-membuat-project-google-developer">Membuat Project Google Developer</a></li>
<li><a href="#enable-gmail-api" id="markdown-toc-enable-gmail-api">Enable GMail API</a></li>
<li><a href="#membuat-credentials" id="markdown-toc-membuat-credentials">Membuat Credentials</a></li>
<li><a href="#membuat-project-spring-boot" id="markdown-toc-membuat-project-spring-boot">Membuat Project Spring Boot</a></li>
<li><a href="#menambahkan-dependensi-gmail-api" id="markdown-toc-menambahkan-dependensi-gmail-api">Menambahkan Dependensi GMail API</a></li>
<li><a href="#persiapan-file-konfigurasi" id="markdown-toc-persiapan-file-konfigurasi">Persiapan File Konfigurasi</a></li>
<li><a href="#melakukan-otorisasi-oauth" id="markdown-toc-melakukan-otorisasi-oauth">Melakukan Otorisasi OAuth</a></li>
<li><a href="#mengirim-email" id="markdown-toc-mengirim-email">Mengirim Email</a></li>
<li><a href="#mengirim-email-dengan-template-html" id="markdown-toc-mengirim-email-dengan-template-html">Mengirim Email dengan Template HTML</a></li>
<li><a href="#deployment-heroku" id="markdown-toc-deployment-heroku">Deployment Heroku</a> <ul>
<li><a href="#menggunakan-external-web-server" id="markdown-toc-menggunakan-external-web-server">Menggunakan External Web Server</a></li>
<li><a href="#menggunakan-environment-variable" id="markdown-toc-menggunakan-environment-variable">Menggunakan Environment Variable</a></li>
</ul>
</li>
<li><a href="#penutup" id="markdown-toc-penutup">Penutup</a></li>
</ul>
<h2 id="membuat-project-google-developer">Membuat Project Google Developer</h2>
<p>Agar aplikasi kita bisa mengirim email, terlebih dulu kita buat Google Developer Project. Pertama, kita harus sudah login dulu ke akun GMail.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/01-login-gmail.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/01-login-gmail.png" alt="Gmail Login" /></a></p>
<p>Setelah itu, buka <a href="https://console.developers.google.com">Google Developer Console</a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/02-google-developer-console.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/02-google-developer-console.png" alt="Google Developer Console" /></a></p>
<p>Kemudian buat project baru</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/03-select-or-create.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/03-select-or-create.png" alt="Create New Project" /></a></p>
<p>Masukkan nama project. Namanya bebas apa saja boleh</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/04-new-project.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/04-new-project.png" alt="Nama Project" /></a></p>
<p>Project selesai dibuat, sekarang kita bisa lanjutkan ke aktivasi GMail API</p>
<h2 id="enable-gmail-api">Enable GMail API</h2>
<p>Di dashboard sudah disediakan tombolnya</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/05-project-created.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/05-project-created.png" alt="Project Dashboard" /></a></p>
<p>Klik saja icon GMail API</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/06-enable-gmail-api.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/06-enable-gmail-api.png" alt="Aktivasi GMail API" /></a></p>
<p>Selanjutnya kita sudah bisa melihat bahwa GMail API sudah aktif.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/07-api-enabled.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/07-api-enabled.png" alt="Gmail API Enabled" /></a></p>
<h2 id="membuat-credentials">Membuat Credentials</h2>
<p>Biasanya, kita mengirim email melalui aplikasi web based yang sudah disediakan GMail, yaitu setelah memasukkan username dan password. Tetapi kali ini, kita ingin mengirim email dari aplikasi, yang tidak memiliki kemampuan untuk login dengan username dan password. Untuk itu, kita menggunakan otentikasi dan otorisasi OAuth. Lebih detail mengenai OAuth, silahkan tonton seri pelatihan OAuth saya di Youtube.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/m5jI0Y14Wqc" frameborder="0" allowfullscreen=""></iframe>
<p>Sebelum membuat credential bertipe OAuth, terlebih dulu kita harus mengkonfigurasikan OAuth Consent Screen. Klik menu Consent Screen di panel kiri, kemudian pilih User Type External</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/08-oauth-consent-screen-user-type.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/08-oauth-consent-screen-user-type.png" alt="Consent Screen User Type" /></a></p>
<p>Selanjutnya, beri nama aplikasi dan masukkan email support</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/09-oauth-consent-screen-app-info.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/09-oauth-consent-screen-app-info.png" alt="Consent Screen App Info" /></a></p>
<p>App Domain dan authorized domain bisa kita kosongkan. Developer contact harus diisi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/10-oauth-consent-screen-app-domain.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/10-oauth-consent-screen-app-domain.png" alt="Consent Screen App Domain" /></a></p>
<p>Untuk scope, kita tidak perlu isi apa-apa.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/11-oauth-consent-screen-app-scope.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/11-oauth-consent-screen-app-scope.png" alt="Consent Screen App Scope" /></a></p>
<p>Demikian juga untuk test user, dikosongkan saja.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/12-oauth-consent-screen-test-users.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/12-oauth-consent-screen-test-users.png" alt="Consent Screen Test User" /></a></p>
<p>Setelah consent screen selesai dikonfigurasi, kita lanjutkan ke Create Credentials. Ada beberapa jenis user yang bisa dipilih, kita gunakan OAuth Client ID.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/13-create-credentials.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/13-create-credentials.png" alt="Create Credentials - User Type" /></a></p>
<p>Tentukan jenis aplikasi, kita pilih saja Desktop Application. Karena walaupun aplikasi kita berbasis web, tapi pengiriman email hanya akan dilakukan oleh satu email account saja. Bukan email account user aplikasi.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/14-credential-application-option.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/14-credential-application-option.png" alt="Create Credential - App Type" /></a></p>
<p>Isikan nama aplikasi, ini yang nanti akan tampil di consent screen.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/15-credential-application-type.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/15-credential-application-type.png" alt="Create Credentials - App Name" /></a></p>
<p>Selesai, client id dan secret sudah selesai.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/16-client-id-created.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/16-client-id-created.png" alt="Credential created" /></a></p>
<p>Agar bisa digunakan di aplikasi, kita perlu mengunduh file credential</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/17-dashboard-credentials.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/17-dashboard-credentials.png" alt="Link Download Credential" /></a></p>
<p>Download filenya, kita akan membutuhkannya nanti.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/18-file-credentials.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/18-file-credentials.png" alt="File Credentials" /></a></p>
<h2 id="membuat-project-spring-boot">Membuat Project Spring Boot</h2>
<p>Selanjutnya, kita buat aplikasi untuk mengirim email. Seperti biasa, kita generate project di <a href="https://start.spring.io">start.spring.io</a></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/19-spring-starter.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/19-spring-starter.png" alt="Start Spring IO" /></a></p>
<p>Beberapa modul yang saya gunakan:</p>
<ul>
<li>Web : aplikasi kita adalah aplikasi web, nantinya kita kirim email dengan menggunakan REST API</li>
<li>Lombok : agar tidak repot membuat getter/setter</li>
<li>Mail : library Java untuk mengirim email</li>
<li>DevTools : supaya gampang restart aplikasi</li>
</ul>
<h2 id="menambahkan-dependensi-gmail-api">Menambahkan Dependensi GMail API</h2>
<p>Selanjutnya, kita tambahkan dependensi library GMail API. Daftar library bisa dilihat di file konfigurasi Gradle di <a href="https://developers.google.com/gmail/api/quickstart/java">dokumentasi yang sudah disediakan Google</a>.</p>
<p>Berikut dependensinya dalam format Maven</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.google.api-client<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>google-api-client<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.2.0<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.google.oauth-client<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>google-oauth-client-jetty<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.34.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.google.apis<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>google-api-services-gmail<span class="nt"></artifactId></span>
<span class="nt"><version></span>v1-rev20230206-2.0.0<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Kita siapkan dulu kerangka class untuk melakukan pengiriman email. Saya biasanya buat di dalam package <code class="language-plaintext highlighter-rouge">service</code>. Classnya kita beri nama saja <code class="language-plaintext highlighter-rouge">GmailApiService</code>. Berikut kerangka classnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.belajar.belajargmailapi.service</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Service</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.annotation.PostConstruct</span><span class="o">;</span>
<span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">GmailApiService</span> <span class="o">{</span>
<span class="nd">@PostConstruct</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">inisialisasiOauth</span><span class="o">(){</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">kirimEmail</span><span class="o">(</span><span class="nc">String</span> <span class="n">from</span><span class="o">,</span> <span class="nc">String</span> <span class="n">to</span><span class="o">,</span> <span class="nc">String</span> <span class="n">subject</span><span class="o">,</span> <span class="nc">String</span> <span class="n">content</span><span class="o">){</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="persiapan-file-konfigurasi">Persiapan File Konfigurasi</h2>
<p>Aplikasi kita membutuhkan dua konfigurasi agar kita bisa menggunakan GMail API :</p>
<ul>
<li>lokasi file <code class="language-plaintext highlighter-rouge">client_secret.json</code> yang sudah kita unduh di langkah sebelumnya</li>
<li>lokasi folder tempat penyimpanan file hasil otorisasi</li>
</ul>
<p>Misalnya, kita akan sediakan folder <code class="language-plaintext highlighter-rouge">${HOME}/.gmail-api/credentials</code> untuk lokasi folder. File <code class="language-plaintext highlighter-rouge">client_secret.json</code> juga akan kita taruh di sana. Maka konfigurasi di <code class="language-plaintext highlighter-rouge">application.properties</code> akan terlihat seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spring.application.name=notifikasi-gmail
gmail.account.username=endy.muhardin@gmail.com
gmail.folder=${user.home}/.gmail-api/credentials
</code></pre></div></div>
<p>Tentunya jangan lupa kita buatkan dulu folder di atas. Kita juga harus pindahkan dan rename file json yang kita dapatkan dari Google Developer Console tadi. Pastikan akun gmail yang kita taruh di file konfigurasi (<code class="language-plaintext highlighter-rouge">gmail.account.username</code>) sama dengan akun gmail yang digunakan untuk membuat project di Developer Console.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p ~/.gmail-api/credentials
cp ~/Downloads/client*.json ~/.gmail-api/credentials/client_secret.json
</code></pre></div></div>
<p>Pastikan sudah benar</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -lR ~/.gmail-api
</code></pre></div></div>
<p>Seharusnya outputnya kira-kira seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>total 0
drwxr-xr-x 3 endymuhardin staff 96 Nov 13 10:28 credentials
/Users/endymuhardin/.gmail-api/credentials:
total 8
-rw-r--r--@ 1 endymuhardin staff 430 Nov 13 10:28 client_secret.json
</code></pre></div></div>
<p>Isi file konfigurasi ini bisa kita baca di class <code class="language-plaintext highlighter-rouge">GmailApiService</code> sebagai berikut:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">GmailApiService</span> <span class="o">{</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${spring.application.name}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">applicationName</span><span class="o">;</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${gmail.account.username}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">gmailUsername</span><span class="o">;</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${gmail.folder}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">dataStoreFolder</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="melakukan-otorisasi-oauth">Melakukan Otorisasi OAuth</h2>
<p>Agar aplikasi kita bisa mengirim email atas nama pemilik akun, terlebih dulu si pemilik akun harus mengkonfirmasi bahwa aplikasi kita benar-benar diperbolehkan mengirim email. Kalau tidak begitu, nanti akan banyak penyalahgunaan, ada email yang datang atas nama kita padahal bukan kita yang kirim.</p>
<p>Berikut kode program untuk menginisiasi proses otorisasi.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GmailApiService</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="no">SCOPES</span> <span class="o">=</span>
<span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="nc">GmailScopes</span><span class="o">.</span><span class="na">GMAIL_SEND</span><span class="o">);</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">GoogleClientSecrets</span> <span class="n">clientSecrets</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">JsonFactory</span> <span class="n">jsonFactory</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Gmail</span> <span class="n">gmail</span><span class="o">;</span>
<span class="nd">@PostConstruct</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">inisialisasiOauth</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">createDirectories</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">dataStoreFolder</span><span class="o">));</span>
<span class="nc">FileDataStoreFactory</span> <span class="n">fileDataStoreFactory</span> <span class="o">=</span>
<span class="k">new</span> <span class="nf">FileDataStoreFactory</span><span class="o">(</span><span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">dataStoreFolder</span><span class="o">));</span>
<span class="nc">HttpTransport</span> <span class="n">httpTransport</span> <span class="o">=</span> <span class="nc">GoogleNetHttpTransport</span><span class="o">.</span><span class="na">newTrustedTransport</span><span class="o">();</span>
<span class="nc">GoogleAuthorizationCodeFlow</span> <span class="n">flow</span> <span class="o">=</span>
<span class="k">new</span> <span class="nc">GoogleAuthorizationCodeFlow</span><span class="o">.</span><span class="na">Builder</span><span class="o">(</span>
<span class="n">httpTransport</span><span class="o">,</span> <span class="n">jsonFactory</span><span class="o">,</span> <span class="n">clientSecrets</span><span class="o">,</span> <span class="no">SCOPES</span><span class="o">)</span>
<span class="o">.</span><span class="na">setDataStoreFactory</span><span class="o">(</span><span class="n">fileDataStoreFactory</span><span class="o">)</span>
<span class="o">.</span><span class="na">setAccessType</span><span class="o">(</span><span class="s">"offline"</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="nc">Credential</span> <span class="n">gmailCredential</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AuthorizationCodeInstalledApp</span><span class="o">(</span>
<span class="n">flow</span><span class="o">,</span> <span class="k">new</span> <span class="nc">LocalServerReceiver</span><span class="o">()).</span><span class="na">authorize</span><span class="o">(</span><span class="s">"user"</span><span class="o">);</span>
<span class="n">gmail</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Gmail</span><span class="o">.</span><span class="na">Builder</span><span class="o">(</span><span class="n">httpTransport</span><span class="o">,</span> <span class="n">jsonFactory</span><span class="o">,</span> <span class="n">gmailCredential</span><span class="o">)</span>
<span class="o">.</span><span class="na">setApplicationName</span><span class="o">(</span><span class="n">applicationName</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Setelah kita pasang kode di atas, kita bisa coba jalankan aplikasinya dengan perintah <code class="language-plaintext highlighter-rouge">mvn clean spring-boot:run</code>. Pada waktu dijalankan pertama kali, kita akan melakukan otorisasi aplikasi. Proses ini akan menghasilkan satu file yang dibuatkan GMail API di folder yang telah kita konfigurasikan, yaitu <code class="language-plaintext highlighter-rouge">${user.home}/.gmail-api/credentials</code>.</p>
<p>Berikut output pada waktu dijalankan, proses run akan berhenti pada waktu menampilkan URL otorisasi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> . ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.8.RELEASE)
2017-11-13 10:41:44.270 INFO 64131 --- [ restartedMain] c.m.e.b.b.BelajarGmailApiApplication : Starting BelajarGmailApiApplication on Endys-MacBook-Air.local with PID 64131 (/Volumes/SDUF128G/workspace/belajar/belajar-gmail-api/target/classes started by endymuhardin in /Volumes/SDUF128G/workspace/belajar/belajar-gmail-api)
2017-11-13 10:41:44.271 INFO 64131 --- [ restartedMain] c.m.e.b.b.BelajarGmailApiApplication : No active profile set, falling back to default profiles: default
2017-11-13 10:41:44.469 INFO 64131 --- [ restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@7c012de5: startup date [Mon Nov 13 10:41:44 WIB 2017]; root of context hierarchy
2017-11-13 10:41:47.871 INFO 64131 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-11-13 10:41:47.914 INFO 64131 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2017-11-13 10:41:47.920 INFO 64131 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.23
2017-11-13 10:41:48.155 INFO 64131 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2017-11-13 10:41:48.155 INFO 64131 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3692 ms
2017-11-13 10:41:48.396 INFO 64131 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2017-11-13 10:41:48.408 INFO 64131 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-11-13 10:41:48.409 INFO 64131 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-11-13 10:41:48.410 INFO 64131 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-11-13 10:41:48.410 INFO 64131 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2017-11-13 10:41:49.076 INFO 64131 --- [ restartedMain] org.mortbay.log : Logging to Logger[org.mortbay.log] via org.mortbay.log.Slf4jLog
2017-11-13 10:41:49.081 INFO 64131 --- [ restartedMain] org.mortbay.log : jetty-6.1.26
2017-11-13 10:41:49.147 INFO 64131 --- [ restartedMain] org.mortbay.log : Started SocketConnector@localhost:51302
Please open the following address in your browser:
https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=937633732253-53nu68qiif1gp5io2aotbf0kuaqq5gm5.apps.googleusercontent.com&redirect_uri=http://localhost:51302/Callback&response_type=code&scope=https://www.googleapis.com/auth/gmail.send
</code></pre></div></div>
<p>Buka url tadi di browser.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/20-choose-account.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/20-choose-account.png" alt="Choose Account" /></a></p>
<p>Kita akan disuruh memilih akun mana yang kita akan pakai. Pastikan pilih akun sesuai pembuatan project di Developer Console. Selanjutnya, kita akan ditanya apakah akan mengijinkan (Allow) <code class="language-plaintext highlighter-rouge">Aplikasi Notifikasi</code> untuk mengirim email atas nama / seolah-olah dari <code class="language-plaintext highlighter-rouge">artivisi.intermedia@gmail.com</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/21-allow-access.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/21-allow-access.png" alt="Allow Access" /></a></p>
<p>Begitu kita allow, tampilan browsernya sebagai berikut.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/22-oauth-success.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/22-oauth-success.png" alt="Allow Success" /></a></p>
<p>Di belakang layar, GMail akan memberikan respon sukses ke aplikasi kita yang sedang berjalan tadi (<code class="language-plaintext highlighter-rouge">mvn spring-boot:run</code>), sehingga prosesnya berlanjut sampai aplikasi berjalan sempurna.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2017-11-13 10:44:08.920 INFO 64131 --- [ restartedMain] org.mortbay.log : Stopped SocketConnector@localhost:51302
2017-11-13 10:44:09.871 INFO 64131 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@7c012de5: startup date [Mon Nov 13 10:41:44 WIB 2017]; root of context hierarchy
2017-11-13 10:44:10.308 INFO 64131 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-11-13 10:44:10.311 INFO 64131 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-11-13 10:44:10.403 INFO 64131 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-13 10:44:10.403 INFO 64131 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-13 10:44:10.542 INFO 64131 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-13 10:44:10.903 INFO 64131 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2017-11-13 10:44:11.069 INFO 64131 --- [ restartedMain] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-11-13 10:44:11.284 INFO 64131 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-11-13 10:44:11.309 INFO 64131 --- [ restartedMain] c.m.e.b.b.BelajarGmailApiApplication : Started BelajarGmailApiApplication in 148.042 seconds (JVM running for 149.023)
</code></pre></div></div>
<p>Kita bisa cek di folder <code class="language-plaintext highlighter-rouge">${user.home}/.gmail-api/credentials</code>, harusnya ada file baru hasil otorisasi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls -lR ~/.gmail-api
total 0
drwx------ 4 endymuhardin staff 128 Nov 13 10:41 credentials
/Users/endymuhardin/.gmail-api/credentials:
total 16
-rw-r--r-- 1 endymuhardin staff 1000 Nov 13 10:44 StoredCredential
-rw-r--r--@ 1 endymuhardin staff 430 Nov 13 10:28 client_secret.json
</code></pre></div></div>
<p>Bila file ini sudah ada, aplikasi kita tidak akan meminta otorisasi lagi seperti langkah di atas. Aplikasi kita akan langsung start seperti biasa. Kita bisa cek dengan cara stop aplikasi (<code class="language-plaintext highlighter-rouge">Ctrl-C</code>) dan start lagi. Harusnya tidak ada prompt otorisasi lagi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] --- spring-boot-maven-plugin:1.5.8.RELEASE:run (default-cli) @ belajar-gmail-api ---
[INFO] Attaching agents: []
10:50:26.491 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
10:50:26.493 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
10:50:26.494 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/Volumes/SDUF128G/workspace/belajar/belajar-gmail-api/target/classes/]
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.8.RELEASE)
2017-11-13 10:50:27.444 INFO 64358 --- [ restartedMain] c.m.e.b.b.BelajarGmailApiApplication : Starting BelajarGmailApiApplication on Endys-MacBook-Air.local with PID 64358 (/Volumes/SDUF128G/workspace/belajar/belajar-gmail-api/target/classes started by endymuhardin in /Volumes/SDUF128G/workspace/belajar/belajar-gmail-api)
2017-11-13 10:50:27.446 INFO 64358 --- [ restartedMain] c.m.e.b.b.BelajarGmailApiApplication : No active profile set, falling back to default profiles: default
2017-11-13 10:50:27.629 INFO 64358 --- [ restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@f83f998: startup date [Mon Nov 13 10:50:27 WIB 2017]; root of context hierarchy
2017-11-13 10:50:30.755 INFO 64358 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-11-13 10:50:30.781 INFO 64358 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2017-11-13 10:50:30.784 INFO 64358 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.23
2017-11-13 10:50:31.014 INFO 64358 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2017-11-13 10:50:31.015 INFO 64358 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3391 ms
2017-11-13 10:50:31.244 INFO 64358 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2017-11-13 10:50:31.254 INFO 64358 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-11-13 10:50:31.255 INFO 64358 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-11-13 10:50:31.256 INFO 64358 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-11-13 10:50:31.256 INFO 64358 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2017-11-13 10:50:32.327 INFO 64358 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@f83f998: startup date [Mon Nov 13 10:50:27 WIB 2017]; root of context hierarchy
2017-11-13 10:50:32.436 INFO 64358 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-11-13 10:50:32.437 INFO 64358 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-11-13 10:50:32.502 INFO 64358 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-13 10:50:32.503 INFO 64358 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-13 10:50:32.564 INFO 64358 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-13 10:50:32.799 INFO 64358 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2017-11-13 10:50:32.880 INFO 64358 --- [ restartedMain] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-11-13 10:50:32.991 INFO 64358 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-11-13 10:50:33.004 INFO 64358 --- [ restartedMain] c.m.e.b.b.BelajarGmailApiApplication : Started BelajarGmailApiApplication in 6.483 seconds (JVM running for 7.444)
</code></pre></div></div>
<p>Pada saat kita mendeploy aplikasi ke server, jangan lupa untuk menyertakan kedua file tersebut (<code class="language-plaintext highlighter-rouge">client_secret.json</code> dan <code class="language-plaintext highlighter-rouge">StoredCredential</code>) di folder yang sesuai dengan konfigurasi.</p>
<h2 id="mengirim-email">Mengirim Email</h2>
<p>Untuk mengirim email, berikut kode programnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.belajar.belajargmailapi.service</span><span class="o">;</span>
<span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">GmailApiService</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Logger</span> <span class="no">LOGGER</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">GmailApiService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="kd">private</span> <span class="nc">Gmail</span> <span class="n">gmail</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">kirimEmail</span><span class="o">(</span><span class="nc">String</span> <span class="n">from</span><span class="o">,</span> <span class="nc">String</span> <span class="n">to</span><span class="o">,</span> <span class="nc">String</span> <span class="n">subject</span><span class="o">,</span> <span class="nc">String</span> <span class="n">content</span><span class="o">){</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">Properties</span> <span class="n">props</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Properties</span><span class="o">();</span>
<span class="nc">Session</span> <span class="n">session</span> <span class="o">=</span> <span class="nc">Session</span><span class="o">.</span><span class="na">getDefaultInstance</span><span class="o">(</span><span class="n">props</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="nc">InternetAddress</span> <span class="n">destination</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">InternetAddress</span><span class="o">(</span><span class="n">to</span><span class="o">);</span>
<span class="nc">MimeMessage</span> <span class="n">email</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MimeMessage</span><span class="o">(</span><span class="n">session</span><span class="o">);</span>
<span class="n">email</span><span class="o">.</span><span class="na">setFrom</span><span class="o">(</span><span class="k">new</span> <span class="nc">InternetAddress</span><span class="o">(</span><span class="n">gmailUsername</span><span class="o">,</span> <span class="n">from</span><span class="o">));</span>
<span class="n">email</span><span class="o">.</span><span class="na">addRecipient</span><span class="o">(</span><span class="nc">RecipientType</span><span class="o">.</span><span class="na">TO</span><span class="o">,</span> <span class="n">destination</span><span class="o">);</span>
<span class="n">email</span><span class="o">.</span><span class="na">setSubject</span><span class="o">(</span><span class="n">subject</span><span class="o">);</span>
<span class="n">email</span><span class="o">.</span><span class="na">setContent</span><span class="o">(</span><span class="n">content</span><span class="o">,</span> <span class="s">"text/html; charset=utf-8"</span><span class="o">);</span>
<span class="nc">ByteArrayOutputStream</span> <span class="n">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span>
<span class="n">email</span><span class="o">.</span><span class="na">writeTo</span><span class="o">(</span><span class="n">buffer</span><span class="o">);</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">buffer</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">encodedEmail</span> <span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">getEncoder</span><span class="o">().</span><span class="na">encodeToString</span><span class="o">(</span><span class="n">bytes</span><span class="o">);</span>
<span class="nc">Message</span> <span class="n">message</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Message</span><span class="o">();</span>
<span class="n">message</span><span class="o">.</span><span class="na">setRaw</span><span class="o">(</span><span class="n">encodedEmail</span><span class="o">);</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">gmail</span><span class="o">.</span><span class="na">users</span><span class="o">().</span><span class="na">messages</span><span class="o">().</span><span class="na">send</span><span class="o">(</span><span class="s">"me"</span><span class="o">,</span> <span class="n">message</span><span class="o">).</span><span class="na">execute</span><span class="o">();</span>
<span class="no">LOGGER</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Email {} from {} to {} with subject {}"</span><span class="o">,</span> <span class="n">message</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">from</span><span class="o">,</span> <span class="n">destination</span><span class="o">,</span> <span class="n">subject</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="no">LOGGER</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Awas jangan salah import, seharusnya <code class="language-plaintext highlighter-rouge">com.google.api.services.gmail.model.Message</code>, <strong>bukan</strong> <code class="language-plaintext highlighter-rouge">javax.mail.Message</code>.</p>
<p>Selanjutnya kita test dengan JUnit.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.belajar.belajargmailapi</span><span class="o">;</span>
<span class="nd">@RunWith</span><span class="o">(</span><span class="nc">SpringRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@SpringBootTest</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BelajarGmailApiApplicationTests</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">GmailApiService</span> <span class="n">gmailApiService</span><span class="o">;</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testKirimEmail</span><span class="o">()</span> <span class="o">{</span>
<span class="n">gmailApiService</span><span class="o">.</span><span class="na">kirimEmail</span><span class="o">(</span>
<span class="s">"Belajar GMail API"</span><span class="o">,</span>
<span class="s">"endy.muhardin@gmail.com"</span><span class="o">,</span>
<span class="s">"Email Percobaan"</span> <span class="o">+</span> <span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">(),</span>
<span class="s">"Ini email percobaan dikirim dari aplikasi"</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Cek inbox gan, harusnya ada message baru.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/23-inbox.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/23-inbox.png" alt="Inbox" /></a></p>
<p>Klik untuk melihat isinya</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/24-email-content.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/24-email-content.png" alt="Choose Account" /></a></p>
<h2 id="mengirim-email-dengan-template-html">Mengirim Email dengan Template HTML</h2>
<p>Tentunya kalau email polos begitu kurang menarik. Kita ingin email yang tampilannya bagus seperti yang biasa kita terima dari online shop dan sebagainya. Untuk itu, kita cari template HTML email yang bagus dan siap pakai.</p>
<p>Untungnya ada yang sudah membuatkan template gratis siap pakai, yaitu <a href="https://www.sendwithus.com/">Send With Us</a>. Kita bisa unduh templatenya <a href="https://github.com/sendwithus/templates">di Github SendWithUs</a>.</p>
<p>Sebagai contoh, kita ambil salah satu file template <code class="language-plaintext highlighter-rouge">welcome.html</code> dan taruh di folder <code class="language-plaintext highlighter-rouge">src/main/resources</code>. Nantinya kita bisa taruh file ini di mana saja, tinggal kita konfigurasi saja di aplikasi.</p>
<p>Untuk memasang variabel di dalam template (misalnya nama, tanggal, no. rekening, dan sebagainya), kita membutuhkan template engine. Saya akan gunakan <a href="https://github.com/spullara/mustache.java">Mustache for Java</a> yang sudah terbukti handal dipakai oleh Twitter.</p>
<p>Berikut tambahan dependensi di <code class="language-plaintext highlighter-rouge">pom.xml</code> untuk menggunakan Mustache.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.github.spullara.mustache.java<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>compiler<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.9.10<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Kita instankan <code class="language-plaintext highlighter-rouge">MustacheFactory</code> yang berguna untuk melakukan pemrosesan template di main class. Main class saya adalah <code class="language-plaintext highlighter-rouge">BelajarGmailApiApplication</code>, berikut isinya:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.belajar.belajargmailapi</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.github.mustachejava.DefaultMustacheFactory</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.github.mustachejava.MustacheFactory</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.boot.SpringApplication</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.boot.autoconfigure.SpringBootApplication</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Bean</span><span class="o">;</span>
<span class="nd">@SpringBootApplication</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BelajarGmailApiApplication</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">BelajarGmailApiApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">MustacheFactory</span> <span class="nf">mustacheFactory</span><span class="o">(){</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">DefaultMustacheFactory</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Nama variabel di Mustache ditandai dengan kurung kurawal berganda. Jadi bila kita ingin memasukkan variabel <code class="language-plaintext highlighter-rouge">nama</code>, di dalam template kita akan tulis seperti ini : {{nama}}. Agar sederhana, saya akan buat saja dua variabel, yaitu <code class="language-plaintext highlighter-rouge">nama</code> dan <code class="language-plaintext highlighter-rouge">pesan</code>. Silahkan cek isi file template untuk melihat caranya. Saya tidak paste di sini karena filenya lumayan besar. Variabel <code class="language-plaintext highlighter-rouge">nama</code> ada <a href="https://github.com/endymuhardin/belajar-gmail-api/blob/master/src/main/resources/templates/welcome.html#L1480">di baris 1480</a> dan variabel <code class="language-plaintext highlighter-rouge">pesan</code> dipasang <a href="https://github.com/endymuhardin/belajar-gmail-api/blob/master/src/main/resources/templates/welcome.html#L1508">di baris 1508</a>.</p>
<p>Selanjutnya, kita akan membaca file template ini dan mengisinya dengan variabel. Lalu kita kirim dengan class <code class="language-plaintext highlighter-rouge">GmailApiService</code> tadi.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.belajar.belajargmailapi</span><span class="o">;</span>
<span class="c1">// import dihapus supaya tidak terlalu panjang. Bereskan saja dengan bantuan IDE</span>
<span class="nd">@RunWith</span><span class="o">(</span><span class="nc">SpringRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@SpringBootTest</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BelajarGmailApiApplicationTests</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">GmailApiService</span> <span class="n">gmailApiService</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">MustacheFactory</span> <span class="n">mustacheFactory</span><span class="o">;</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testKirimEmailDenganTemplate</span><span class="o">(){</span>
<span class="nc">Mustache</span> <span class="n">templateEmail</span> <span class="o">=</span> <span class="n">mustacheFactory</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"templates/welcome.html"</span><span class="o">);</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
<span class="n">data</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"nama"</span><span class="o">,</span> <span class="s">"Endy Muhardin"</span><span class="o">);</span>
<span class="n">data</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"pesan"</span><span class="o">,</span> <span class="s">"Anda telah terdaftar di Aplikasi Notifikasi. Silahkan tunggu instruksi selanjutnya"</span><span class="o">);</span>
<span class="nc">StringWriter</span> <span class="n">output</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringWriter</span><span class="o">();</span>
<span class="n">templateEmail</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">output</span><span class="o">,</span> <span class="n">data</span><span class="o">);</span>
<span class="n">gmailApiService</span><span class="o">.</span><span class="na">kirimEmail</span><span class="o">(</span>
<span class="s">"Belajar GMail API"</span><span class="o">,</span>
<span class="s">"endy.muhardin@gmail.com"</span><span class="o">,</span>
<span class="s">"Percobaan Mustache Template"</span><span class="o">,</span>
<span class="n">output</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Berikut outputnya di mailbox.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/25-inbox-template.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/25-inbox-template.png" alt="Inbox Template" /></a></p>
<p>Dan seperti ini tampilan hasilnya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/26-email-content-template.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/26-email-content-template.png" alt="Choose Account" /></a></p>
<h2 id="deployment-heroku">Deployment Heroku</h2>
<p>Ada pertanyaan di komentar :</p>
<blockquote>
<p>Bagaimana cara menjalankan aplikasi ini di Heroku?</p>
</blockquote>
<p>Deployment di Heroku agak sedikit ribet, tapi bisa dilakukan. Sumber kesulitan utama adalah karena Heroku menganut prinsip <a href="https://devcenter.heroku.com/articles/dynos#isolation-and-security">ephemeral file system</a>. Artinya, semua file yang bukan bagian dari aplikasi akan <strong>dihapus</strong> setiap kali aplikasi dideploy atau direstart. Kesulitan yang ditimbulkan karena ini antara lain:</p>
<ul>
<li>Bila ingin menjadikan <code class="language-plaintext highlighter-rouge">client_secret.json</code> sebagai bagian aplikasi, maka file ini harus dicommit ke source code repository. Tentunya ini tidak baik, karena semua yang masuk repo bukan lagi <code class="language-plaintext highlighter-rouge">secret</code>.</li>
<li>Kita tidak bisa menaruh file <code class="language-plaintext highlighter-rouge">client_secret.json</code> di server aplikasi / dyno Heroku, karena akan dibuang setiap kali restart/deploy.</li>
<li>Kita tidak bisa menjalankan proses OAuth Google API, karena dia akan membuka port secara random, dan melakukan redirect ke port tersebut. Seperti kita lihat pada log di atas, dia mengharapkan redirect ke <code class="language-plaintext highlighter-rouge">http://localhost:51302/Callback&response_type=code&scope=https://www.googleapis.com/auth/gmail.send</code>. Port <code class="language-plaintext highlighter-rouge">51302</code> ini hanya akan jalan di jaringan internal Heroku dan tidak bisa diakses dari luar.</li>
</ul>
<p>Untuk itu, ada dua strategi untuk mengatasi masalah ini:</p>
<ol>
<li>Dengan menggunakan external webserver</li>
<li>Menggunakan environment variable</li>
</ol>
<h3 id="menggunakan-external-web-server">Menggunakan External Web Server</h3>
<p>Secara garis besar, cara kerjanya seperti ini:</p>
<ul>
<li>Taruh file <code class="language-plaintext highlighter-rouge">client_secret.json</code> di lokasi yang bisa diunduh oleh aplikasi kita di Heroku.</li>
<li>Supaya tidak perlu menjalankan proses otorisasi OAuth, kita jalankan prosesnya di laptop/PC, kemudian upload hasilnya, yaitu file <code class="language-plaintext highlighter-rouge">StoredCredential</code> ke lokasi tersebut.</li>
<li>Download file <code class="language-plaintext highlighter-rouge">client_secret.json</code> dan <code class="language-plaintext highlighter-rouge">StoredCredential</code> dari lokasi tersebut ke aplikasi kita di Heroku.</li>
</ul>
<p>Kita bisa menaruh kedua file ini di server VPS kita, atau menggunakan layanan cloud seperti Dropbox atau Amazon S3. Cara mengambil file dari kedua layanan ini tidak saya bahas di sini. Sebagai contoh sederhana, kita akan gunakan layanan <a href="https://ngrok.com/">Ngrok</a> supaya laptop kita bisa diakses dari Heroku.</p>
<p>Kita akan mempublikasikan kedua file credentials tersebut agar bisa diakses oleh aplikasi kita di Heroku. Ada dua peralatan yang kita gunakan di sini:</p>
<ul>
<li>web server di laptop supaya folder berisi file credentials bisa diakses melalui HTTP</li>
<li>ngrok untuk mempublikasikan web server lokal tersebut agar bisa diakses dari internet</li>
</ul>
<p>Web server sederhana banyak ditemukan di internet. Sudah ada yang membuatkan <a href="https://gist.github.com/willurd/5720255">rekap daftar web server yang bisa dijalankan dengan satu baris perintah</a>. Pilih saja salah satu, lalu jalankan. Misalnya seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ~/.gmail-api/credentials
python -m SimpleHTTPServer 8000
</code></pre></div></div>
<p>Dia akan segera ready di port <code class="language-plaintext highlighter-rouge">8000</code>. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Serving HTTP on 0.0.0.0 port 8000 ...
</code></pre></div></div>
<p>Selanjutnya, port 8000 tersebut kita publikasikan dengan ngrok</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./ngrok http 8000
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ngrok by @inconshreveable
(Ctrl+C to quit)
Session Status online
Session Expires 7 hours, 15 minutes
Version 2.2.8
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://646f1763.ngrok.io -> localhost:8000
Forwarding https://646f1763.ngrok.io -> localhost:8000
</code></pre></div></div>
<p>Kita bisa gunakan URL yang disediakan ngrok untuk mengambil file dari aplikasi kita. Berikut kode programnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">CLIENT_SECRET_JSON_FILE</span> <span class="o">=</span> <span class="s">"client_secret.json"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">STORED_CREDENTIAL_FILE</span> <span class="o">=</span> <span class="s">"StoredCredential"</span><span class="o">;</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${GMAIL_CREDENTIAL_FILE_SERVER}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">credentialFileServer</span><span class="o">;</span>
<span class="nd">@Bean</span> <span class="nd">@Profile</span><span class="o">(</span><span class="s">"!heroku"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">GoogleClientSecrets</span> <span class="nf">localFileClientSecrets</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">GoogleClientSecrets</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">jsonFactory</span><span class="o">(),</span>
<span class="k">new</span> <span class="nf">InputStreamReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="n">dataStoreFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="no">CLIENT_SECRET_JSON_FILE</span><span class="o">)));</span>
<span class="o">}</span>
<span class="nd">@Bean</span> <span class="nd">@Profile</span><span class="o">(</span><span class="s">"heroku"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">GoogleClientSecrets</span> <span class="nf">downloadClientSecrets</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">downloadCredentialsFile</span><span class="o">(</span><span class="no">CLIENT_SECRET_JSON_FILE</span><span class="o">);</span>
<span class="n">downloadCredentialsFile</span><span class="o">(</span><span class="no">STORED_CREDENTIAL_FILE</span><span class="o">);</span>
<span class="k">return</span> <span class="nc">GoogleClientSecrets</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">jsonFactory</span><span class="o">(),</span>
<span class="k">new</span> <span class="nf">InputStreamReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="n">dataStoreFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="no">CLIENT_SECRET_JSON_FILE</span><span class="o">)));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">downloadCredentialsFile</span><span class="o">(</span><span class="nc">String</span> <span class="n">filename</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">RestTemplate</span> <span class="n">restTemplate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestTemplate</span><span class="o">();</span>
<span class="n">restTemplate</span><span class="o">.</span><span class="na">getMessageConverters</span><span class="o">().</span><span class="na">add</span><span class="o">(</span>
<span class="k">new</span> <span class="nf">ByteArrayHttpMessageConverter</span><span class="o">());</span>
<span class="nc">HttpHeaders</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpHeaders</span><span class="o">();</span>
<span class="n">headers</span><span class="o">.</span><span class="na">setAccept</span><span class="o">(</span><span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_OCTET_STREAM</span><span class="o">));</span>
<span class="nc">HttpEntity</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">entity</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpEntity</span><span class="o"><</span><span class="nc">String</span><span class="o">>(</span><span class="n">headers</span><span class="o">);</span>
<span class="nc">ResponseEntity</span><span class="o"><</span><span class="kt">byte</span><span class="o">[]></span> <span class="n">response</span> <span class="o">=</span> <span class="n">restTemplate</span><span class="o">.</span><span class="na">exchange</span><span class="o">(</span>
<span class="n">credentialFileServer</span> <span class="o">+</span> <span class="s">"/"</span> <span class="o">+</span> <span class="n">filename</span><span class="o">,</span>
<span class="nc">HttpMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">,</span> <span class="n">entity</span><span class="o">,</span> <span class="kt">byte</span><span class="o">[].</span><span class="na">class</span><span class="o">,</span> <span class="s">"1"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getStatusCode</span><span class="o">()</span> <span class="o">==</span> <span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">createDirectories</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">dataStoreFolder</span><span class="o">));</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">dataStoreFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="n">filename</span><span class="o">),</span>
<span class="n">response</span><span class="o">.</span><span class="na">getBody</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kita memisahkan kode program untuk mempersiapkan client secret menjadi satu method tersendiri. Yang satu menyiapkan credential dari file local. Ini adalah kondisi normal seperti sebelumnya. Satu lagi mengambil file credential dari server lain (dalam hal ini laptop yang sudah dipublish dengan ngrok) dan menaruhnya di tempat yang sesuai. Nantinya setiap kali startup, aplikasi kita akan terlebih dulu mengunduh file credential tersebut.</p>
<p>Untuk memilih method mana yang dieksekusi, kita gunakan anotasi <code class="language-plaintext highlighter-rouge">@Profile</code>. Yang satu jalan bila profile <code class="language-plaintext highlighter-rouge">heroku</code> tidak aktif, yaitu bila aplikasi tidak jalan di Heroku. Satu lagi jalan bila aplikasi dideploy ke Heroku.</p>
<p>Agar aplikasi berjalan baik, kita akan setup dua environment variable di Heroku untuk dibaca oleh aplikasi kita. Yang pertama adalah profile <code class="language-plaintext highlighter-rouge">heroku</code> agar method <code class="language-plaintext highlighter-rouge">downloadClientSecrets</code> dijalankan. Berikut perintahnya dengan <code class="language-plaintext highlighter-rouge">heroku-cli</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku config:set SPRING_PROFILES_ACTIVE=heroku
</code></pre></div></div>
<p>Anda juga bisa memasang variabel ini lewat web UI seperti pada <a href="https://software.endy.muhardin.com/java/project-bootstrap-03/">tutorial terdahulu</a>.</p>
<p>Selain itu, kita pasang juga environment variable <code class="language-plaintext highlighter-rouge">GMAIL_CREDENTIAL_FILE_SERVER</code> sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku config:set GMAIL_CREDENTIAL_FILE_SERVER=https://646f1763.ngrok.io
</code></pre></div></div>
<p>Nah, sekarang aplikasi kita sudah bisa dideploy ke Heroku.</p>
<blockquote>
<p>Disclaimer !!! Teknik ngrok di atas tidak untuk dipakai di production, karena tidak ada proteksi apapun terhadap <code class="language-plaintext highlighter-rouge">client_secret.json</code> dan <code class="language-plaintext highlighter-rouge">StoredCredential</code>. Lakukan proteksi yang memadai bila mau dihosting sendiri, atau gunakan layanan dengan security yang baik seperti Dropbox atau Amazon S3.</p>
</blockquote>
<h3 id="menggunakan-environment-variable">Menggunakan Environment Variable</h3>
<p>Secara garis besar, cara kerjanya seperti ini:</p>
<ul>
<li>Simpan isi file <code class="language-plaintext highlighter-rouge">client_secret.json</code> dan <code class="language-plaintext highlighter-rouge">StoredCredential</code> ke dalam environment variable</li>
<li>Pada waktu aplikasi start, baca environment variable tersebut, dan tulis kembali menjadi file</li>
<li>Selanjutnya, aplikasi bisa dijalankan seperti biasa.</li>
</ul>
<p>File <code class="language-plaintext highlighter-rouge">client_secret.json</code> bentuknya text, sedangkan file <code class="language-plaintext highlighter-rouge">StoredCredential</code> adalah file binary. File text bisa langsung kita pasang menjadi environment variable. File binary harus dikonversi dulu menjadi text, supaya bisa digunakan sebagai environment variable. Algoritma konversi (encoding) yang lazim dipakai orang adalah <code class="language-plaintext highlighter-rouge">Base64</code>.</p>
<p>Agar kode program di aplikasi lebih sederhana, kita akan tetap mengkonversi file <code class="language-plaintext highlighter-rouge">client_secret.json</code> dengan <code class="language-plaintext highlighter-rouge">Base64</code>.</p>
<p>Untuk itu, kita buat dulu kode program Java untuk mengkonversi kedua file ini. Kode programnya bisa kita buat sebagai JUnit test supaya mudah dijalankan. Berikut kode program untuk mengkonversi filenya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">KonversiCredentialTests</span> <span class="o">{</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${gmail.folder}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">dataStoreFolder</span><span class="o">;</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testConvertStoredCredential</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">credentialFile</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(</span>
<span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">dataStoreFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span>
<span class="nc">GmailApiConfiguration</span><span class="o">.</span><span class="na">STORED_CREDENTIAL_FILE</span><span class="o">));</span>
<span class="nc">String</span> <span class="n">base64Encoded</span>
<span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">getEncoder</span><span class="o">()</span>
<span class="o">.</span><span class="na">encodeToString</span><span class="o">(</span><span class="n">credentialFile</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">base64Encoded</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testConvertClientSecret</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">clientSecretJson</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(</span>
<span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">dataStoreFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span>
<span class="nc">GmailApiConfiguration</span><span class="o">.</span><span class="na">CLIENT_SECRET_JSON_FILE</span><span class="o">));</span>
<span class="nc">String</span> <span class="n">base64Encoded</span>
<span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">getEncoder</span><span class="o">()</span>
<span class="o">.</span><span class="na">encodeToString</span><span class="o">(</span><span class="n">clientSecretJson</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">base64Encoded</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Setelah dijalankan, kita akan mendapatkan satu baris string di layar.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/27-convert-base64.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/27-convert-base64.png" alt="Convert Base 64" /></a></p>
<p>Kita pasang baris ini sebagai environment variable di Heroku seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/28-heroku-environment.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gmail-api/28-heroku-environment.png" alt="Environment Variable Heroku" /></a></p>
<p>Kita juga bisa setup environment variable melalui Heroku CLI melalui command line seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku config:set CLIENT_SECRET_JSON=blablablayaddayaddayadda
heroku config:set STORED_CREDENTIAL=blablablayaddayaddayadda
</code></pre></div></div>
<p>Kemudian, kita baca environment variable ini pada saat aplikasi dijalankan, dan kita tulis ke file.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">CLIENT_SECRET_JSON_ENV</span> <span class="o">=</span> <span class="s">"CLIENT_SECRET_JSON"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">STORED_CREDENTIAL_ENV</span> <span class="o">=</span> <span class="s">"STORED_CREDENTIAL"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">CLIENT_SECRET_JSON_FILE</span> <span class="o">=</span> <span class="s">"client_secret.json"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">STORED_CREDENTIAL_FILE</span> <span class="o">=</span> <span class="s">"StoredCredential"</span><span class="o">;</span>
<span class="nd">@Bean</span> <span class="nd">@Profile</span><span class="o">(</span><span class="s">"heroku"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">GoogleClientSecrets</span> <span class="nf">environmentVariableClientSecrets</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">restoreEnvironmentVariableToFile</span><span class="o">(</span><span class="no">CLIENT_SECRET_JSON_ENV</span><span class="o">,</span> <span class="no">CLIENT_SECRET_JSON_FILE</span><span class="o">);</span>
<span class="n">restoreEnvironmentVariableToFile</span><span class="o">(</span><span class="no">STORED_CREDENTIAL_ENV</span><span class="o">,</span> <span class="no">STORED_CREDENTIAL_FILE</span><span class="o">);</span>
<span class="k">return</span> <span class="nf">loadGoogleClientSecrets</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">GoogleClientSecrets</span> <span class="nf">loadGoogleClientSecrets</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">GoogleClientSecrets</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">jsonFactory</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">InputStreamReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="n">dataStoreFolder</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="no">CLIENT_SECRET_JSON_FILE</span><span class="o">)));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">restoreEnvironmentVariableToFile</span><span class="o">(</span><span class="nc">String</span> <span class="n">environmentVariableName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">filename</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">createDirectories</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">dataStoreFolder</span><span class="o">));</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">dataStoreFolder</span> <span class="o">+</span>
<span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="n">filename</span><span class="o">),</span>
<span class="nc">Base64</span><span class="o">.</span><span class="na">getDecoder</span><span class="o">().</span><span class="na">decode</span><span class="o">(</span><span class="n">env</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">environmentVariableName</span><span class="o">)));</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Menurut saya, cara kedua ini lebih <em>robust</em>, karena bisa berjalan secara mandiri tanpa perlu setup webserver lain. Juga lebih secure, karena file credential tidak perlu ditaruh di lokasi lain dan tidak perlu dikirim melalui internet.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah cara menggunakan GMail API untuk mengirim notifikasi email dari aplikasi kita. Source code lengkap ada <a href="https://github.com/endymuhardin/belajar-gmail-api">di Github</a>. Lihat juga <a href="https://github.com/endymuhardin/belajar-gmail-api/commits/master">commit history</a> untuk urutan implementasi codingnya.</p>
<p>Semoga bermanfaat ;)</p>
Workflow Git untuk Manajemen Pengembangan Aplikasi2017-10-21T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/workflow-git-manajemen<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/gitgraph.js/1.11.4/gitgraph.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/gitgraph.js/1.11.4/gitgraph.min.js"></script>
<script src="https://software.endy.muhardin.com/files/artivisi-gitgraph.js"></script>
<p>Pada artikel sebelumnya, kita sudah membahas tentang workflow development baik <a href="https://software.endy.muhardin.com/aplikasi/workflow-git-kontributor/">sebagai kontributor</a> maupun <a href="https://software.endy.muhardin.com/aplikasi/workflow-git-maintainer/">sebagai maintainer</a>. Dengan dua artikel tersebut, kita sudah bisa membuat perubahan dalam source code, dan juga bisa menerima dan mengintegrasikan hasil pekerjaan orang lain ke repo utama.</p>
<p>Walaupun demikian, kita belum membahas bagaimana cara mengelola siklus pengembangan aplikasi. Kita terutama ingin mengatasi beberapa permasalahan berikut:</p>
<ul>
<li>bagaimana semua kontributor bisa bekerja sama dengan baik</li>
<li>bagaimana memastikan kode program yang dihasilkan oleh kontributor bisa direview dengan seksama</li>
<li>bagaimana melakukan rilis ke testing server dan production</li>
<li>bagaimana menangani bug yang terjadi di production sambil tetap mengerjakan development untuk versi selanjutnya</li>
<li>bagaimana meng-copy bugfix yang sudah kita kerjakan di atas production release (hotfix) ke development</li>
<li>bagaimana mengotomasi proses rilis menggunakan continuous integration/delivery</li>
</ul>
<p><strong>TLDR;</strong> Workflow yang kita gunakan di ArtiVisi adalah sebagai berikut:</p>
<ul>
<li>single permanent branch : master</li>
<li>develop di topic branch</li>
<li>push branch ke remote</li>
<li>raise pull request dari branch</li>
<li>merge pull request</li>
<li>hapus branch setelah merge</li>
<li>
<p>deploy ketika ada tag dibuat</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">A.B.C-M.xxx</code> : deploy ke dev (otomatis)</li>
<li><code class="language-plaintext highlighter-rouge">A.B.C-RC.xxx</code> : deploy ke test (otomatis)</li>
<li><code class="language-plaintext highlighter-rouge">A.B.C-RELEASE</code> : deploy ke production (manual)</li>
</ul>
</li>
<li>bugfix di release branch, kemudian merge ke master</li>
</ul>
<p>Visualisasinya seperti ini</p>
<div style="overflow: auto;">
<canvas id="gitGraphHorizontal"></canvas>
</div>
<p>Bila setelah membaca masih bingung, bisa nonton <a href="https://youtu.be/gDqT_Wvt3VQ">video penjelasan workflow</a> atau <a href="https://www.youtube.com/watch?v=XZxaY2XvRzE&list=PL9oC_cq7OYbwhs_x2S_Vv9VFRKnoXs8hn">video training Git</a></p>
<p>Buat yang fakir kuota atau lebih suka versi tulisan, berikut penjelasan versi panjangnya …</p>
<!--more-->
<p>Di internet banyak ditemukan berbagai workflow manajemen proyek yang populer, diantaranya:</p>
<ul>
<li><a href="http://nvie.com/posts/a-successful-git-branching-model/">GitFlow</a></li>
<li><a href="https://www.kernel.org/pub/software/scm/git/docs/gitworkflows.html">Git Developer Workflow</a></li>
<li><a href="http://scottchacon.com/2011/08/31/github-flow.html">Github Flow</a></li>
<li><a href="https://about.gitlab.com/2016/07/27/the-11-rules-of-gitlab-flow/">Gitlab Flow</a></li>
<li><a href="https://trunkbaseddevelopment.com/">Trunk Based Development</a></li>
</ul>
<p>Berbagai metodologi di atas semua ingin mengatasi masalah yang telah kita sebutkan di atas. Semua tujuannya sama, tapi ada perbedaan dalam urutan prioritas masalah-masalah tersebut. Sebagai contoh, Gitlab flow memprioritaskan kerja paralel antara development, testing, production, dan hotfix. Trunk based development memprioritaskan continuous delivery. Gitlab flow mengutamakan code review dan CI/CD. Github flow mengutamakan kecepatan rilis ke production.</p>
<p>Semua workflow di atas bisa mengatasi semua masalah yang telah kita sebutkan, tapi dengan prioritas yang berbeda-beda. Oleh karena itu, tidak ada satu metodologi yang paling hebat dan cocok untuk seluruh kondisi. Kita harus menentukan sendiri workflow yang paling cocok untuk kondisi kita sendiri.</p>
<p>Setelah mempelajari dan mencoba semua workflow di atas, berikut beberapa poin kesimpulan yang saya dapatkan :</p>
<h2 id="git-flow">Git Flow</h2>
<p>Gitflow adalah workflow yang paling pertama populer. Ini terutama didukung oleh infografis yang indah dan penjelasan yang gamblang. Selama tahun-tahun awal boomingnya Git, dialah satu-satunya workflow yang dijelaskan secara detail di internet. Oleh karena itu, banyak yang mengadopsinya dan bahkan membuatkan script khusus untuk menjalankannya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/gitflow.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/gitflow.png" alt="Gitflow" /></a></p>
<p>Berjalan beberapa tahun, setelah banyak orang yang mahir menggunakan Git, mulailah banyak terjadi kritik terhadap Gitflow. Detailnya bisa dibaca <a href="http://endoflineblog.com/gitflow-considered-harmful">di sini</a> dan <a href="https://barro.github.io/2016/02/a-succesful-git-branching-model-considered-harmful/">di sini</a>. Pada intinya, berikut poin-poin para kritikus tadi:</p>
<ul>
<li>Terlalu kompleks. Para kritikus menyatakan bahwa kalau sampai dibuatkan scriptnya, berarti terlalu sulit untuk bisa dijalankan oleh manusia biasa. Ini akan menjadi masalah kalau ada programmer baru bergabung. Bisa habis satu pekan sendiri untuk menjelaskannya.</li>
<li>Terlalu banyak branch. Branch develop harusnya tidak perlu ada</li>
</ul>
<p>Walaupun demikian, kita bisa mengambil hal positif dari Gitflow, yaitu caranya mengelola release branch.</p>
<h2 id="git-developer-workflow">Git Developer Workflow</h2>
<p>Ini adalah workflow yang digunakan tim pengembang Git itu sendiri. Workflow ini memiliki 3 permanent branch, yaitu :</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">maint</code> : berisi semua commit yang akan diikutkan pada rilis <code class="language-plaintext highlighter-rouge">maintenance</code> berikutnya. Rilis <code class="language-plaintext highlighter-rouge">maintenance</code> artinya pengembangan dari versi yang sudah dirilis, biasanya berupa tuning performance, perbaikan implementasi, bugfix, dan perubahan minor lainnya. Biasanya maintenance rilis menaikkan minor version, misalnya dari <code class="language-plaintext highlighter-rouge">1.1.0</code> ke <code class="language-plaintext highlighter-rouge">1.1.1</code> atau <code class="language-plaintext highlighter-rouge">1.2.0</code>.</li>
<li><code class="language-plaintext highlighter-rouge">master</code> : berisi semua commit untuk rilis besar berikutnya, misalnya dari <code class="language-plaintext highlighter-rouge">1.2.4</code> ke <code class="language-plaintext highlighter-rouge">2.0.0</code></li>
<li><code class="language-plaintext highlighter-rouge">next</code> : branch untuk mengintegrasikan dan mengetes kontribusi sebelum masuk ke <code class="language-plaintext highlighter-rouge">master</code></li>
</ul>
<p>Selain itu, ada satu lagi branch yang selalu ada, tapi sering dihapus dan dibuat ulang, yaitu :</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">pu</code> atau proposed update. Branch ini dibuat pada saat ingin dilakukan integrasi dan pengetesan dari seluruh kontribusi committer.</li>
</ul>
<h2 id="trunk-based-development">Trunk Based Development</h2>
<p>Ini adalah praktek yang sudah dijalankan sejak jaman awal dibuatnya version control dahulu kala. Trunk artinya branch utama, di Git biasa disebut <code class="language-plaintext highlighter-rouge">master</code>. Di antara yang menggunakan metode ini adalah <a href="https://trunkbaseddevelopment.com/game-changers/index.html#google-revealing-their-monorepo-trunk-2016">Google</a> dan Microsoft.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/trunk-based-development.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/trunk-based-development.png" alt="Trunk Flow" /></a></p>
<p>Beberapa praktek yang dijalankan oleh aliran ini:</p>
<ul>
<li>semua programmer commit ke branch utama (<code class="language-plaintext highlighter-rouge">trunk</code> : istilah Subversion, <code class="language-plaintext highlighter-rouge">master</code> : istilah Git).</li>
<li>hotfix dilakukan di trunk, kemudian di-cherry pick ke release branch. Praktek ini disebut dengan istilah upstream-first-policy.</li>
</ul>
<p>Penggunaan <code class="language-plaintext highlighter-rouge">trunk</code> atau <code class="language-plaintext highlighter-rouge">master</code> sebagai single permanent branch sangat menyederhanakan proses. Branch <code class="language-plaintext highlighter-rouge">master</code> sudah otomatis dibuatkan oleh Git pada waktu kita membuat repository ataupun melakukan clone. Jadi semua programmer pasti punya.</p>
<p>Upstream first policy juga menarik. Idenya adalah dengan melakukan fix di master, maka bug akan selesai untuk selamanya. Bila kita kerjakan di release branch, maka nanti ada kemungkinan programmernya lupa memasukkannya ke trunk sehingga pada waktu kita rilis dari master, bug tersebut akan muncul lagi. Ini disebut dengan istilah <code class="language-plaintext highlighter-rouge">regression bug</code>.</p>
<p>Walaupun demikian, policy ini tidak selalu bisa dilakukan karena :</p>
<ul>
<li>Bugnya belum tentu masih ada di master. Bisa jadi fitur tersebut sudah mengalami modifikasi signifikan, atau bahkan dihilangkan.</li>
<li>Agar bisa di-cherry-pick ke release branch, commitnya harus benar-benar bersih. Bila kondisi sebelum fix berbeda dengan kondisi di release branch, bisa jadi cherry-pick tidak bisa diaplikasikan.</li>
</ul>
<h2 id="metode-lain">Metode Lain</h2>
<p>Secara garis besar, metode lain seperti Github Flow, Gitlab Flow, Bitbucket Flow, dan lainnya tidak jauh berbeda. Gitlab Flow misalnya, memaksimalkan fitur CI/CD Gitlab yang mampu mengetes feature branch, sehingga dia mempromosikan pembuatan Issue, Feature Branch, dan Merge Request.</p>
<h2 id="artivisi-flow">ArtiVisi Flow</h2>
<p>Setelah membaca itu semua, akhirnya kami mencoba merumuskan metodologi yang sesuai dengan kebutuhan internal kami. Beberapa kriteria yang digunakan antara lain:</p>
<ul>
<li>Mudah dipahami. Kami di ArtiVisi banyak menerima anak magang dan fresh graduate. Oleh karena itu, prosedur penggunaan Git harus bisa dipahami dalam beberapa jam saja.</li>
<li>Mengakomodasi code review. Untuk anak magang dan karyawan baru, kita tidak bisa begitu saja langsung memasukkan semua commit ke master. Apalagi kita juga ada beberapa project open source yang kontributornya kadang hanya menyumbang satu commit saja.</li>
<li>Bisa diotomasi dengan CI/CD. Jaman now begini, masa masih harus deploy manual.</li>
<li>Mengakomodasi maintenance release. Artinya, kita ingin tetap menyediakan bugfix untuk versi 2.x sementara kita develop 3.x</li>
</ul>
<p>Hasilnya, kami menggunakan beberapa panduan berikut:</p>
<ul>
<li>Permanent branch hanya satu, yaitu <code class="language-plaintext highlighter-rouge">master</code> : menampung semua kegiatan development</li>
<li>Untuk melakukan development, semua orang membuat topic branch seperti dijelaskan di <a href="https://software.endy.muhardin.com/aplikasi/workflow-git-kontributor/">artikel terdahulu</a>. Committer senior boleh langsung merge ke master, anak magang dan freshman harus direview dulu oleh committer senior. Bila oke, committer senior yang akan merge ke master.</li>
<li>CI/CD dilakukan di <code class="language-plaintext highlighter-rouge">master</code> branch. Bila sukses, maka akan dideploy otomatis ke development server.</li>
<li>
<p>Deployment ke testing, staging, production dilakukan menggunakan tag. Misalnya:</p>
<ul>
<li>Tag <code class="language-plaintext highlighter-rouge">1.0.0-M.001</code> : deploy ke testing</li>
<li>Tag <code class="language-plaintext highlighter-rouge">1.0.0-RC.001</code> : deploy ke testing lain atau staging</li>
<li>Tag <code class="language-plaintext highlighter-rouge">1.0.0-RELEASE</code> : deploy ke production</li>
</ul>
</li>
<li>
<p>Bila ada bug di production, misalnya ada bug dengan nomer <code class="language-plaintext highlighter-rouge">111</code> di rilis <code class="language-plaintext highlighter-rouge">1.2.2-RELEASE</code>, maka:</p>
<ul>
<li>
<p>Buat branch untuk memperbaiki bug. Branch diambil dari tag <code class="language-plaintext highlighter-rouge">1.2.2-RELEASE</code> di master</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git checkout -b fix-111 1.2.2-RELEASE
</code></pre></div> </div>
</li>
<li>
<p>Lakukan bugfixing dan test di sana. Di sini harusnya nanti ada tag <code class="language-plaintext highlighter-rouge">1.2.3-M.xxx</code> dan <code class="language-plaintext highlighter-rouge">1.2.3-RC.xxx</code>.</p>
</li>
<li>
<p>Setelah oke, tag <code class="language-plaintext highlighter-rouge">1.2.3-RELEASE</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git tag 1.2.3-RELEASE
</code></pre></div> </div>
</li>
<li>
<p>Merge ke master</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git checkout master
git merge fix-111
</code></pre></div> </div>
</li>
<li>
<p>Hapus fix branch di local dan remote</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git branch -d fix-111
git push :fix-111 remotename
</code></pre></div> </div>
</li>
</ul>
</li>
</ul>
<h3 id="visualisasi">Visualisasi</h3>
<p>Berikut adalah visualisasi dari skenario di atas.</p>
<div style="overflow: auto;">
<canvas id="gitGraphVertical"></canvas>
</div>
<p>Visualisasi dibuat menggunakan <a href="http://gitgraphjs.com/">Gitgraph.js</a></p>
<h2 id="otomasi">Otomasi</h2>
<p>Workflow di atas bisa diotomasi menggunakan Gitlab CI dengan script sebagai berikut:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">alpine:latest</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">build</span>
<span class="pi">-</span> <span class="s">package</span>
<span class="pi">-</span> <span class="s">deploy</span>
<span class="na">compile-test</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "Build $CI_COMMIT_SHA in branch $CI_COMMIT_REF_SLUG with tag $CI_COMMIT_TAG"</span>
<span class="na">static-analysis</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "Static Code Analysis, parallel run with compiling"</span>
<span class="na">build-dan-package</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">package</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "Build $CI_COMMIT_SHA in branch $CI_COMMIT_REF_SLUG with tag $CI_COMMIT_TAG"</span>
<span class="pi">-</span> <span class="s">cat README.md > README-$CI_COMMIT_SHA.md</span>
<span class="pi">-</span> <span class="s">echo "Version $CI_COMMIT_SHA" >> README-$CI_COMMIT_SHA.md</span>
<span class="na">artifacts</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">README-$CI_COMMIT_SHA.md</span>
<span class="na">deploy-to-development</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/-M\./</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "Deploy build" +$CI_COMMIT_SHA+ " to development server"</span>
<span class="pi">-</span> <span class="s">cat README-$CI_COMMIT_SHA.md</span>
<span class="na">deploy-to-testing</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/-RC\./</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "Deploy build" +$CI_COMMIT_SHA+ " to testing server"</span>
<span class="pi">-</span> <span class="s">cat README-$CI_COMMIT_SHA.md</span>
<span class="na">deploy-to-production</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/-RELEASE$/</span>
<span class="na">when</span><span class="pi">:</span> <span class="s">manual</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "Deploy build" +$CI_COMMIT_SHA+ " to production server"</span>
<span class="pi">-</span> <span class="s">cat README-$CI_COMMIT_SHA.md</span>
</code></pre></div></div>
<p>Semoga bermanfaat.</p>
Workflow Git untuk Maintainer Project Open Source2017-10-11T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/workflow-git-maintainer<p>Pada artikel sebelumnya, kita sudah membahas tentang apa yang harus dilakukan oleh kontributor agar hasil pekerjaannya siap untuk digabungkan dengan repository utama/induk/upstream, yaitu membuat Pull Request.</p>
<p>Sebagai maintainer project, tugas kita adalah memilah semua kontribusi yang masuk. Bila kontribusinya bagus, maka kita masukkan ke dalam project. Bila kurang bagus, kita pandu untuk memperbaikinya.</p>
<p>Kita bisa menerima kontribusi dalam berbagai bentuk, misalnya:</p>
<ul>
<li>Pull Request di aplikasi version control (Github, Gitlab, Bitbucket, dsb)</li>
<li>Link repository kontributor dan branchnya</li>
<li>File patch</li>
</ul>
<p>Begitu kita mendapatkan kontribusi, ada beberapa hal yang harus kita lakukan, yaitu:</p>
<ul id="markdown-toc">
<li><a href="#pull-request-di-aplikasi" id="markdown-toc-pull-request-di-aplikasi">Pull Request di Aplikasi</a></li>
<li><a href="#persiapan-review" id="markdown-toc-persiapan-review">Persiapan Review</a> <ul>
<li><a href="#unduh-kontribusi-dari-branch" id="markdown-toc-unduh-kontribusi-dari-branch">Unduh Kontribusi dari Branch</a></li>
<li><a href="#commit-tercampur-dengan-task-lain" id="markdown-toc-commit-tercampur-dengan-task-lain">Commit Tercampur dengan Task Lain</a></li>
<li><a href="#unduh-satu-commit" id="markdown-toc-unduh-satu-commit">Unduh Satu Commit</a></li>
<li><a href="#file-patch" id="markdown-toc-file-patch">File Patch</a></li>
</ul>
</li>
<li><a href="#berbagai-kemungkinan-hasil" id="markdown-toc-berbagai-kemungkinan-hasil">Berbagai Kemungkinan Hasil</a></li>
<li><a href="#menerima-hasil" id="markdown-toc-menerima-hasil">Menerima Hasil</a></li>
</ul>
<!--more-->
<h2 id="pull-request-di-aplikasi">Pull Request di Aplikasi</h2>
<p>Semua aplikasi version control populer yang mainstream sekarang ini – seperti Github, Gitlab, Bitbucket – biasanya sudah memiliki fitur manajemen Pull Request. Ini memudahkan orang untuk mengirim kontribusi ke project yang dihosting di aplikasi tersebut. Secara garis besar caranya sama, walaupun disebut dengan istilah yang berbeda-beda. Misalnya, Gitlab dan Bitbucket menyebutnya dengan istilah Merge Request.</p>
<p>Sebagai ilustrasi, kita akan bahas yang Github saja. Aplikasi lain tidak jauh berbeda.</p>
<p>Pertama, kita bisa lihat daftar Pull Request dengan membuka tab Pull Request.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/12-daftar-pull-request.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/12-daftar-pull-request.png" alt="Daftar Pull Request" /></a></p>
<p>Kemudian buka detailnya dengan mengklik salah satu pull request yang ingin diproses. Kita akan melihat banyak informasi di sana.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/13-detail-pull-request.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/13-detail-pull-request.png" alt="Detail Pull Request" /></a></p>
<p>Untuk perubahan sederhana dan non-coding, kita bisa langsung klik commitnya dan melihat perubahan yang dikirimkan. Selanjutnya kita juga bisa langsung menerima kontribusi dengan klik tombol <code class="language-plaintext highlighter-rouge">Merge Pull Request</code>.</p>
<p>Walaupun demikian, cara ini tidak dianjurkan. Khususnya untuk kontribusi kode program. Kita harus cek dulu apakah kode programnya bisa dijalankan dengan baik dan tidak mengganggu fitur-fitur yang lain.</p>
<p>Best practicesnya adalah mengunduh dulu kontribusinya di komputer kita dan melakukan review.</p>
<h2 id="persiapan-review">Persiapan Review</h2>
<p>Pertama, kita pastikan dulu kondisi lokal kita sudah up to date dengan remote.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout master
git pull upstream master
</code></pre></div></div>
<p>Sebelum melakukan review, sebaiknya kita buat dulu branch khusus untuk integrasi dan review.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git branch review-implementasi-lombok
git checkout review-implementasi-lombok
</code></pre></div></div>
<p>Nah, sekarang kita siap menarik perubahan yang dikirim kontributor. Caranya berbeda-beda tergantung metode pengiriman kontribusi. Beberapa kondisi yang biasa terjadi:</p>
<ul>
<li>kontributor memiliki repository yang bisa diakses online, kontribusinya terdiri dari beberapa commit dan sudah dikelompokkan dalam satu branch</li>
<li>kontribusi terdiri dari beberapa commit, tercampur dalam branch master dengan commit lain yang tidak berkaitan</li>
<li>kontribusi hanya terdiri dari satu commit saja</li>
<li>kontribusi dikirim dalam bentuk file, bisa via email, layanan sharing file, whatsapp/telegram, dsb</li>
</ul>
<h3 id="unduh-kontribusi-dari-branch">Unduh Kontribusi dari Branch</h3>
<p>Kasus pertama yang paling sederhana. Biasanya dilakukan oleh kontributor yang sudah berpengalaman. Kita tinggal <code class="language-plaintext highlighter-rouge">pull</code> saja branch tersebut. Misalnya repo kontributor ada di <code class="language-plaintext highlighter-rouge">https://github.com/endymuhardin/aplikasi-dosen.git</code> branchnya <code class="language-plaintext highlighter-rouge">implementasi-lombok</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout review-implementasi-lombok
git pull https://github.com/endymuhardin/aplikasi-dosen.git implementasi-lombok
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>From https://github.com/endymuhardin/aplikasi-dosen
* branch implementasi-lombok -> FETCH_HEAD
Updating fe9c196..d8686f6
Fast-forward
pom.xml | 5 ++
src/main/java/id/ac/tazkia/dosen/entity/BidangIlmu.java | 36 +-----------
src/main/java/id/ac/tazkia/dosen/entity/BuktiKinerja.java | 34 +-----------
src/main/java/id/ac/tazkia/dosen/entity/BuktiKinerjaKegiatan.java | 35 +-----------
src/main/java/id/ac/tazkia/dosen/entity/BuktiPenugasan.java | 35 +-----------
src/main/java/id/ac/tazkia/dosen/entity/BuktiPenugasanKegiatan.java | 35 +-----------
src/main/java/id/ac/tazkia/dosen/entity/Dosen.java | 218 +------------------------------------------------------------------------
src/main/java/id/ac/tazkia/dosen/entity/Fakultas.java | 26 +--------
src/main/java/id/ac/tazkia/dosen/entity/JenisBuktiKegiatan.java | 27 +--------
src/main/java/id/ac/tazkia/dosen/entity/JenisKegiatan.java | 50 +----------------
src/main/java/id/ac/tazkia/dosen/entity/JenisPengajuanDokumen.java | 34 +-----------
src/main/java/id/ac/tazkia/dosen/entity/JenisSurat.java | 34 +-----------
src/main/java/id/ac/tazkia/dosen/entity/KategoriBuktiKegiatan.java | 28 +---------
src/main/java/id/ac/tazkia/dosen/entity/KategoriKegiatan.java | 28 +---------
src/main/java/id/ac/tazkia/dosen/entity/Kecamatan.java | 35 +-----------
src/main/java/id/ac/tazkia/dosen/entity/KegiatanBelajarMengajar.java | 107 +-----------------------------------
src/main/java/id/ac/tazkia/dosen/entity/KegiatanDosen.java | 108 +-----------------------------------
src/main/java/id/ac/tazkia/dosen/entity/Kota.java | 35 +-----------
src/main/java/id/ac/tazkia/dosen/entity/PengajuanDosenProfile.java | 148 +------------------------------------------------
src/main/java/id/ac/tazkia/dosen/entity/Permission.java | 34 +++---------
src/main/java/id/ac/tazkia/dosen/entity/PoinKegiatan.java | 42 +-------------
src/main/java/id/ac/tazkia/dosen/entity/ProgramStudi.java | 44 +--------------
src/main/java/id/ac/tazkia/dosen/entity/Provinsi.java | 28 +---------
src/main/java/id/ac/tazkia/dosen/entity/Role.java | 36 +-----------
src/main/java/id/ac/tazkia/dosen/entity/SatuanHasilKegiatan.java | 27 +--------
src/main/java/id/ac/tazkia/dosen/entity/SuratTugas.java | 51 +----------------
src/main/java/id/ac/tazkia/dosen/entity/User.java | 51 +----------------
src/main/java/id/ac/tazkia/dosen/entity/UserPassword.java | 26 +--------
28 files changed, 81 insertions(+), 1316 deletions(-)
</code></pre></div></div>
<p>Perubahan sudah masuk ke repo lokal. Kita bisa buka filenya, jalankan aplikasinya, review kualitas kode program, dan sebagainya.</p>
<h3 id="commit-tercampur-dengan-task-lain">Commit Tercampur dengan Task Lain</h3>
<p>Kalau saya sebagai maintainer, tidak mau repot dengan yang satu ini. Langsung saja berikan feedback kepada kontributor untuk merapikan kontribusinya. Bila dia memang ingin kontribusi ke dua hal berbeda, minimal commitnya dipisahkan. Akan lebih ideal kalau branchnya juga dipisah.</p>
<h3 id="unduh-satu-commit">Unduh Satu Commit</h3>
<p>Seringkali terjadi orang malas membuat branch. Misalnya dia hanya ingin menambahkan satu hal kecil saja. Koreksi typo, penamaan variabel, komentar, atau hal lain yang remeh temeh. Kemudian dia memberikan ke kita commit-id perubahan tersebut. Di Github, biasanya sudah ada URL patch untuk satu commit tertentu, misalnya <a href="https://github.com/endymuhardin/aplikasi-dosen/commit/d8686f6cc4306b75109d88e2834f7e58456c60f6.patch">https://github.com/endymuhardin/aplikasi-dosen/commit/d8686f6cc4306b75109d88e2834f7e58456c60f6.patch</a></p>
<p>Kita sebagai maintainer bisa langsung tarik dari URL tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout review-implementasi-lombok
curl https://github.com/endymuhardin/aplikasi-dosen/commit/d8686f6cc4306b75109d88e2834f7e58456c60f6.patch | git am
</code></pre></div></div>
<p>Kita gunakan perintah <code class="language-plaintext highlighter-rouge">git am</code> untuk mengunduh file patch, kemudian menggabungkannya ke source code yang ada. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 50730 0 50730 0 0 8804 0 --:--:-- 0:00:05 --:--:-- 11263
Applying: implement lombok, remove getter/setter
.git/rebase-apply/patch:1570: new blank line at EOF.
+
warning: 1 line adds whitespace errors
</code></pre></div></div>
<p>Perubahan sudah masuk, kita tinggal review dan test.</p>
<h3 id="file-patch">File Patch</h3>
<p>Ada kalanya kita menerima file dengan format patch. Misalnya, file tersebut kita taruh di folder <code class="language-plaintext highlighter-rouge">/tmp</code>. Maka kita bisa proses file tadi dengan <code class="language-plaintext highlighter-rouge">git am</code> seperti pada bagian sebelumnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git am < /tmp/0001-implement-lombok-remove-getter-setter.patch
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.git/rebase-apply/patch:1570: new blank line at EOF.
+
warning: 1 line adds whitespace errors.
</code></pre></div></div>
<p>Perubahan siap kita test dan review.</p>
<h2 id="berbagai-kemungkinan-hasil">Berbagai Kemungkinan Hasil</h2>
<p>Pada waktu kita menggabungkan (<code class="language-plaintext highlighter-rouge">merge</code>) kontribusi orang, ada beberapa hal yang bisa terjadi:</p>
<ul>
<li>kontribusinya oke : bisa langsung diterima.</li>
<li>terjadi merge conflict : saya suruh perbaiki dulu. Biasanya ini terjadi karena kontributor coding di atas versi upstream yang jadul. Solusinya, mereka harus pull dari upstream terbaru, kemudian <code class="language-plaintext highlighter-rouge">rebase</code> topic branch ke upstream yang baru tersebut.</li>
<li>minor problem : saya minta perbaiki, commit lagi ke topic branch yang sama, kemudian notifikasi saya agar saya coba merge lagi.</li>
</ul>
<h2 id="menerima-hasil">Menerima Hasil</h2>
<p>Setelah kontribusi kita terima, kita akan publish ke repo utama, agar orang lain bisa ikut menikmati kontribusi tersebut. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout master
git merge review-implementasi-lombok
git push upstream master
</code></pre></div></div>
<p>Kita bisa menghapus branch <code class="language-plaintext highlighter-rouge">review-implementasi-lombok</code> tadi, karena sudah tidak dipakai lagi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git branch -d review-implementasi-lombok
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Deleted branch review-implementasi-lombok (was d9c732c).
</code></pre></div></div>
<p>Selanjutnya, menjadi tanggung jawab masing-masing kontributor untuk mengupdate local dan remote reponya masing-masing.</p>
<p>Selamat mencoba, semoga bermanfaat.</p>
Workflow Git untuk Kontributor Project Open Source2017-10-09T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/workflow-git-kontributor<p>Beberapa waktu belakangan ini, saya membuat beberapa aplikasi yang kemudian dirilis secara open source. Karena kultur open-source di Indonesia masih berkutat di level user, maka masih jarang yang mau ikut berkontribusi. Mungkin perlu dukungan dari perusahaan agar karyawannya terlibat dalam project open source, ataupun merilis aplikasi yang dibuatnya dengan lisensi open source.</p>
<p>Kendala-kendala yang biasanya terdengar antara lain:</p>
<ul>
<li>tidak punya cukup waktu luang</li>
<li>tidak tertarik dengan aplikasi atau teknologi yang digunakan</li>
<li>merasa belum cukup mahir coding</li>
<li>tidak familiar dengan cara kerja menggunakan Git</li>
</ul>
<p>Masalah yang pertama dan kedua, saya tidak ada solusinya.</p>
<p>Untuk yang ketiga, seharusnya coba saja kontribusi. Satu dua baris tetap diterima. Kalaupun ada yang perlu diperbaiki, biasanya akan saya berikan <a href="https://github.com/idtazkia/aplikasi-dosen/pull/1">feedback yang jelas</a>.</p>
<p>Problem keempat, sebetulnya saya sudah buatkan <a href="https://youtu.be/gDqT_Wvt3VQ">video tutorialnya</a>.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/gDqT_Wvt3VQ" frameborder="0" allowfullscreen=""></iframe>
<p>Tapi agar lebih jelas, saya akan buatkan rangkuman langkah-langkah dan perintah Git yang harus dilakukan.</p>
<ul id="markdown-toc">
<li><a href="#persiapan-project" id="markdown-toc-persiapan-project">Persiapan Project</a> <ul>
<li><a href="#fork-repository" id="markdown-toc-fork-repository">Fork Repository</a></li>
<li><a href="#clone-repository" id="markdown-toc-clone-repository">Clone Repository</a></li>
<li><a href="#mendaftarkan-repository-asal" id="markdown-toc-mendaftarkan-repository-asal">Mendaftarkan Repository Asal</a></li>
<li><a href="#buka-dengan-editor" id="markdown-toc-buka-dengan-editor">Buka dengan Editor</a></li>
</ul>
</li>
<li><a href="#membuat-perubahan" id="markdown-toc-membuat-perubahan">Membuat Perubahan</a> <ul>
<li><a href="#membuat-topic-branch" id="markdown-toc-membuat-topic-branch">Membuat Topic Branch</a></li>
<li><a href="#menyimpan-perubahan" id="markdown-toc-menyimpan-perubahan">Menyimpan Perubahan</a></li>
<li><a href="#upload-perubahan" id="markdown-toc-upload-perubahan">Upload Perubahan</a></li>
<li><a href="#membuat-pull-request" id="markdown-toc-membuat-pull-request">Membuat Pull Request</a></li>
<li><a href="#berbagai-alternatif-mekanisme-pull-request" id="markdown-toc-berbagai-alternatif-mekanisme-pull-request">Berbagai Alternatif Mekanisme Pull Request</a></li>
</ul>
</li>
<li><a href="#sinkronisasi-dengan-project-asal" id="markdown-toc-sinkronisasi-dengan-project-asal">Sinkronisasi dengan project asal</a> <ul>
<li><a href="#mengunduh-perubahan-dari-upstream" id="markdown-toc-mengunduh-perubahan-dari-upstream">Mengunduh Perubahan dari Upstream</a></li>
<li><a href="#sesuaikan-topic-branch-dengan-upstream-latest" id="markdown-toc-sesuaikan-topic-branch-dengan-upstream-latest">Sesuaikan Topic Branch dengan Upstream Latest</a></li>
</ul>
</li>
<li><a href="#revisi-pull-request" id="markdown-toc-revisi-pull-request">Revisi Pull Request</a></li>
<li><a href="#setelah-pull-request-diterima" id="markdown-toc-setelah-pull-request-diterima">Setelah Pull Request Diterima</a></li>
<li><a href="#penutup" id="markdown-toc-penutup">Penutup</a></li>
</ul>
<!--more-->
<h2 id="persiapan-project">Persiapan Project</h2>
<p>Sebelum bisa berkontribusi, terlebih dulu kita siapkan repository baik di remote maupun di lokal. Agar bisa menjalankan langkah-langkah berikut dengan baik, pastikan Anda sudah memiliki akun Github dan sudah menginstal aplikasi command line Git di laptop/PC. Cara-caranya sudah pernah saya jelaskan di <a href="https://software.endy.muhardin.com/aplikasi/instalasi-git-di-windows/">artikel ini</a>.</p>
<h3 id="fork-repository">Fork Repository</h3>
<p>Forking repository artinya kita mengcopy repository project asal di akun pembuatnya ke akun kita. Ini biasanya dilakukan karena kita tidak punya akses untuk langsung membuat perubahan di repository asal. Jadi kita buat perubahan di akun sendiri untuk kemudian minta maintainer/admin project untuk mengambil perubahan yang kita buat.</p>
<p>Pertama, kita copy dulu repositorynya dari akun <code class="language-plaintext highlighter-rouge">idtazkia</code> ke akun kita sendiri. Di Github sudah ada tombolnya, tinggal ditekan saja.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/01-tombol-fork.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/01-tombol-fork.png" alt="Tombol Fork" /></a></p>
<p>Setelah itu, dia akan membuat fork. Tunggu saja sebentar, kira-kira beberapa detik.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/02-sedang-fork.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/02-sedang-fork.png" alt="Sedang Fork" /></a></p>
<p>Hasilnya, kita akan memiliki copy dari repo tadi di akun kita sendiri.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/03-hasil-fork.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/03-hasil-fork.png" alt="Hasil Fork" /></a></p>
<h3 id="clone-repository">Clone Repository</h3>
<p>Agar file-file bisa diedit, kita harus ambil dulu repository tersebut ke laptop/PC kita. Kita membutuhkan URL repository yang bisa didapatkan dengan cara menekan tombol hijau <code class="language-plaintext highlighter-rouge">Clone or Download</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/04-tombol-clone.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/04-tombol-clone.png" alt="Tombol Clone" /></a></p>
<p>Pastikan kita clone repository di akun kita sendiri (<code class="language-plaintext highlighter-rouge">endymuhardin</code>), bukan di akun repository asal (<code class="language-plaintext highlighter-rouge">idtazkia</code>).</p>
<p>Buka command prompt, kemudian pindah ke folder yang diinginkan, lalu lakukan <code class="language-plaintext highlighter-rouge">git clone</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd project-saya
git clone git@github.com:endymuhardin/aplikasi-dosen.git
</code></pre></div></div>
<p>Setelah perintah di atas dijalankan, harusnya di komputer kita akan ada folder baru sesuai nama repository, yaitu <code class="language-plaintext highlighter-rouge">aplikasi-dosen</code>. Jangan lupa untuk masuk / pindah ke folder tersebut sebelum menjalankan perintah-perintah berikutnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd aplikasi-dosen
</code></pre></div></div>
<h3 id="mendaftarkan-repository-asal">Mendaftarkan Repository Asal</h3>
<p>Tentunya yang nantinya akan berkontribusi ke project ini bukan cuma kita saja. Ada orang lain yang mungkin lebih banyak ikut coding. Untuk itu, kita perlu secara berkala melakukan sinkronisasi dengan repo asal agar perubahan yang dibuat orang bisa kita dapatkan. Biasanya maintainer/admin project asal akan mengecek dulu apakah perubahan yang kita buat kompatibel atau tidak dengan perubahan orang lain yang sudah masuk duluan. Akan lebih baik kalau kita mengetes sendiri kompatibilitas tersebut sebelum mengirim kontribusi. Untuk itu, kita perlu mendapatkan perubahan terbaru di repo asal.</p>
<p>Repository asal biasanya disebut dengan istilah <code class="language-plaintext highlighter-rouge">upstream</code>. Buka halaman project asal di Github, kemudian ambil URLnya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/05-url-repo-asal.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/05-url-repo-asal.png" alt="URL Repo Asal" /></a></p>
<p>Lalu tambahkan sebagai remote repository.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add upstream git@github.com:idtazkia/aplikasi-dosen.git
</code></pre></div></div>
<p>Hasilnya bisa dicek dengan perintah <code class="language-plaintext highlighter-rouge">git remote -v</code>. Harusnya ada 2 remote yang terdaftar, yaitu:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">origin</code> : repo di akun kita sendiri</li>
<li><code class="language-plaintext highlighter-rouge">upstream</code> : repo di akun asalnya</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>origin git@github.com:endymuhardin/aplikasi-dosen.git (fetch)
origin git@github.com:endymuhardin/aplikasi-dosen.git (push)
upstream git@github.com:idtazkia/aplikasi-dosen.git (fetch)
upstream git@github.com:idtazkia/aplikasi-dosen.git (push)
</code></pre></div></div>
<h3 id="buka-dengan-editor">Buka dengan Editor</h3>
<p>Selanjutnya project tersebut bisa kita buka di editor.</p>
<h2 id="membuat-perubahan">Membuat Perubahan</h2>
<p>Setelah project berhasil dibuka dan dijalankan di komputer kita sendiri, barulah kita bisa mulai membuat sesuatu. Daftar pekerjaan yang bisa kita lakukan bisa dilihat di daftar <code class="language-plaintext highlighter-rouge">Issues</code> di Github. Silahkan pilih yang kita bisa kerjakan. Sebagai contoh, misalnya kita ingin memasang library <code class="language-plaintext highlighter-rouge">lombok</code>. Berikut adalah prosedurnya.</p>
<h3 id="membuat-topic-branch">Membuat Topic Branch</h3>
<p>Git memiliki fitur <code class="language-plaintext highlighter-rouge">branch</code>. Secara singkat, fitur ini berguna untuk membuat beberapa copy dari folder project kita ini. Dengan demikian, kita bisa dengan mudah mengembalikan posisi file dan folder seperti semula (sebelum diedit). Manfaatnya, misalnya kita belum selesai mengerjakan hal yang kita akan buat, tapi kita ingin mengunduh perubahan terbaru yang sudah rilis di <code class="language-plaintext highlighter-rouge">upstream</code>. Agar tidak bercampur, kita unduh perubahan dari <code class="language-plaintext highlighter-rouge">upstream</code> di branch lain.</p>
<p>Tanpa tools version control seperti Git, skenario di atas kita lakukan dengan mengcopy foldernya, kemudian rename. Hasilnya nanti seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -l
aplikasi-dosen-asli
aplikasi-dosen-implement-validasi
aplikasi-dosen-bugfix-format-tanggal
</code></pre></div></div>
<p>Tentu ini kurang elok dan tidak kekinian.</p>
<p>Idealnya, pekerjaan kita lakukan di branch khusus untuk pekerjaan tersebut. Jadi untuk implementasi validasi yang akan kita lakukan, kita buat dulu branchnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git branch implementasi-lombok
</code></pre></div></div>
<p>Kemudian kita masuk ke branch tersebut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout implementasi-lombok
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Switched to branch 'implementasi-lombok'
</code></pre></div></div>
<p>Atau kita bisa lakukan dengan satu perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout -b implementasi-lombok
</code></pre></div></div>
<p>Kita bisa lihat daftar branch yang ada dengan perintah <code class="language-plaintext highlighter-rouge">git branch -a</code>. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>master
* implementasi-lombok
remotes/origin/HEAD -> origin/master
remotes/origin/master
</code></pre></div></div>
<p>Perintah di atas bisa kita jalankan bila kita lupa sedang berada di branch mana. Branch yang sedang kita tempati ditandai dengan tanda <code class="language-plaintext highlighter-rouge">*</code>.</p>
<p>Setelah berada di branch yang tepat, kita bisa mulai mengedit file.</p>
<h3 id="menyimpan-perubahan">Menyimpan Perubahan</h3>
<p>Source code sudah kita edit, dan juga tidak lupa kita tes dulu di komputer lokal. Jangan sampai mengirim kode program yang masih error.</p>
<p>Berikutnya, kita simpan perubahan tersebut ke repository lokal. Cek dulu perubahannya dengan <code class="language-plaintext highlighter-rouge">git status</code>. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>On branch implement-lombok
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: pom.xml
modified: src/main/java/id/ac/tazkia/dosen/entity/BidangIlmu.java
modified: src/main/java/id/ac/tazkia/dosen/entity/BuktiKinerja.java
modified: src/main/java/id/ac/tazkia/dosen/entity/BuktiKinerjaKegiatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/BuktiPenugasan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/BuktiPenugasanKegiatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/Dosen.java
modified: src/main/java/id/ac/tazkia/dosen/entity/Fakultas.java
modified: src/main/java/id/ac/tazkia/dosen/entity/Jabatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/JenisBuktiKegiatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/JenisKegiatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/JenisPengajuanDokumen.java
modified: src/main/java/id/ac/tazkia/dosen/entity/JenisSurat.java
modified: src/main/java/id/ac/tazkia/dosen/entity/KategoriBuktiKegiatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/KategoriKegiatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/Kecamatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/KegiatanBelajarMengajar.java
modified: src/main/java/id/ac/tazkia/dosen/entity/KegiatanDosen.java
modified: src/main/java/id/ac/tazkia/dosen/entity/Kota.java
modified: src/main/java/id/ac/tazkia/dosen/entity/MataKuliah.java
modified: src/main/java/id/ac/tazkia/dosen/entity/PasswordResetToken.java
modified: src/main/java/id/ac/tazkia/dosen/entity/PengajuanDosenDokumen.java
modified: src/main/java/id/ac/tazkia/dosen/entity/PengajuanDosenProfile.java
modified: src/main/java/id/ac/tazkia/dosen/entity/Permission.java
modified: src/main/java/id/ac/tazkia/dosen/entity/PoinKegiatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/ProgramStudi.java
modified: src/main/java/id/ac/tazkia/dosen/entity/Provinsi.java
modified: src/main/java/id/ac/tazkia/dosen/entity/Role.java
modified: src/main/java/id/ac/tazkia/dosen/entity/SatuanHasilKegiatan.java
modified: src/main/java/id/ac/tazkia/dosen/entity/SuratTugas.java
modified: src/main/java/id/ac/tazkia/dosen/entity/User.java
modified: src/main/java/id/ac/tazkia/dosen/entity/UserPassword.java
</code></pre></div></div>
<p>Jangan lupa dites dulu ya. Setelah yakin oke, baru simpan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
git commit -m "implement lombok, remove getter/setter"
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[implement-lombok bdd83eb] implement lombok, remove getter/setter
32 files changed, 92 insertions(+), 1461 deletions(-)
</code></pre></div></div>
<h3 id="upload-perubahan">Upload Perubahan</h3>
<p>Berikutnya, kita upload perubahan ke Github, karena langkah sebelumnya cuma menyimpan di lokal saja.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push
</code></pre></div></div>
<p>Karena ini branch baru, belum ada di Github, kita akan mendapat pesan error</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push
fatal: The current branch implement-lombok has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin implement-lombok
</code></pre></div></div>
<p>Ikuti petunjuknya, copas dan jalankan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push --set-upstream origin implement-lombok
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Counting objects: 42, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (38/38), done.
Writing objects: 100% (42/42), 4.20 KiB | 614.00 KiB/s, done.
Total 42 (delta 34), reused 0 (delta 0)
remote: Resolving deltas: 100% (34/34), completed with 29 local objects.
To github.com:endymuhardin/aplikasi-dosen.git
* [new branch] implement-lombok -> implement-lombok
Branch implement-lombok set up to track remote branch implement-lombok from origin.
</code></pre></div></div>
<h3 id="membuat-pull-request">Membuat Pull Request</h3>
<p>Pull request artinya notifikasi ke administrator/maintainer project asal untuk melihat perubahan yang sudah kita lakukan. Bila perubahannya dinilai bermanfaat dan bisa dipakai (tidak error, kualitas coding rapih, dan berbagai kriteria lain), maka maintainer akan mengambil (<code class="language-plaintext highlighter-rouge">pull</code>) perubahan tersebut dari repo kita. Bila ternyata kurang sesuai, kita akan diminta untuk merevisi.</p>
<p>Di halaman web repo kita di Github, sudah tersedia tombol untuk melakukan Pull Request.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/06-tombol-pull-request.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/06-tombol-pull-request.png" alt="Tombol Pull Request" /></a></p>
<p>Langsung saja tekan, nanti kita akan mendapati form input Pull Request.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/07-form-pull-request.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/07-form-pull-request.png" alt="Form Pull Request" /></a></p>
<p>Klik tombol <code class="language-plaintext highlighter-rouge">Create Pull Request</code>. Project maintainer akan mendapatkan notifikasi dari Github bahwa ada pull request dari kita. Apa yang harus dilakukan oleh maintainer akan kita bahas pada artikel tersendiri. Sedangkan tugas kita sementara ini sudah selesai.</p>
<h3 id="berbagai-alternatif-mekanisme-pull-request">Berbagai Alternatif Mekanisme Pull Request</h3>
<p>Yang kita bahas di atas adalah pull request menggunakan fasilitas yang disediakan Github. Akan tetapi, sebetulnya aplikasi Git yang kita gunakan untuk versioning sudah lebih dulu ada sebelum adanya layanan Github. Jadi sebenarnya banyak cara lain untuk membuat Pull Request selain menggunakan fitur Github. Bahkan pembuat Git sendiri, yaitu om Linus yang terkenal itu, <a href="https://github.com/torvalds/linux/pull/17#issuecomment-5654674">menolak menggunakan fitur Pull Request Github</a>.</p>
<p>Intinya Pull Request adalah mengirimkan perubahan yang kita lakukan ke pengelola (maintainer) dari project open source tersebut. Ada beberapa cara untuk mengirimkannya, misalnya:</p>
<ul>
<li>memberikan link/URL repository kita ke maintainer. Link ini bisa dikirim melalui email, whatsapp, telegram, sms, terserahlah metode apa yang ingin kita gunakan.</li>
<li>membuat patch/diff/selisih yang berisi perubahan yang kita lakukan. Kemudian mengirimkan patch tersebut melalui email. Cara inilah yang dipilih oleh om Linus.</li>
</ul>
<p>URL repo bisa kita dapatkan seperti pada langkah clone di atas. Jangan lupa untuk memberitahukan branch yang ingin kita kontribusikan ke maintainer.</p>
<p>Sedangkan bila kita ingin membuat file <code class="language-plaintext highlighter-rouge">patch</code> untuk dikirim melalui email, berikut perintahnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout implementasi-lombok
git format-patch master..implementasi-lombok
</code></pre></div></div>
<p>Perintah di atas akan menghasilkan file <code class="language-plaintext highlighter-rouge">0001-implement-lombok-remove-getter-setter.patch</code></p>
<p>Isi filenya kira-kira seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>From d8686f6cc4306b75109d88e2834f7e58456c60f6 Mon Sep 17 00:00:00 2001
From: Endy Muhardin <endy.muhardin@gmail.com>
Date: Wed, 11 Oct 2017 10:55:14 +0700
Subject: [PATCH] implement lombok, remove getter/setter
---
pom.xml | 5 +
.../java/id/ac/tazkia/dosen/entity/BidangIlmu.java | 36 +---
.../id/ac/tazkia/dosen/entity/BuktiKinerja.java | 34 +---
.../tazkia/dosen/entity/BuktiKinerjaKegiatan.java | 35 +---
.../id/ac/tazkia/dosen/entity/BuktiPenugasan.java | 35 +---
.../dosen/entity/BuktiPenugasanKegiatan.java | 35 +---
src/main/java/id/ac/tazkia/dosen/entity/Dosen.java | 218 +--------------------
.../java/id/ac/tazkia/dosen/entity/Fakultas.java | 26 +--
.../ac/tazkia/dosen/entity/JenisBuktiKegiatan.java | 27 +--
.../id/ac/tazkia/dosen/entity/JenisKegiatan.java | 50 +----
.../tazkia/dosen/entity/JenisPengajuanDokumen.java | 34 +---
.../java/id/ac/tazkia/dosen/entity/JenisSurat.java | 34 +---
.../tazkia/dosen/entity/KategoriBuktiKegiatan.java | 28 +--
.../ac/tazkia/dosen/entity/KategoriKegiatan.java | 28 +--
.../java/id/ac/tazkia/dosen/entity/Kecamatan.java | 35 +---
.../dosen/entity/KegiatanBelajarMengajar.java | 107 +---------
.../id/ac/tazkia/dosen/entity/KegiatanDosen.java | 108 +---------
src/main/java/id/ac/tazkia/dosen/entity/Kota.java | 35 +---
.../tazkia/dosen/entity/PengajuanDosenProfile.java | 148 +-------------
.../java/id/ac/tazkia/dosen/entity/Permission.java | 34 +---
.../id/ac/tazkia/dosen/entity/PoinKegiatan.java | 42 +---
.../id/ac/tazkia/dosen/entity/ProgramStudi.java | 44 +----
.../java/id/ac/tazkia/dosen/entity/Provinsi.java | 28 +--
src/main/java/id/ac/tazkia/dosen/entity/Role.java | 36 +---
.../tazkia/dosen/entity/SatuanHasilKegiatan.java | 27 +--
.../java/id/ac/tazkia/dosen/entity/SuratTugas.java | 51 +----
src/main/java/id/ac/tazkia/dosen/entity/User.java | 51 +----
.../id/ac/tazkia/dosen/entity/UserPassword.java | 26 +--
diff --git a/pom.xml b/pom.xml
index c1d759d..654ba52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,6 +59,11 @@
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</code></pre></div></div>
<p>File di atas kemudian bisa kita kirimkan ke maintainer melalui berbagai metode yang memungkinkan (email, ftp, scp, flashdisk, dsb).</p>
<p>Selain itu, Github juga memiliki fitur untuk membuatkan patch secara online. Kita cukup klik kanan tulisan commit id untuk mendapatkan linknya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/09-link-patch.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/09-link-patch.png" alt="Link Commit" /></a></p>
<p>Misalnya, kita mendapat link <a href="https://github.com/endymuhardin/aplikasi-dosen/commit/d8686f6cc4306b75109d88e2834f7e58456c60f6">https://github.com/endymuhardin/aplikasi-dosen/commit/d8686f6cc4306b75109d88e2834f7e58456c60f6</a>. Bila dibuka, tampilannya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/10-commit-detail-page.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/10-commit-detail-page.png" alt="Link Commit" /></a></p>
<p>Kita bisa tambahkan <code class="language-plaintext highlighter-rouge">.patch</code> di belakang link tersebut sehingga menjadi <a href="https://github.com/endymuhardin/aplikasi-dosen/commit/d8686f6cc4306b75109d88e2834f7e58456c60f6.patch">https://github.com/endymuhardin/aplikasi-dosen/commit/d8686f6cc4306b75109d88e2834f7e58456c60f6.patch</a> yang bila dibuka akan menghasilkan tampilan patch, sama seperti output perintah <code class="language-plaintext highlighter-rouge">git format-patch</code> di atas.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/11-github-patch-page.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/11-github-patch-page.png" alt="Link Commit" /></a></p>
<p>Link patch tersebut bisa kita berikan kepada maintainer.</p>
<h2 id="sinkronisasi-dengan-project-asal">Sinkronisasi dengan project asal</h2>
<p>Sinkronisasi dengan project asal atau repository <code class="language-plaintext highlighter-rouge">upstream</code> sebaiknya dilakukan di branch <code class="language-plaintext highlighter-rouge">master</code>. Demikian pula, sebaiknya kita <strong>tidak</strong> coding di dalam <code class="language-plaintext highlighter-rouge">master</code>, supaya kondisinya selalu bersih dan siap menerima update terbaru dari <code class="language-plaintext highlighter-rouge">upstream</code>.</p>
<h3 id="mengunduh-perubahan-dari-upstream">Mengunduh Perubahan dari Upstream</h3>
<p>Pastikan dulu kita berada di branch <code class="language-plaintext highlighter-rouge">master</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout master
</code></pre></div></div>
<p>Setelah itu, ambil perubahan terbaru dari <code class="language-plaintext highlighter-rouge">upstream</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git pull upstream master
</code></pre></div></div>
<p>Bila ada perubahan, maka branch <code class="language-plaintext highlighter-rouge">master</code> kita di komputer akan terupdate dengan versi terbaru dari master.</p>
<h3 id="sesuaikan-topic-branch-dengan-upstream-latest">Sesuaikan Topic Branch dengan Upstream Latest</h3>
<p>Ada kalanya maintainer hanya mau mengambil perubahan yang langsung dibuat di atas <code class="language-plaintext highlighter-rouge">master</code> terbaru di <code class="language-plaintext highlighter-rouge">upstream</code>. Atau kita ingin memastikan bahwa perubahan kita menggunakan fitur/kondisi terbaru yang ada di <code class="language-plaintext highlighter-rouge">upstream</code>, misalnya bila ada bugfix penting di <code class="language-plaintext highlighter-rouge">upstream</code> yang ingin kita tes bersama fitur/perubahan yang kita lakukan. Untuk itu, kita perlu menyesuaikan topic branch kita dengan kondisi <code class="language-plaintext highlighter-rouge">upstream</code> terkini.</p>
<p>Pindah dulu ke topic branch yang mau diupdate sesuai dengan <code class="language-plaintext highlighter-rouge">upstream</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout implement-lombok
</code></pre></div></div>
<p>Selanjutnya, sinkronisasi dengan <code class="language-plaintext highlighter-rouge">master</code> yang sudah terupdate seperti dijelaskan di atas.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git rebase master
</code></pre></div></div>
<p>Setelah menjalankan perintah ini, mungkin saja terjadi konflik. Bila terjadi, silahkan dibereskan sesuai panduan di <a href="https://www.youtube.com/watch?v=Ov59iNfGHps">video ini</a></p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Ov59iNfGHps" frameborder="0" allowfullscreen=""></iframe>
<p>Setelah itu, kita bisa push ke Github. Tapi biasanya kalau kita sudah melakukan rebase, kita harus menggunakan opsi <code class="language-plaintext highlighter-rouge">-f</code> agar bisa push</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push -f
</code></pre></div></div>
<h2 id="revisi-pull-request">Revisi Pull Request</h2>
<p>Ada kalanya pull request kita ditolak oleh maintainer. Banyak penyebabnya, misalnya:</p>
<ul>
<li>perubahan kita tidak sesuai dengan roadmap pengembangan aplikasi</li>
<li>kualitas kode program kurang baik</li>
<li>teknik coding tidak sesuai aturan dalam project, misalnya penamaan variabel, unit test, dan sebagainya</li>
<li>sebab-sebab lain</li>
</ul>
<p>Bila pull request kita ditolak, tidak perlu baper. Cukup tanyakan pada maintainer apa masalahnya. Project open source biasanya butuh banyak kontribusi, jadi biasanya maintainer justru gembira kalau menerima pull request. Bila dia menolak, biasanya ada komentar atau penjelasan apa yang kurang dan bagaimana cara memperbaikinya. Contohnya seperti di gambar berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/08-revisi-pull-request.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/git-workflow/08-revisi-pull-request.png" alt="Feedback PR" /></a></p>
<p>Pada gambar di atas, terlihat bahwa awalnya Pull Request dikomentari oleh maintainer. Di situ dijelaskan apa yang salah, dan bagaimana cara memperbaikinya. Kemudian kontributor melakukan perubahan lagi sesuai apa yang diminta. Akhirnya Pull Request diterima oleh maintainer.</p>
<p>Bila kita mendapatkan permintaan revisi dari maintainer, kembali buka topic branch kita tadi. Inilah gunanya topic branch. Sementara menunggu feedback, kita bisa membuat topic branch lain dan mengerjakan issue lain –baik fitur ataupun bugfix– di branch yang kondisinya bersih, tidak terkontaminasi perubahan <code class="language-plaintext highlighter-rouge">implement-lombok</code> tadi. Begitu ada feedback, kita bisa kembali ke topic branch <code class="language-plaintext highlighter-rouge">implement-lombok</code> yang kondisinya sesuai posisi terakhir Pull Request kita tadi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout implementasi-lombok
</code></pre></div></div>
<p>Lakukan modifikasi sesuai feedback maintainer, kemudian commit lagi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
git commit -m "perubahan sesuai permintaan maintainer"
</code></pre></div></div>
<p>Lalu push lagi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push
</code></pre></div></div>
<p>Maintainer akan otomatis diberitahu oleh Github bahwa ada update baru di Pull Request kita. Sementara itu kita bisa kembali mengerjakan apa yang tadinya sedang kita kerjakan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout branch-lain-yang-kita-kerjakan
</code></pre></div></div>
<h2 id="setelah-pull-request-diterima">Setelah Pull Request Diterima</h2>
<p>Horee …. !!! Pull request kita diterima. Selanjutnya apa yang kita lakukan?</p>
<p>Bila pull request kita diterima, harusnya perubahan yang kita lakukan sudah ada di <code class="language-plaintext highlighter-rouge">upstream</code>. Coba kita lihat dulu.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout master
git pull upstream master
</code></pre></div></div>
<p>Kondisi <code class="language-plaintext highlighter-rouge">master</code> kita sudah up to date dengan <code class="language-plaintext highlighter-rouge">upstream</code>. Sekarang lihat daftar perubahannya dengan perintah <code class="language-plaintext highlighter-rouge">git log --oneline</code>. Harusnya kita bisa melihat perubahan yang kita sumbangkan ke project.</p>
<p>Karena perubahan kita sudah terkandung dalam <code class="language-plaintext highlighter-rouge">upstream</code>, kita bisa menghapus topic branch kita tadi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git branch -D implement-lombok
</code></pre></div></div>
<p>Hapus juga yang ada di Github</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push origin :implement-lombok
</code></pre></div></div>
<p>Artinya, push <code class="language-plaintext highlighter-rouge">NULL</code> ke <code class="language-plaintext highlighter-rouge">origin</code> untuk menggantikan <code class="language-plaintext highlighter-rouge">implement-lombok</code>, atau bahasa gampangnya <code class="language-plaintext highlighter-rouge">hapus</code> :D</p>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>To github.com:endymuhardin/aplikasi-dosen.git
- [deleted] implement-lombok
</code></pre></div></div>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah daftar perintah yang biasa kita lakukan kalau menjadi kontributor project open source di Github. Untuk lebih lengkapnya, silahkan tonton <a href="https://youtu.be/XZxaY2XvRzE">rekaman training Git berikut</a>.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/XZxaY2XvRzE" frameborder="0" allowfullscreen=""></iframe>
<p>Semoga bermanfaat.</p>
Hosting Blog dengan SSL di Gitlab2017-10-03T08:00:00+07:00https://software.endy.muhardin.com/devops/jekyll-ssl-gitlab<p>Sejak beberapa waktu yang lalu, blog ini sebetulnya sudah pindah hosting dari Github ke Gitlab. Alasannya sederhana, Github sampai saat artikel ini ditulis tidak mendukung SSL untuk custom domain. Ada sih akal-akalan menggunakan CloudFlare, seperti dijelaskan di <a href="https://hackernoon.com/set-up-ssl-on-github-pages-with-custom-domains-for-free-a576bdf51bc">artikel ini</a>, tapi tetap saja koneksi dari CloudFlare ke Github tidak terproteksi.</p>
<p>Oleh karena itu, saya pindahkan hostingnya ke Gitlab. Berikut langkah-langkah untuk memasang sertifikat SSL.</p>
<!--more-->
<p>Pertama, kita harus punya sertifikat dulu. Caranya sudah dibahas pada <a href="https://software.endy.muhardin.com/devops/letsencrypt-manual-dns/">artikel terdahulu</a>. Silahkan dibaca dulu supaya nyambung.</p>
<p>Setelah sertifikat kita dapatkan, buat repo buat menghosting blog Jekyll kita, tambahkan remote url ke repo tersebut, dan kemudian push blog Jekyll kita ke Gitlab. Perintahnya kurang lebih seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add gitlab git@gitlab.com:endymuhardin/endymuhardin.gitlab.io.git
git push gitlab master
</code></pre></div></div>
<p>Setelah dipush, harusnya blog sudah bisa diakses dengan alamat <a href="https://endymuhardin.gitlab.io">https://endymuhardin.gitlab.io</a>. Pastikan dulu sudah terdeploy dengan baik sebelum kita lanjutkan ke setting custom domain.</p>
<h2 id="custom-domain">Custom Domain</h2>
<p>Untuk memasang custom domain, terlebih dulu kita arahkan setting DNS dengan memasang record CNAME yang mengarahkan <code class="language-plaintext highlighter-rouge">software.endy.muhardin.com</code> ke <code class="language-plaintext highlighter-rouge">endymuhardin.gitlab.io</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/ssl-gitlab/cname-dns.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/ssl-gitlab/cname-dns.png" alt="CNAME" /></a></p>
<p>Setelah itu, masuk ke menu <code class="language-plaintext highlighter-rouge">Settings > Pages</code> di repo Gitlab.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/ssl-gitlab/settings-pages.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/ssl-gitlab/settings-pages.png" alt="Menu Settings Pages" /></a></p>
<p>Klik <code class="language-plaintext highlighter-rouge">New Domain</code>, kemudian masukkan nama domain, public key, dan private key sertifikat SSL.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/ssl-gitlab/custom-domain.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/ssl-gitlab/custom-domain.png" alt="Custom Domain" /></a></p>
<p>Bila <a href="https://software.endy.muhardin.com/devops/letsencrypt-manual-dns/">menggunakan sertifikat SSL gratis dari LetsEncrypt</a>, file yang digunakan adalah sebagai berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">fullchain.pem</code> untuk di isian <code class="language-plaintext highlighter-rouge">Certificate (PEM)</code></li>
<li><code class="language-plaintext highlighter-rouge">privkey.pem</code> untuk di isian <code class="language-plaintext highlighter-rouge">Key (PEM)</code></li>
</ul>
<p>Klik <code class="language-plaintext highlighter-rouge">Save</code>. Hasilnya sebagai berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/ssl-gitlab/hasil.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/ssl-gitlab/hasil.png" alt="Hasil Settings" /></a></p>
<p>Sekarang blog sudah bisa diakses di alamat <code class="language-plaintext highlighter-rouge">https://software.endy.muhardin.com</code></p>
Menggunakan Proprietary Jar dengan Gitlab CI dan Heroku2017-09-28T08:52:00+07:00https://software.endy.muhardin.com/devops/proprietary-jar-gitlab-ci-heroku<p>Pada artikel sebelumnya, kita sudah <a href="https://software.endy.muhardin.com/devops/gitlab-ci-kubernetes-gke/">mengotomasi proses build dengan Gitlab CI</a> dan <a href="https://software.endy.muhardin.com/java/project-bootstrap-03/">deployment ke Heroku</a>. Proses tersebut bisa dilakukan dengan lancar apabila semua library/dependensi yang kita gunakan dalam aplikasi kita adalah open source. Dependensi open source tersedia di repository Maven Central, sehingga dimanapun kita jalankan perintah build, maka Maven akan mengunduh dependensi yang dibutuhkan langsung dari internet.</p>
<p>Akan menjadi persoalan kalau project kita menggunakan library yang tidak open source atau proprietary. Contoh paling umum adalah database Oracle. Agar aplikasi kita bisa terhubung ke database Oracle, kita harus menggunakan JDBC Driver dari Oracle yang tidak open source, sehingga tidak tersedia di Maven Central.</p>
<p>Untuk mengatasi hal ini, kita perlu menyesuaikan proses build aplikasi kita agar tetap bisa berjalan otomatis dari commit hingga deployment.</p>
<ul id="markdown-toc">
<li><a href="#membuat-repo-maven-lokal" id="markdown-toc-membuat-repo-maven-lokal">Membuat Repo Maven Lokal</a></li>
<li><a href="#setup-gitlab-ci" id="markdown-toc-setup-gitlab-ci">Setup Gitlab CI</a></li>
<li><a href="#deployment-ke-heroku" id="markdown-toc-deployment-ke-heroku">Deployment ke Heroku</a></li>
<li><a href="#kesimpulan" id="markdown-toc-kesimpulan">Kesimpulan</a> <ul>
<li><a href="#gitlab-ciyml" id="markdown-toc-gitlab-ciyml">.gitlab-ci.yml</a></li>
<li><a href="#pomxml" id="markdown-toc-pomxml">pom.xml</a></li>
</ul>
</li>
</ul>
<!--more-->
<h2 id="membuat-repo-maven-lokal">Membuat Repo Maven Lokal</h2>
<p>Agar project kita terpenuhi dependensinya, kita perlu membuat repository lokal di laptop/PC dan kemudian menginstal dependensi tersebut di repo lokal. Tentunya sebelumnya kita perlu mengunduh dulu JDBC Driver Oracle <a href="http://www.oracle.com/technetwork/database/application-development/jdbc/overview/index.html">di websitenya</a>. Kita asumsikan saja setelah diunduh, file <code class="language-plaintext highlighter-rouge">ojdbc8.jar</code> tersebut kita letakkan di folder <code class="language-plaintext highlighter-rouge">/tmp/</code>.</p>
<p>Selanjutnya, kita akan membuat repository Maven lokal di dalam folder project. Biasanya Maven sudah memiliki repo lokal yang berlokasi di <code class="language-plaintext highlighter-rouge">HOME/.m2/repository</code>. Kita akan memasukkan file <code class="language-plaintext highlighter-rouge">ojdbc8.jar</code> tersebut sesuai dengan struktur folder dan aturan penamaan file Maven. Berikut perintah untuk instalasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn install:install-file -DgroupId=com.oracle.jdbc -DartifactId=ojdbc8 -Dversion=12.2.0.1 -Dpackaging=jar -Dfile=/tmp/ojdbc8.jar
</code></pre></div></div>
<p>Setelah file tersebut terinstal (bisa dipastikan dengan cara melihat ke folder <code class="language-plaintext highlighter-rouge">HOME/.m2/repository/com/oracle/jdbc</code>) kita bisa menggunakannya di project dengan mendeklarasikan dependensi seperti ini</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupid></span>com.oracle.jdbc<span class="nt"></groupid></span>
<span class="nt"><artifactid></span>ojdbc8<span class="nt"></artifactid></span>
<span class="nt"><version></span>12.2.0.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Kita bisa test build dan jalankan project kita seperti biasa, misalnya dengan perintah <code class="language-plaintext highlighter-rouge">mvn clean spring-boot:run</code> untuk memastikan aplikasi kita bisa dijalankan dengan baik.</p>
<p>Bila sudah berjalan lancar, kita akan melakukan tindakan lebih lanjut supaya project kita bisa dibuild juga secara otomatis oleh Gitlab CI. Kira-kira seperti ini diagramnya:</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/proprietary-jar/skema-private-repo.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/proprietary-jar/skema-private-repo.png" alt="Laptop - File Server - Gitlab CI" /></a></p>
<h2 id="setup-gitlab-ci">Setup Gitlab CI</h2>
<p>Agar skema di atas bisa berjalan dengan baik, kita perlu server yang bisa diakses dari internet, misalnya kita beri nama <code class="language-plaintext highlighter-rouge">file.server.saya.com</code>. Kita upload file <code class="language-plaintext highlighter-rouge">ojdbc8.jar</code> tersebut dari laptop ke server tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp /tmp/ojdbc8.jar root@file.server.saya.com:/var/lib/
</code></pre></div></div>
<p>File ini nantinya akan diunduh oleh proses build Gitlab CI dengan perintah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp root@file.server.saya.com:/var/lib/ojdbc8.jar /tmp/
</code></pre></div></div>
<p>Untuk selanjutnya diinstal di repo lokal Maven dengan perintah yang sama</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn install:install-file -DgroupId=com.oracle.jdbc -DartifactId=ojdbc8 -Dversion=12.2.0.1 -Dpackaging=jar -Dfile=/tmp/ojdbc8.jar
</code></pre></div></div>
<p>Agar perintah <code class="language-plaintext highlighter-rouge">scp</code> dari Gitlab CI ke <code class="language-plaintext highlighter-rouge">file.server.saya.com</code> berjalan dengan mulus, perlu dilakukan persiapan:</p>
<ul>
<li>membuat pasangan private-public key untuk SSH</li>
<li>memasang isi private key menjadi secret variabel di Gitlab CI</li>
<li>mendaftarkan public key ke <code class="language-plaintext highlighter-rouge">file.server.saya.com</code> agar bisa login SSH tanpa password</li>
<li>mendaftarkan digital signature hostname <code class="language-plaintext highlighter-rouge">file.server.saya.com</code> di Gitlab CI agar tidak diminta konfirmasi host karena baru pertama akses SSH</li>
</ul>
<p>Rangkaian persiapan ini telah dijelaskan <a href="https://software.endy.muhardin.com/devops/deploy-gitlab-vps/">di artikel terdahulu</a>. Silakan dibaca kembali bila lupa.</p>
<p>Berikut potongan file konfigurasi <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> yang berkaitan dengan kegiatan unduh dan setup repo lokal maven di Gitlab CI.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">maven:3-jdk-8</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">build</span>
<span class="pi">-</span> <span class="s">deploy</span>
<span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">which</span><span class="nv"> </span><span class="s">ssh-agent</span><span class="nv"> </span><span class="s">||</span><span class="nv"> </span><span class="s">(</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">update</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">install</span><span class="nv"> </span><span class="s">openssh-client</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">)'</span>
<span class="pi">-</span> <span class="s">eval $(ssh-agent -s)</span>
<span class="pi">-</span> <span class="s">ssh-add <(echo "$SSH_PRIVATE_KEY")</span>
<span class="pi">-</span> <span class="s">mkdir -p ~/.ssh</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">[[</span><span class="nv"> </span><span class="s">-f</span><span class="nv"> </span><span class="s">/.dockerenv</span><span class="nv"> </span><span class="s">]]</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">ssh-keyscan</span><span class="nv"> </span><span class="s">-H</span><span class="nv"> </span><span class="s">"$SSH_HOSTNAME"</span><span class="nv"> </span><span class="s">></span><span class="nv"> </span><span class="s">~/.ssh/known_hosts'</span>
<span class="pi">-</span> <span class="s">scp root@$SSH_HOSTNAME:/var/lib/ojdbc8.jar /tmp/</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">mvn install:install-file -DgroupId=com.oracle.jdbc -DartifactId=ojdbc8 -Dversion=12.2.0.1 -Dpackaging=jar -Dfile=/tmp/ojdbc8.jar</span>
<span class="pi">-</span> <span class="s">mvn clean package -DskipTests</span>
<span class="na">artifacts</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">target/*.jar</span>
</code></pre></div></div>
<p>Jangan lupa daftarkan variabel <code class="language-plaintext highlighter-rouge">SSH_HOSTNAME</code> dengan isi <code class="language-plaintext highlighter-rouge">file.server.saya.com</code>.</p>
<h2 id="deployment-ke-heroku">Deployment ke Heroku</h2>
<p>Pada <a href="https://software.endy.muhardin.com/java/project-bootstrap-03/">artikel terdahulu</a> memang kita telah membahas tentang deployment ke Heroku. Akan tetapi pada artikel tersebut, kita menggunakan metode source deployment, yaitu mengunggah source code ke Heroku untuk kemudian menyuruh Heroku melakukan build sekali lagi.</p>
<p>Metode ini tidak bisa kita lakukan saat ini, karena nantinya akan ribet lagi untuk menyuruh Heroku mengunduh <code class="language-plaintext highlighter-rouge">ojdbc8.jar</code> dari <code class="language-plaintext highlighter-rouge">file.server.saya.com</code>. Oleh karena itu, kita ingin langsung saja mengunggah hasil build dari langkah sebelumnya. Hasil build dari proses sebelumnya bisa digunakan oleh proses berikut dengan konfigurasi artifact yang kita pasang pada job <code class="language-plaintext highlighter-rouge">build</code></p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="na">artifacts</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">target/*.jar</span>
</code></pre></div></div>
<p>Agar bisa mendeploy <code class="language-plaintext highlighter-rouge">jar</code> ke Heroku, kita membutuhkan Heroku CLI. Cara instalasinya bisa dilihat di websitenya, pilih yang <a href="https://devcenter.heroku.com/articles/heroku-cli#debian-ubuntu">versi Debian/Ubuntu</a>. Dengan demikian, kita juga harus menggunakan image <code class="language-plaintext highlighter-rouge">ubuntu:latest</code> agar langkah-langkah tersebut bisa dijalankan.</p>
<p>Selanjutnya, kita hanya perlu menjalankan perintah untuk instalasi Heroku CLI, kemudian menjalankan perintah deploy. Kita membutuhkan <code class="language-plaintext highlighter-rouge">api key</code> Heroku yang terpasang sebagai environment variable dan nama aplikasi di Heroku. Berikut konfigurasi scriptnya</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">deploy-heroku</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">ubuntu:latest</span>
<span class="na">variables</span><span class="pi">:</span>
<span class="na">HEROKU_API_KEY</span><span class="pi">:</span> <span class="s">$HEROKU_API_KEY</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">apt install wget openjdk-8-jdk-headless -y</span>
<span class="pi">-</span> <span class="s">wget -qO- https://cli-assets.heroku.com/install-ubuntu.sh | sh</span>
<span class="pi">-</span> <span class="s">heroku plugins:install heroku-cli-deploy</span>
<span class="pi">-</span> <span class="s">heroku deploy:jar target/*.jar --app $HEROKU_APP_NAME</span>
</code></pre></div></div>
<p>Jangan lupa, kita juga harus menginstal <code class="language-plaintext highlighter-rouge">openjdk-8</code> supaya bisa mendeploy jar. Bila tidak terinstal, maka akan muncul pesan error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku deploy:jar target/*.jar --app $HEROKU_APP_NAME
Uploading payment-virtualaccount-1.0.1-M.001.jar
▸ 'ENOENT': spawn java ENOENT
ERROR: Job failed: exit code 1
</code></pre></div></div>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Sebisa mungkin, selalu gunakan library open source. Ini akan sangat memudahkan kita dalam proses development dan otomasi workflow. Akan tetapi adakalanya kita tidak bisa menghindari penggunaan library proprietary. Jadi apa boleh buat terpaksa harus dilakukan akal-akalan. Walaupun demikian, tetap harus legal.</p>
<p>Sebetulnya bisa saja tadi kita masukkan file <code class="language-plaintext highlighter-rouge">ojdbc8.jar</code> ke dalam struktur folder project, misalnya di folder <code class="language-plaintext highlighter-rouge">.mvn/repository</code> sejajar dengan <code class="language-plaintext highlighter-rouge">src</code> dan <code class="language-plaintext highlighter-rouge">pom.xml</code>. Perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn deploy:deploy-file -Durl=file://$(pwd)/.mvn/repository -DgroupId=com.oracle.jdbc -DartifactId=ojdbc8 -Dversion=12.2.0.1 -Dpackaging=jar -Dfile=/tmp/ojdbc8.jar
</code></pre></div></div>
<p>Kemudian kita daftarkan folder tersebut sebagai repo lokal di <code class="language-plaintext highlighter-rouge">pom.xml</code> dengan konfigurasi berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><repositories></span>
<span class="nt"><repository></span>
<span class="nt"><id></span>project.local<span class="nt"></id></span>
<span class="nt"><name></span>project<span class="nt"></name></span>
<span class="nt"><url></span>file:${project.basedir}/.mvn/repository<span class="nt"></url></span>
<span class="nt"></repository></span>
<span class="nt"></repositories></span>
</code></pre></div></div>
<p>Kemudian tinggal kita commit saja folder <code class="language-plaintext highlighter-rouge">.mvn/repository</code> ke Git repo, sehingga proses build berjalan dengan lancar di Gitlab CI maupun Heroku. Teknik ini diajarkan oleh Heroku sendiri <a href="https://devcenter.heroku.com/articles/local-maven-dependencies">di artikel ini</a>.</p>
<p>Walaupun demikian, kita tidak bisa lakukan teknik ini untuk project open source, karena kita tidak diijinkan Oracle untuk mendistribusikan file <code class="language-plaintext highlighter-rouge">ojdbc8.jar</code> tersebut. Bila kita buat repo lokal berisi file tersebut dan kita unggah ke Git repo terbuka, maka kita bisa dimarahi oleh pengacaranya Oracle ;)</p>
<p>Demikian penjelasan tentang penggunaan library proprietary dengan Gitlab CI. Berikut isi file lengkapnya untuk referensi</p>
<h3 id="gitlab-ciyml">.gitlab-ci.yml</h3>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">maven:3-jdk-8</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">build</span>
<span class="pi">-</span> <span class="s">deploy</span>
<span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">which</span><span class="nv"> </span><span class="s">ssh-agent</span><span class="nv"> </span><span class="s">||</span><span class="nv"> </span><span class="s">(</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">update</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">install</span><span class="nv"> </span><span class="s">openssh-client</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">)'</span>
<span class="pi">-</span> <span class="s">eval $(ssh-agent -s)</span>
<span class="pi">-</span> <span class="s">ssh-add <(echo "$SSH_PRIVATE_KEY")</span>
<span class="pi">-</span> <span class="s">mkdir -p ~/.ssh</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">[[</span><span class="nv"> </span><span class="s">-f</span><span class="nv"> </span><span class="s">/.dockerenv</span><span class="nv"> </span><span class="s">]]</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">ssh-keyscan</span><span class="nv"> </span><span class="s">-H</span><span class="nv"> </span><span class="s">"$SSH_HOSTNAME"</span><span class="nv"> </span><span class="s">></span><span class="nv"> </span><span class="s">~/.ssh/known_hosts'</span>
<span class="pi">-</span> <span class="s">scp root@$SSH_HOSTNAME:/var/lib/ojdbc8.jar /tmp/</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">mvn install:install-file -DgroupId=com.oracle.jdbc -DartifactId=ojdbc8 -Dversion=12.2.0.1 -Dpackaging=jar -Dfile=/tmp/ojdbc8.jar</span>
<span class="pi">-</span> <span class="s">mvn clean package -DskipTests</span>
<span class="na">artifacts</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">target/*.jar</span>
<span class="na">deploy-dev</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">ubuntu:latest</span>
<span class="na">variables</span><span class="pi">:</span>
<span class="na">HEROKU_API_KEY</span><span class="pi">:</span> <span class="s">$HEROKU_API_KEY</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">apt install wget openjdk-8-jdk-headless -y</span>
<span class="pi">-</span> <span class="s">wget -qO- https://cli-assets.heroku.com/install-ubuntu.sh | sh</span>
<span class="pi">-</span> <span class="s">heroku plugins:install heroku-cli-deploy</span>
<span class="pi">-</span> <span class="s">heroku deploy:jar target/*.jar --app $HEROKU_APP_NAME</span>
</code></pre></div></div>
<h3 id="pomxml">pom.xml</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">></span>
<span class="nt"><modelVersion></span>4.0.0<span class="nt"></modelVersion></span>
<span class="nt"><groupId></span>id.artivisi.belajar<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>belajar-private-jar<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.0.1<span class="nt"></version></span>
<span class="nt"><packaging></span>jar<span class="nt"></packaging></span>
<span class="nt"><name></span>belajar-private-jar<span class="nt"></name></span>
<span class="nt"><description></span>Demo project for Spring Boot<span class="nt"></description></span>
<span class="nt"><parent></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-parent<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.5.7.RELEASE<span class="nt"></version></span>
<span class="nt"><relativePath/></span> <span class="c"><!-- lookup parent from repository --></span>
<span class="nt"></parent></span>
<span class="nt"><properties></span>
<span class="nt"><project.build.sourceEncoding></span>UTF-8<span class="nt"></project.build.sourceEncoding></span>
<span class="nt"><project.reporting.outputEncoding></span>UTF-8<span class="nt"></project.reporting.outputEncoding></span>
<span class="nt"><java.version></span>1.8<span class="nt"></java.version></span>
<span class="nt"><thymeleaf.version></span>3.0.2.RELEASE<span class="nt"></thymeleaf.version></span>
<span class="nt"><thymeleaf-layout-dialect.version></span>2.1.1<span class="nt"></thymeleaf-layout-dialect.version></span>
<span class="nt"></properties></span>
<span class="nt"><dependencies></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-actuator<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-data-jpa<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.flywaydb<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flyway-core<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-web<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-thymeleaf<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-security<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-cloud-connectors<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.thymeleaf.extras<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>thymeleaf-extras-springsecurity4<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.projectlombok<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>lombok<span class="nt"></artifactId></span>
<span class="nt"><optional></span>true<span class="nt"></optional></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupid></span>com.oracle.jdbc<span class="nt"></groupid></span>
<span class="nt"><artifactid></span>ojdbc8<span class="nt"></artifactid></span>
<span class="nt"><version></span>12.2.0.1<span class="nt"></version></span>
<span class="nt"><scope></span>runtime<span class="nt"></scope></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-devtools<span class="nt"></artifactId></span>
<span class="nt"><scope></span>runtime<span class="nt"></scope></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-test<span class="nt"></artifactId></span>
<span class="nt"><scope></span>test<span class="nt"></scope></span>
<span class="nt"></dependency></span>
<span class="nt"></dependencies></span>
<span class="nt"><build></span>
<span class="nt"><plugins></span>
<span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><configuration></span>
<span class="nt"><executable></span>true<span class="nt"></executable></span>
<span class="nt"></configuration></span>
<span class="nt"></plugin></span>
<span class="nt"><plugin></span>
<span class="nt"><groupId></span>pl.project13.maven<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>git-commit-id-plugin<span class="nt"></artifactId></span>
<span class="nt"><configuration></span>
<span class="nt"><failOnNoGitDirectory></span>false<span class="nt"></failOnNoGitDirectory></span>
<span class="nt"></configuration></span>
<span class="nt"></plugin></span>
<span class="nt"></plugins></span>
<span class="nt"></build></span>
<span class="nt"></project></span>
</code></pre></div></div>
<p>Semoga bermanfaat …</p>
SSL Gratis dengan LetsEncrypt2017-09-14T08:52:00+07:00https://software.endy.muhardin.com/devops/letsencrypt-manual-dns<p>Di tahun 2017 ini, sudah tidak ada lagi alasan bagi kita untuk tidak menggunakan <code class="language-plaintext highlighter-rouge">HTTPS</code>. <a href="https://software.endy.muhardin.com/aplikasi/apa-itu-ssl/">Sertifikat SSL</a> bisa didapatkan dengan mudah dan gratis dengan adanya <a href="https://letsencrypt.org/">LetsEncrypt</a>.</p>
<p>LetsEncrypt menyediakan beberapa metode yang bisa kita gunakan untuk membuktikan bahwa domain yang ingin kita buatkan sertifikatnya benar-benar kita miliki (Domain Validation), yaitu:</p>
<ul>
<li>apache</li>
<li>nginx</li>
<li>standalone</li>
<li>manual</li>
</ul>
<p>Metode <code class="language-plaintext highlighter-rouge">apache</code> dan <code class="language-plaintext highlighter-rouge">nginx</code> kita gunakan bila kita menggunakan webserver tersebut. Aplikasi Certbot dari LetsEncrypt akan membuat file khusus di webserver tersebut dan mencoba mengaksesnya dari internet. Bila berhasil, artinya kita benar-benar menguasai domain dan webservernya.</p>
<p>Metode standalone kita gunakan bila aplikasi kita tidak berjalan di atas <code class="language-plaintext highlighter-rouge">apache</code> atau <code class="language-plaintext highlighter-rouge">nginx</code>, misalnya bila kita menggunakan Tomcat, Jetty, dan sebagainya. Dengan metode ini, <code class="language-plaintext highlighter-rouge">certbot</code> akan menjalankan webserver dan menunggu request dari server LetsEncrypt. Bila server LetsEncrypt bisa mengakses webserver <code class="language-plaintext highlighter-rouge">certbot</code> tersebut, artinya kita benar-benar punya akses ke domain dan servernya. Cara menggunakan metode ini sudah kita bahas di <a href="https://software.endy.muhardin.com/java/instalasi-jenkins-ssl/">artikel terdahulu</a>.</p>
<p>Walaupun demikian, adakalanya kita belum punya server yang akan kita gunakan untuk menjalankan aplikasi. Belum tahu mau pakai webserver apa. Atau aplikasi kita berjalan di port non-standar (selain 80 dan 443). Karena servernya belum ada, maka kita tidak bisa menunggu request di server tersebut. Kita juga tidak bisa mengarahkan nama domain ke laptop kita, karena pada umumnya, laptop kita tidak memiliki IP publik. Untuk itu, pada artikel kali ini kita akan membahas metode <code class="language-plaintext highlighter-rouge">manual</code> dan verifikasi <code class="language-plaintext highlighter-rouge">dns</code>. Metode ini bisa dijalankan di laptop, tanpa harus punya server ataupun mendeploy aplikasi kita ke server tertentu.</p>
<!--more-->
<p>Pertama, kita harus instal dulu aplikasi <code class="language-plaintext highlighter-rouge">certbot</code> untuk berinteraksi dengan LetsEncrypt. Cara instalasinya tidak kita bahas di sini, silahkan langsung <a href="https://certbot.eff.org/docs/install.html">baca dokumentasinya</a>.</p>
<p>Misalnya, kita ingin membuat sertifikat SSL untuk domain <code class="language-plaintext highlighter-rouge">payment.tazkia.ac.id</code>. Kita harus punya akses ke DNS server yang mengelola domain induknya yaitu <code class="language-plaintext highlighter-rouge">tazkia.ac.id</code>. Nantinya kita akan menambahkan record khusus ke DNS server. LetsEncrypt akan mengecek keberadaan record tersebut untuk membuktikan bahwa kita benar-benar merupakan administrator untuk domain tersebut. Lebih lanjut mengenai proses validasi domain ini bisa dibaca pada <a href="https://software.endy.muhardin.com/aplikasi/membeli-sertifikat-ssl/">artikel tentang pembelian sertifikat SSL</a>.</p>
<p>Setelah aplikasi <code class="language-plaintext highlighter-rouge">certbot</code> terinstal, kita jalankan perintah untuk memulai proses Domain Validation. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certbot certonly -d payment.tazkia.ac.id --manual --preferred-challenges dns --config-dir config --work-dir work --logs-dir logs
</code></pre></div></div>
<p>Berikut keterangan opsi-opsi yang digunakan:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">-d payment.tazkia.ac.id</code> : nama domain yang akan dibuatkan sertifikat SSLnya. Jadi nantinya kita akan mengakses aplikasi/website dengan alamat <code class="language-plaintext highlighter-rouge">https://payment.tazkia.ac.id</code></li>
<li><code class="language-plaintext highlighter-rouge">--manual</code> : metode yang kita gunakan adalah manual. Artinya kita akan menjalankan proses sendiri, tanpa dibantu fitur otomasi <code class="language-plaintext highlighter-rouge">certbot</code></li>
<li><code class="language-plaintext highlighter-rouge">--preferred-challenges dns</code> : metode validasi yang kita gunakan adalah konfigurasi DNS server. Ini kita pilih karena kita belum punya server untuk menjalankan verifikasi dengan file.</li>
<li><code class="language-plaintext highlighter-rouge">--config-dir config --work-dir work --logs-dir logs</code> : folder kerja <code class="language-plaintext highlighter-rouge">certbot</code>. Ini saya arahkan sendiri karena saya ingin menyimpan hasilnya dalam Dropbox supaya terbackup secara otomatis.</li>
</ul>
<p>Output dari perintah tersebut seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Saving debug log to /Users/endymuhardin/Dropbox/Konfigurasi/LetsEncrypt/logs/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for payment.tazkia.ac.id
-------------------------------------------------------------------------------
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.
Are you OK with your IP being logged?
-------------------------------------------------------------------------------
(Y)es/(N)o: y
-------------------------------------------------------------------------------
Please deploy a DNS TXT record under the name
_acme-challenge.payment.tazkia.ac.id with the following value:
0wvNZdimx8RfsGO4Wm4a5HoC1CaUmtHQCsfuHNSjWOs
Before continuing, verify the record is deployed.
-------------------------------------------------------------------------------
Press Enter to Continue
</code></pre></div></div>
<p>Menurut instruksi di atas, kita disuruh membuat record baru di DNS server dengan tipe <code class="language-plaintext highlighter-rouge">TXT</code>, nama record <code class="language-plaintext highlighter-rouge">_acme-challenge.payment.tazkia.ac.id</code> berisi nilai <code class="language-plaintext highlighter-rouge">0wvNZdimx8RfsGO4Wm4a5HoC1CaUmtHQCsfuHNSjWOs</code>. Cara membuat recordnya berbeda-beda tergantung tempat kita membeli domain. Bila kita menggunakan CPanel, tampilannya kira-kira seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/letsencrypt-manual-dns/cpanel-dns.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/letsencrypt-manual-dns/cpanel-dns.png" alt="Konfigurasi DNS Record" /></a></p>
<p>Setelah kita masukkan, tekan <code class="language-plaintext highlighter-rouge">Save</code>, kita lanjutkan ke command prompt tadi. Tekan <code class="language-plaintext highlighter-rouge">Enter</code>. Prosesnya akan berlanjut. Server LetsEncrypt akan mengecek nilai tadi ke DNS server kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Waiting for verification...
Cleaning up challenges
Non-standard path(s), might not work with crontab installed by your operating system package manager
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/Users/endymuhardin/Dropbox/Konfigurasi/LetsEncrypt/config/live/payment.tazkia.ac.id/fullchain.pem
Your key file has been saved at:
/Users/endymuhardin/Dropbox/Konfigurasi/LetsEncrypt/config/live/payment.tazkia.ac.id/privkey.pem
Your cert will expire on 2017-12-13. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
</code></pre></div></div>
<p>Selesai, kita sudah mendapatkan sertifikat di file <code class="language-plaintext highlighter-rouge">fullchain.pem</code> dan private key di file <code class="language-plaintext highlighter-rouge">privkey.pem</code>. Selanjutnya kita bisa memasangnya di webserver kita seperti sudah dijelaskan di <a href="https://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl/">artikel ini</a>.</p>
<p>Jangan lupa bahwa sertifikat ini hanya berlaku selama 3 bulan. Sebelum berakhir masa berlakunya, kita harus memperbaruinya dengan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certonly -d payment.tazkia.ac.id --manual --preferred-challenges dns --config-dir config --work-dir work --logs-dir logs --force-renew
</code></pre></div></div>
<p>Selamat mencoba, semoga bermanfaat.</p>
Instalasi dan Hardening Raspberry PI2017-05-30T07:00:00+07:00https://software.endy.muhardin.com/linux/raspi-hardening<p>Raspberry PI tentunya sudah bukan barang baru lagi bagi para opreker nusantara. Cara instalasinya pun saya yakin sudah pada lancar. Akan tetapi, sebelum dipasang ke internet, perlu ada tindakan pengamanan supaya tidak dihack orang.</p>
<p>Berikut langkah-langkah yang biasa saya lakukan pada saat setup Raspberry PI</p>
<ol>
<li><a href="/linux/raspi-hardening//#instalasi">Instalasi Raspbian ke MicroSD</a></li>
<li><a href="/linux/raspi-hardening//#setup-network">Setup Network</a></li>
<li><a href="/linux/raspi-hardening//#login">Login ke Raspbian</a></li>
<li><a href="/linux/raspi-hardening//#user-baru">Buat user baru</a></li>
<li><a href="/linux/raspi-hardening//#hapus-user-pi">Hapus user pi</a></li>
<li><a href="/linux/raspi-hardening//#keyboard-timezone">Ganti keyboard dan timezone</a></li>
<li><a href="/linux/raspi-hardening//#update-upgrade">Update & Upgrade</a></li>
<li><a href="/linux/raspi-hardening//#passwordless-ssh">Setup passwordless login</a></li>
<li><a href="/linux/raspi-hardening//#proteksi-brute-force-ssh">Proteksi brute-force ssh</a></li>
<li><a href="/linux/raspi-hardening//#automount-usb-debian">Automount USB</a></li>
</ol>
<!--more-->
<p><a name="instalasi"></a></p>
<h2 id="instalasi-raspbian">Instalasi Raspbian</h2>
<p>Instalasinya mudah, sudah ada juga <a href="https://www.raspberrypi.org/documentation/installation/installing-images/README.md">dokumentasi resminya</a>. Akan tetapi supaya lengkap, baiklah saya tulis lagi di sini.</p>
<p>Petunjuk instalasi khusus macOS, pengguna Linux biasanya tidak perlu diajari lagi caranya. Pada dasarnya hanya <code class="language-plaintext highlighter-rouge">dd if=namafile.iso of=namadevicemicrosd</code> saja.</p>
<p>Setelah kita <a href="https://www.raspberrypi.org/downloads/raspbian/">mengunduh image terbaru</a>, kita akan menulisnya ke MicroSD card. Pasang MicroSDnya, kemudian cari tau nama devicenya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>diskutil list
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *121.3 GB disk0
1: EFI EFI 209.7 MB disk0s1
2: Apple_CoreStorage Macintosh HD 120.5 GB disk0s2
3: Apple_Boot Recovery HD 650.0 MB disk0s3
/dev/disk1 (internal, virtual):
#: TYPE NAME SIZE IDENTIFIER
0: Apple_HFS Macintosh HD +120.1 GB disk1
Logical Volume on disk0s2
D355AA2B-BB42-4858-9058-6FA6AB6783A3
Unlocked Encrypted
/dev/disk2 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *31.9 GB disk2
1: Windows_FAT_32 boot 66.1 MB disk2s1
2: Linux 31.8 GB disk2s2
/dev/disk3 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *124.2 GB disk3
1: EFI EFI 209.7 MB disk3s1
2: Apple_CoreStorage SDUF128G 123.9 GB disk3s2
3: Apple_Boot Boot OS X 134.2 MB disk3s3
</code></pre></div></div>
<p>MicroSD kita ada di <code class="language-plaintext highlighter-rouge">/dev/disk2</code> karena dia satu-satunya yang bertipe <code class="language-plaintext highlighter-rouge">Windows_FAT_32</code>.</p>
<p>Selanjutnya, umount dulu supaya bisa diakses langsung ke devicenya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>diskutil unmountDisk /dev/disk2
</code></pre></div></div>
<p>Kemudian, tulis file image yang sudah diunduh ke device. Untuk di macOS, biasanya ditambahkan <code class="language-plaintext highlighter-rouge">r</code> di nama disk sehingga menjadi <code class="language-plaintext highlighter-rouge">/dev/rdisk2</code>. Awas jangan salah ketik, bisa-bisa hardisk utama kita kena format.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo dd bs=1m if=Downloads/2017-04-10-raspbian-jessie-lite.img of=/dev/rdisk2
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Password:
1237+1 records in
1237+1 records out
1297862656 bytes transferred in 142.520214 secs (9106516 bytes/sec)
</code></pre></div></div>
<p>Saya biasanya menjadikan Raspberry PI ini menjadi headless, alias tanpa monitor. Untuk mengaksesnya kita gunakan SSH. Biasanya SSH ini harus diaktifkan dari menu konfigurasi yang baru bisa diakses setelah login ke OS. Tapi kali ini, kita akan aktifkan langsung tanpa perlu pasang monitor dan keyboard.</p>
<p>Caranya sederhana, yaitu buat file dengan nama <code class="language-plaintext highlighter-rouge">ssh</code> di top level folder dalam MicroSD. Untuk itu, mount dulu MicroSD yang sudah diisi image Raspbian tadi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mount
/dev/disk1 on / (hfs, local, journaled)
devfs on /dev (devfs, local, nobrowse)
map -hosts on /net (autofs, nosuid, automounted, nobrowse)
map auto_home on /home (autofs, automounted, nobrowse)
/dev/disk2s2 on /Volumes/Untitled (ufsd_ExtFS, local, nodev, nosuid, noowners)
/dev/disk2s1 on /Volumes/boot (msdos, local, nodev, nosuid, noowners)
</code></pre></div></div>
<p>Kemudian buat filenya. Bisa dengan File Explorer, klik kanan, Create New File. Tapi command line jauh lebih cepat.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ touch /Volumes/boot/ssh
</code></pre></div></div>
<p><a name="setup-network"></a></p>
<h2 id="setup-networking">Setup Networking</h2>
<p>Ada dua interface yang biasa ditemukan di Raspberry: ethernet dan wifi.</p>
<h3 id="ethernet">Ethernet</h3>
<p>Secara default, Raspberry Pi akan mengatur ethernet card yang dimilikinya untuk mendapatkan alamat IP otomatis (DHCP Client). Akan tetapi, kadangkala kita ingin mendapatkan alamat IP yang statis, sehingga bisa langsung kita akses melalui SSH dan cross cable.</p>
<p>Untuk mengatur agar alamat IPnya statis, kita edit file <code class="language-plaintext highlighter-rouge">/etc/dhcpcd.conf</code> yang ada di partisi <code class="language-plaintext highlighter-rouge">rootfs</code>. Aktifkan baris berikut ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>controlgroup wheel
interface eth0
static ip_address=192.168.0.10/24
</code></pre></div></div>
<h3 id="wifi">WiFi</h3>
<p>Raspberry PI model terbaru (Pi 3 model B dan Pi Zero W) sudah memiliki chipset WiFi. Berikut cara untuk mendaftarkan SSID beserta passwordnya langsung ke SD Card sehingga pada waktu dinyalakan Raspi akan langsung terhubung dengan WiFi kita.</p>
<p>Misalnya, nama SSIDnya adalah <code class="language-plaintext highlighter-rouge">wifiendy</code> dan passwordnya <code class="language-plaintext highlighter-rouge">abcd11223344</code>. Isi konfigurasi berikut di file <code class="language-plaintext highlighter-rouge">/Volumes/boot/wpa_supplicant.conf</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>country=ID
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
ssid="wifiendy"
psk="abcd11223344"
}
</code></pre></div></div>
<p>Setelah itu, unmount MicroSD, dan pasang di Raspberry PI. Lalu tancapkan di kabel power dan nyalakan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>diskutil unmountDisk /dev/rdisk2
</code></pre></div></div>
<p><a name="login"></a></p>
<h2 id="login-ke-raspbian">Login ke Raspbian</h2>
<p>Beberapa saat kemudian, kita cek router kita untuk mengetahui alamat IP si Raspberry PI ini. Caranya bisa dengan login ke router dan melihat daftar perangkat yang terkoneksi, atau bisa juga dengan <a href="https://software.endy.muhardin.com/linux/cara-mengetahui-ip-address-dari-mac-address/">scan jaringan menggunakan nmap</a>.</p>
<p>Selanjutnya, kita bisa langsung akses Raspberry PI tersebut menggunakan SSH. Defaultnya Raspberry PI menyediakan username <code class="language-plaintext highlighter-rouge">pi</code> dan password <code class="language-plaintext highlighter-rouge">raspberry</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh pi@192.168.100.10
The authenticity of host '192.168.100.10 (192.168.100.10)' can't be established.
ECDSA key fingerprint is SHA256:I7nVT26pbAkykqkEx7wLcJbLwAER/FZDBpV+PlZOWOg.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.100.10' (ECDSA) to the list of known hosts.
pi@192.168.100.10's password:
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
SSH is enabled and the default password for the 'pi' user has not been changed.
This is a security risk - please login as the 'pi' user and type 'passwd' to set a new password.
</code></pre></div></div>
<p><a name="user-baru"></a></p>
<h2 id="membuat-user-baru">Membuat User Baru</h2>
<p>Seperti kita lihat, Raspberry PI mengeluarkan peringatan bahwa default user <code class="language-plaintext highlighter-rouge">pi</code> tersebut merupakan resiko keamanan, karena semua orang tau username dan passwordnya. Untuk itu kita akan menghapus user <code class="language-plaintext highlighter-rouge">pi</code> dan menggantinya dengan user baru, misalnya namanya <code class="language-plaintext highlighter-rouge">endy</code>. User <code class="language-plaintext highlighter-rouge">endy</code> ini akan memiliki</p>
<p>Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo useradd -m -G $(groups | tr ' ' ',') endy
$ sudo passwd endy
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
</code></pre></div></div>
<p>Kemudian kita coba exit dan ssh lagi dengan user yang baru tersebut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ exit
$ ssh endy@192.168.100.10
</code></pre></div></div>
<p><a name="hapus-user-pi"></a></p>
<h2 id="menghapus-user-pi">Menghapus User pi</h2>
<p>Selanjutnya, kita hapus user <code class="language-plaintext highlighter-rouge">pi</code> supaya benar-benar aman. Beberapa artikel menyarankan disable saja, tapi buat amannya ya kita hapus saja.</p>
<p>Command yang kita gunakan untuk membuat user <code class="language-plaintext highlighter-rouge">endy</code> tadi akan memasukkan user <code class="language-plaintext highlighter-rouge">endy</code> ke semua grup yang dimiliki user <code class="language-plaintext highlighter-rouge">pi</code>, termasuk grup <code class="language-plaintext highlighter-rouge">pi</code>. Sebelum menghapus user <code class="language-plaintext highlighter-rouge">pi</code>, kita harus terlebih dulu mengeluarkan user <code class="language-plaintext highlighter-rouge">endy</code> dari grup <code class="language-plaintext highlighter-rouge">pi</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo deluser endy pi
</code></pre></div></div>
<p>Setelah itu baru kita bisa menghapus user <code class="language-plaintext highlighter-rouge">pi</code> berikut seluruh grup dan home foldernya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo deluser --remove-home pi
</code></pre></div></div>
<p><a name="keyboard-timezone"></a></p>
<h2 id="mengganti-keyboard-dan-timezone">Mengganti Keyboard dan Timezone</h2>
<p>Karena Raspberry PI buatan Inggris, maka default keyboardnya adalah <code class="language-plaintext highlighter-rouge">UK</code>, bukan <code class="language-plaintext highlighter-rouge">US</code> seperti biasanya kita instal Linux. Di komputer saya, dampaknya tombol <code class="language-plaintext highlighter-rouge">"</code> akan menghasilkan karakter <code class="language-plaintext highlighter-rouge">@</code>. Untuk itu kita ganti dulu keyboardnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo nano /etc/default/keyboard
</code></pre></div></div>
<p>Kemudian ganti menjadi <code class="language-plaintext highlighter-rouge">US</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>XKBLAYOUT="us"
</code></pre></div></div>
<p>Selanjutnya, kita ganti juga timezone.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo dpkg-reconfigure tzdata
</code></pre></div></div>
<p><a name="update-upgrade"></a></p>
<h2 id="update-dan-upgrade">Update dan Upgrade</h2>
<p>Langkah standar, kita update dan upgrade dulu Raspbian-nya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y && sudo apt-get autoclean && sudo reboot
</code></pre></div></div>
<p><a name="passwordless-ssh"></a></p>
<h2 id="passwordless-login">Passwordless Login</h2>
<p>Supaya bisa login tanpa password, kita perlu mendaftarkan public key komputer/laptop kita ke Raspberry PI.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh-copy-id endy@192.168.100.10
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/Users/endymuhardin/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
endy@192.168.100.10's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'endy@192.168.100.10'"
and check to make sure that only the key(s) you wanted were added.
</code></pre></div></div>
<p>Sekarang kita coba lagi login, harusnya kita tidak dimintai password.</p>
<p>Setelah berhasil, kita akan sama sekali mematikan password pada ssh. Jadi kalau belum mendaftarkan public key, tidak akan bisa login menggunakan password.</p>
<p>Edit file <code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo nano /etc/ssh/sshd_config
</code></pre></div></div>
<p>Kemudian pasang konfigurasi berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PermitRootLogin no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no
</code></pre></div></div>
<p>Kita bisa test dengan username yang public keynya belum kita daftarkan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh coba@192.168.100.10
Permission denied (publickey).
</code></pre></div></div>
<p><a name="proteksi-brute-force-ssh"></a></p>
<h2 id="proteksi-brute-force-ssh">Proteksi Brute Force SSH</h2>
<p>Kita akan memblokir alamat IP yang berusaha mencoba SSH berkali-kali. Berikut rule <code class="language-plaintext highlighter-rouge">iptables</code>nya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -A INPUT -i eth0 -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name SSH --rsource
iptables -A INPUT -i eth0 -p tcp -m tcp --dport 22 -m recent --rcheck --seconds 30 --hitcount 4 --rttl --name SSH --rsource -j REJECT --reject-with tcp-reset
iptables -A INPUT -i eth0 -p tcp -m tcp --dport 22 -m recent --rcheck --seconds 30 --hitcount 3 --rttl --name SSH --rsource -j LOG --log-prefix "SSH brute force "
iptables -A INPUT -i eth0 -p tcp -m tcp --dport 22 -m recent --update --seconds 30 --hitcount 3 --rttl --name SSH --rsource -j REJECT --reject-with tcp-reset
iptables -A INPUT -i eth0 -p tcp -m tcp --dport 22 -j ACCEPT
</code></pre></div></div>
<p>Penjelasan per baris:</p>
<ol>
<li>Tandai semua paket yang menuju port <code class="language-plaintext highlighter-rouge">22</code> dengan nama <code class="language-plaintext highlighter-rouge">SSH</code></li>
<li>Bila ada koneksi ke-4 dari asal (alamat IP) yang sama dalam 30 detik terakhir, langsung ditolak</li>
<li>Koneksi ke-3 dicatat di log dengan keyword <code class="language-plaintext highlighter-rouge">SSH brute force</code></li>
<li>Setelah dilog, langsung ditolak</li>
<li>Sisanya (yaitu koneksi pertama dan kedua) diijinkan</li>
</ol>
<p>Setelah perintah di atas dijalankan, firewallnya akan langsung aktif. Tetapi pada waktu restart, rule tersebut harus disave dulu. Caranya mudah, cukup pasang paket <code class="language-plaintext highlighter-rouge">iptables-persistent</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt-get install iptables-persistent -y
</code></pre></div></div>
<p>Pada waktu diinstal, dia akan menanyakan apakah rule yang kita pasang tadi ingin disave. Jawab saja iyes. Setelah selesai, dia akan simpan file ke <code class="language-plaintext highlighter-rouge">/etc/iptables/rules.v4</code>. Bisa kita cek dengan text editor.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo vim /etc/iptables/rules.v4
</code></pre></div></div>
<p>Ada beberapa filter tambahan yang biasa saya gunakan. Lengkapnya terlihat seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
# Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT
# Accepts all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allows all outbound traffic
# You could modify this to only allow certain traffic
-A OUTPUT -j ACCEPT
# SSH Brute Force Protection
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name SSH --rsource
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -m recent --rcheck --seconds 30 --hitcount 4 --rttl --name SSH --rsource -j REJECT --reject-with tcp-reset
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -m recent --rcheck --seconds 30 --hitcount 3 --rttl --name SSH --rsource -j LOG --log-prefix "SSH brute force "
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -m recent --update --seconds 30 --hitcount 3 --rttl --name SSH --rsource -j REJECT --reject-with tcp-reset
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -j ACCEPT
# log iptables denied calls (access via 'dmesg' command)
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7
# Reject all other inbound - default deny unless explicitly allowed policy:
-A INPUT -j REJECT
-A FORWARD -j REJECT
COMMIT
</code></pre></div></div>
<p>Kita bisa test dengan membuka beberapa terminal sekaligus dan mencoba ssh ke komputer tersebut. Kita akan lihat bahwa dua koneksi pertama diterima, sedangkan koneksi berikutnya ditolak.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/raspi-hardening/ssh-bruteforce-demo.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/raspi-hardening/ssh-bruteforce-demo.png" alt="Test SSH Brute Force" /></a></p>
<p>Kita bisa lihat bahwa insiden tersebut sudah dilog dengan perintah <code class="language-plaintext highlighter-rouge">dmesg</code>. Outputnya kira-kira seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SSH brute force IN=eth0 OUT= MAC=30:85:a9:47:f6:9c:e4:8d:8c:7b:3c:a5:08:00 SRC=192.168.44.1 DST=192.168.44.252 LEN=64 TOS=0x00 PREC=0x00 TTL=63 ID=14346 DF PROTO=TCP SPT=50169 DPT=22 WINDOW=65535 RES=0x00 SYN URGP=0
</code></pre></div></div>
<p><a name="automount-usb-debian"></a></p>
<h2 id="automount-usb">Automount USB</h2>
<p>Jaman sekarang, USB flashdisk/harddisk sudah menjadi kebutuhan umum. Apalagi kalau kita ingin membuat home theater. Kita ingin pada saat ditancapkan, flashdisknya otomatis dimount. Kita juga ingin agar disk yang berformat <code class="language-plaintext highlighter-rouge">NTFS</code> bisa dibaca dengan baik.</p>
<p>Pertama, install dulu paket yang dibutuhkan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install usbmount ntfs-3g
</code></pre></div></div>
<p>Kemudian, kita edit file <code class="language-plaintext highlighter-rouge">/etc/usbmount/usbmount.conf</code> agar bisa mounting otomatis. Berikut isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FILESYSTEMS="vfat ntfs fuseblk ext2 ext3 ext4 hfsplus"
FS_MOUNTOPTIONS="-fstype=ntfs-3g,nls=utf8,umask=007,gid=46 -fstype=fuseblk,nls=utf8,umask=007,gid=46 -fstype=vfat,gid=1000,uid=1000,umask=007"
</code></pre></div></div>
<p>Edit juga file <code class="language-plaintext highlighter-rouge">/etc/udev/rules.d/usbmount.rules</code> sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>KERNEL=="sd*", DRIVERS=="sbp2", ACTION=="add", PROGRAM="/bin/systemd-escape -p --template=usbmount@.service $env{DEVNAME}", ENV{SYSTEMD_WANTS}+="%c"
KERNEL=="sd*", SUBSYSTEMS=="usb", ACTION=="add", PROGRAM="/bin/systemd-escape -p --template=usbmount@.service $env{DEVNAME}", ENV{SYSTEMD_WANTS}+="%c"
KERNEL=="ub*", SUBSYSTEMS=="usb", ACTION=="add", PROGRAM="/bin/systemd-escape -p --template=usbmount@.service $env{DEVNAME}", ENV{SYSTEMD_WANTS}+="%c"
KERNEL=="sd*", ACTION=="remove", RUN+="/usr/share/usbmount/usbmount remove"
KERNEL=="ub*", ACTION=="remove", RUN+="/usr/share/usbmount/usbmount remove"
</code></pre></div></div>
<p>Dan juga file <code class="language-plaintext highlighter-rouge">/etc/systemd/system/usbmount@.service</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
BindTo=%i.device
After=%i.device
[Service]
Type=oneshot
TimeoutStartSec=0
Environment=DEVNAME=%I
ExecStart=/usr/share/usbmount/usbmount add
RemainAfterExit=yes
</code></pre></div></div>
<p>Kita bisa coba tancapkan flashdisk, kemudian liat isi file <code class="language-plaintext highlighter-rouge">/etc/mtab</code> atau jalankan perintah <code class="language-plaintext highlighter-rouge">mount</code> untuk melihat ke folder mana flashdisk tersebut dimount.</p>
<h2 id="penutup">Penutup</h2>
<p>Nah demikian panduan instalasi Raspberry Pi dengan Raspbian Lite. Mudah-mudahan bermanfaat, terutama buat saya sendiri yang sering instal ulang :D</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li>https://www.reddit.com/r/raspberry_pi/comments/1z31qh/10_things_to_do_after_setting_up_raspberry_pi/?st=j2px1hpz&sh=9507aacd</li>
<li>https://www.raspberrypi.org/documentation/remote-access/ssh/</li>
<li>https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md
https://raspberrypi.stackexchange.com/a/42103</li>
<li>https://serverfault.com/a/544583/400761</li>
</ul>
Tips Mudah Windows Update untuk menangkal WannaCry2017-05-19T07:00:00+07:00https://software.endy.muhardin.com/devops/windows-update-wannacry<p>Wiken lalu, para staf IT di seluruh dunia mendapat hadiah istimewa, yaitu malware WannaCry. Banyak korban yang kena, baik di luar negeri maupun di negara kita. Sampai masuk TV segala.</p>
<p>Saya tidak akan membahas lagi tentang apa itu WannaCry. Silahkan baca <a href="https://rahard.wordpress.com/2017/05/14/penanganan-ransomware-wannacry/">blognya Prof. Budi Rahardjo</a>, baca <a href="https://goo.gl/s7xCV1">slide presentasi saya</a>, atau – bagi yang tidak suka artikel bahasa Indonesia – bisa baca <a href="https://www.troyhunt.com/everything-you-need-to-know-about-the-wannacrypt-ransomware/">tulisannya Troy Hunt</a>. Pada artikel ini saya akan bahas tentang bagaimana menyelesaikan urusan WannaCry ini, untuk puluhan komputer, tanpa membuat kita <strong>wanna cry</strong>.</p>
<!--more-->
<blockquote>
<p>Cara menangkal malware ini sebenarnya sangat sederhana, yaitu Update Windows Anda !!!</p>
</blockquote>
<p>Tapi sayangnya, mayoritas orang Indonesia tidak mengupdate Windows secara berkala. Bahkan sudah disediakan fitur otomatisnya pun malah dimatikan 😪. Banyak alasannya, diantaranya boros benwit, bikin komputer lemot (selama proses update berjalan), dan mengganggu ritual tenggo. Begitu jam 16:55, mau shutdown komputer, ternyata tulisannya installing update 11 of 208 …</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/wsus-offline/windows-update.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/wsus-offline/windows-update.png" alt="Windows Update" /></a></p>
<p>Capedee …</p>
<blockquote>
<p>Oke gampang kan solusinya, tinggal masuk ke Control Panel, tekan Update, tunggu selesai? Apa masalahnya?</p>
</blockquote>
<p>Nah, ternyata masalah besar. Jangankan kita manusia biasa, bahkan <a href="http://www.newyorker.com/humor/borowitz-report/gates-spends-entire-first-day-back-in-office-trying-to-install-windows-8-1">Bill Gates saja seharian penuh tidak berhasil menjalankan Windows Update, bahkan sudah dibantu Satya Nadella</a>. 😂😂</p>
<p>Tambah urusan lagi bila Anda -sebagai staf IT support- punya puluhan komputer yang harus dijalankan Windows Update-nya. Belang-belang pulak. Ada Windows 10, 8.1, 8, 7, Vista, dan bahkan XP 🙈😵😭. Kalau tiap komputer butuh donlod 2GB buat update, bayangkan kalo komputernya ada 20. Bisa2 lemot seisi gedung.</p>
<p>Sebetulnya Microsoft sudah menyediakan patch khusus untuk menambal lubang yang dieksploitasi WannaCry. Tapi sayangnya, seringkali dia mengeluarkan pesan error Not Applicable.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/wsus-offline/update-not-applicable.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/wsus-offline/update-not-applicable.png" alt="Windows Update" /></a></p>
<p>Nampaknya untuk bisa memasang patch ini kita harus pasang dulu patch-patch yang dirilis sebelumnya. Tidak bisa dia sendirian saja. Tidak heran Bill Gates kesulitan.</p>
<blockquote>
<p>Lalu bagaimana?</p>
</blockquote>
<p>Untunglah ada orang lain yang juga sudah mengalami masalah sama, kemudian membuatkan solusinya. Mereka membuat aplikasi open source bernama <a href="http://www.wsusoffline.net/docs/">WSUS Offline</a></p>
<p>Cara kerjanya sangat sederhana. Aplikasi ini memiliki dua bagian : bagian mengunduh dan bagian menjalankan.</p>
<p>Pertama, kita jalankan dulu bagian mengunduh. Berikut tampilannya.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/wsus-offline/wsus-downloader.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/wsus-offline/wsus-downloader.png" alt="WSUS Downloader" /></a></p>
<p>Kita cukup centang versi yang mau kita patch. Karena urusan belang-belang tadi, maka saya centang semua (7,8,10). Vista dan XP tidak ada karena sudah tidak disupport (diskontinu). Silahkan donlod WSUS versi lama kalau butuh Vista dan XP.</p>
<p>Setelah centang, klik Start. Saya anjurkan untuk menjalankannya sebelum pulang kantor dan ditinggal jalan. Soalnya setelah selesai versi 7,8,10 64 bit, saya cek ukurannya 10GB. Bisa diamuk orang sekantor kalo kita jalankan siang-siang.</p>
<p>Pada waktu tekan Start, dia akan mengunduh paket update dari server Microsoft, mulai dari versi pertama OS rilis, sampai hari ini. Makanya ada banyak file yang dia unduh.</p>
<p>Setelah selesai dia donlod, kita zip satu folder tersebut, masukkan ke flashdisk. Ingat ya, zip dulu supaya proses copy gak lemot. Soalnya proses copy ratusan file kecil jauh lebih lambat daripada satu file besar.</p>
<p>Copy file WSUS tersebut ke komputer/laptop yang mau diupdate, kemudian extract. Setelah itu masuk ke folder <code class="language-plaintext highlighter-rouge">client</code>, lalu jalankan <code class="language-plaintext highlighter-rouge">UpdateInstaller</code>.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/wsus-offline/run-wsus-client.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/wsus-offline/wsus-downloader.png" alt="WSUS Downloader" /></a></p>
<p>Klik start, kemudian <code class="language-plaintext highlighter-rouge">sit back and relax</code>. Sementara menunggu selesai, jalankan prosedur yang sama di komputer lain. WSUS client ini akan menginstal update dari hasil download kita tadi, sehingga proses update tidak membutuhkan koneksi internet. Dengan demikian, kita bisa menghemat bandwidth sekaligus mengurangi resiko komputer terinfeksi <code class="language-plaintext highlighter-rouge">WannaCry</code> sementara proses update belum selesai.</p>
<p>Dengan menggunakan prosedur ini, dalam sehari tim IT <a href="http://tazkia.ac.id">Tazkia</a> berhasil mengupdate puluhan laptop dan PC dalam waktu sehari, terdiri dari Windows 7, Windows 8, Windows 8.1, dan Windows 10.</p>
<p>Selamat mencoba, semoga berhasil.</p>
<p>Dan jangan lupa, walaupun <code class="language-plaintext highlighter-rouge">Windows Update</code> (sementara ini) bisa menangkal malware WannaCry, tetap saja <code class="language-plaintext highlighter-rouge">apt-get update</code> merupakan solusi yang jauh lebih strategis dan superior.</p>
Mendeploy Aplikasi dari Gitlab ke VPS2017-05-08T07:00:00+07:00https://software.endy.muhardin.com/devops/deploy-gitlab-vps<p>Setelah kita <a href="http://software.endy.muhardin.com/devops/instalasi-gitlab-runner-autoscale/">menyiapkan infrastruktur Continuous Delivery dengan Gitlab CI</a>, sekarang kita akan menggunakannya untuk melakukan deployment otomatis ke VPS. Pada artikel terdahulu, kita sudah <a href="http://software.endy.muhardin.com/java/deploy-jenkins-vps/">mendeploy dari Jenkins ke VPS</a> dan juga <a href="http://software.endy.muhardin.com/devops/gitlab-ci-kubernetes-gke/">mendeploy dari Gitlab ke Kubernetes</a>. Nah sekarang, kita akan mendeploy dari Gitlab ke VPS.</p>
<ul id="markdown-toc">
<li><a href="#cara-kerja-gitlab-ci" id="markdown-toc-cara-kerja-gitlab-ci">Cara Kerja Gitlab CI</a></li>
<li><a href="#mendaftarkan-private-key" id="markdown-toc-mendaftarkan-private-key">Mendaftarkan Private Key</a></li>
<li><a href="#membuat-cicd-script" id="markdown-toc-membuat-cicd-script">Membuat CI/CD Script</a></li>
<li><a href="#continuous-delivery-workflow" id="markdown-toc-continuous-delivery-workflow">Continuous Delivery Workflow</a></li>
</ul>
<!--more-->
<p>Secara garis besar, berikut adalah langkah-langkahnya:</p>
<ul>
<li>menyiapkan VPS yang akan menjadi target deployment. Ini tidak akan kita bahas lagi, silahkan baca bagian awal dari <a href="http://software.endy.muhardin.com/java/deploy-jenkins-vps/">artikel sebelumnya</a> sampai ke bagian deployment manual.</li>
<li>menyiapkan pasangan public dan private key untuk SSH. Inipun juga sudah ada di artikel terdahulu sehingga tidak akan kita ulangi lagi.</li>
<li>mendaftarkan private key ke Gitlab</li>
<li>membuat script <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> untuk melakukan deployment otomatis pada saat kita membuat tag di repository.</li>
</ul>
<h2 id="cara-kerja-gitlab-ci">Cara Kerja Gitlab CI</h2>
<p>Di <a href="http://software.endy.muhardin.com/devops/instalasi-gitlab-runner-autoscale/">artikel sebelumnya</a>, kita telah jelaskan bahwa kita memilih executor <code class="language-plaintext highlighter-rouge">docker-machine</code> untuk Gitlab Runner kita. Dengan demikian, pada waktu proses build dijalankan, kita akan mendapat container baru dalam setiap job. Ini artinya antar job kita tidak punya file yang disimpan secara permanen, termasuk di antaranya konfigurasi ssh client. Jadi, setiap kali proses build berjalan, kita harus membuat pasangan private dan public key baru karena yang ada di proses build sebelumnya sudah dihapus.</p>
<p>Tentu ini merepotkan, karena public key untuk deployment biasanya kita daftarkan di server yang menjadi target deployment. Kalau setiap kali key baru, tentu harus didaftarkan ulang dan menjadi tidak otomatis lagi.</p>
<p>Solusinya, kita masukkan private key menjadi variabel dalam Gitlab, dan kita suruh dia menulis isi variabel tersebut menjadi file <code class="language-plaintext highlighter-rouge">.ssh/id_rsa</code> seperti file private key pada umumnya.</p>
<p><a name="mendaftarkan-private-key"></a></p>
<h2 id="mendaftarkan-private-key">Mendaftarkan Private Key</h2>
<p>Seperti pada <a href="http://software.endy.muhardin.com/java/deploy-jenkins-vps/">artikel Jenkins terdahulu</a>, buka file private key dengan text editor dan kemudian copy isinya. Kemudian kita masuk ke menu <code class="language-plaintext highlighter-rouge">Settings > CI/CD Pipelines</code>. Tambahkan secret variables seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-ci/ssh-private-key.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-ci/ssh-private-key.png" alt="Secret Variable" /></a></p>
<h2 id="membuat-cicd-script">Membuat CI/CD Script</h2>
<p>Selanjutnya, kita tinggal gunakan variabel tersebut di file <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> seperti ini</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">which</span><span class="nv"> </span><span class="s">ssh-agent</span><span class="nv"> </span><span class="s">||</span><span class="nv"> </span><span class="s">(</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">update</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">install</span><span class="nv"> </span><span class="s">openssh-client</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">)'</span>
<span class="pi">-</span> <span class="s">eval $(ssh-agent -s)</span>
<span class="pi">-</span> <span class="s">ssh-add <(echo "$SSH_PRIVATE_KEY")</span>
<span class="pi">-</span> <span class="s">mkdir -p ~/.ssh</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">[[</span><span class="nv"> </span><span class="s">-f</span><span class="nv"> </span><span class="s">/.dockerenv</span><span class="nv"> </span><span class="s">]]</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">ssh-keyscan</span><span class="nv"> </span><span class="s">-H</span><span class="nv"> </span><span class="s">"server.target.deployment.com"</span><span class="nv"> </span><span class="s">></span><span class="nv"> </span><span class="s">~/.ssh/known_hosts'</span>
</code></pre></div></div>
<p>Pada script di atas, kita membuat blok <code class="language-plaintext highlighter-rouge">before_script</code> yang akan dijalankan sebelum masing-masing job dieksekusi. Ada beberapa hal yang kita kerjakan dalam blok tersebut, yaitu:</p>
<ul>
<li>cek apakah <code class="language-plaintext highlighter-rouge">ssh-agent</code> sudah terinstal. Bila belum install dulu dengan <code class="language-plaintext highlighter-rouge">apt-get</code>. Tentunya ini mengasumsikan kita menggunakan docker image yang turunan debian.</li>
<li>load variabel atau path yang dibutuhkan <code class="language-plaintext highlighter-rouge">ssh-agent</code> ke shell yang kita gunakan (bash, csh, zsh, dan lainnya)</li>
<li>daftarkan isi variabel <code class="language-plaintext highlighter-rouge">$SSH_PRIVATE_KEY</code> ke dalam <code class="language-plaintext highlighter-rouge">ssh-agent</code> sehingga bisa digunakan pada saat melakukan ssh ke mesin lain</li>
<li>buat folder <code class="language-plaintext highlighter-rouge">.ssh</code> untuk menyimpan konfigurasi</li>
<li>ambil ssh host key dari server tujuan menggunakan perintah <code class="language-plaintext highlighter-rouge">ssh-keyscan</code>, hasilnya daftarkan ke dalam file <code class="language-plaintext highlighter-rouge">.ssh/known_hosts</code>. Perintah ini hanya dijalankan kalau berjalan di dalam docker container. Bila kita jalankan perintah ini di shell executor, maka nanti bisa mengganggu konfigurasi yang lainnya.</li>
</ul>
<p>Normalnya, bila kita pertama kali melakukan ssh ke mesin lain, kita akan dimintain konfirmasi host key seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh 192.168.10.100
The authenticity of host '192.168.10.100 (192.168.10.100)' can't be established.
ECDSA key fingerprint is SHA256:Qt7wVAJ7mW/y0TTHMgswxkb2SYhfBZ+pgkrqhQcMEbQ.
Are you sure you want to continue connecting (yes/no)?
</code></pre></div></div>
<p>Kita harus jawab <code class="language-plaintext highlighter-rouge">yes</code> untuk bisa melanjutkan.</p>
<p>Nah prompt ini akan menyulitkan untuk diotomasi, karena tidak bisa dijalankan oleh script. Script kan tidak bisa menjawab <code class="language-plaintext highlighter-rouge">yes</code>. Solusinya, kita ambil dulu host key dari server tersebut menggunakan perintah <code class="language-plaintext highlighter-rouge">ssh-keyscan</code>, kemudian kita daftarkan ke file <code class="language-plaintext highlighter-rouge">.ssh/known_hosts</code>. Bila sudah didaftarkan, kita tidak akan ditanyai lagi pada waktu login.</p>
<p><a name="continuous-delivery-workflow"></a></p>
<h2 id="continuous-delivery-workflow">Continuous Delivery Workflow</h2>
<p>Nah sekarang kita sudah bisa menjalankan workflow deployment sesuai <a href="http://semver.org/">Semantic Versioning</a> sebagai berikut:</p>
<ul>
<li>untuk melakukan rilis development, buat tag dengan qualifier <code class="language-plaintext highlighter-rouge">M</code>. Contohnya <code class="language-plaintext highlighter-rouge">1.0.1-M.001</code></li>
<li>untuk melakukan rilis testing, buat tag dengan qualifier <code class="language-plaintext highlighter-rouge">RC</code>. Contohnya <code class="language-plaintext highlighter-rouge">2.1.0-RC.012</code></li>
<li>untuk melakukan rilis production, buat tag dengan qualifier <code class="language-plaintext highlighter-rouge">RELEASE</code>. Contohnya <code class="language-plaintext highlighter-rouge">2.1.2-RELEASE</code></li>
</ul>
<p>Untuk membuat tag dengan git, perintahnya seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git tag 1.10.0-M114
git push --tags
</code></pre></div></div>
<p>Berikut adalah konfigurasi <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> untuk menjalankan siklus deployment di atas. Dia akan melakukan deployment sesuai dengan tag yang kita buat.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">docker:latest</span>
<span class="na">services</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">docker:dind</span>
<span class="pi">-</span> <span class="s">mysql:latest</span>
<span class="na">variables</span><span class="pi">:</span>
<span class="na">DOCKER_DRIVER</span><span class="pi">:</span> <span class="s">overlay</span>
<span class="na">SPRING_PROFILES_ACTIVE</span><span class="pi">:</span> <span class="s">docker,localstorage</span>
<span class="na">MYSQL_ROOT_PASSWORD</span><span class="pi">:</span> <span class="s">admin</span>
<span class="na">MYSQL_DATABASE</span><span class="pi">:</span> <span class="s">belajar</span>
<span class="na">MYSQL_USER</span><span class="pi">:</span> <span class="s">belajar</span>
<span class="na">MYSQL_PASSWORD</span><span class="pi">:</span> <span class="s">java</span>
<span class="na">cache</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">.m2/repository</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">build</span>
<span class="pi">-</span> <span class="s">deploy</span>
<span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">which</span><span class="nv"> </span><span class="s">ssh-agent</span><span class="nv"> </span><span class="s">||</span><span class="nv"> </span><span class="s">(</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">update</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">install</span><span class="nv"> </span><span class="s">openssh-client</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">)'</span>
<span class="pi">-</span> <span class="s">eval $(ssh-agent -s)</span>
<span class="pi">-</span> <span class="s">ssh-add <(echo "$SSH_PRIVATE_KEY")</span>
<span class="pi">-</span> <span class="s">mkdir -p ~/.ssh</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">[[</span><span class="nv"> </span><span class="s">-f</span><span class="nv"> </span><span class="s">/.dockerenv</span><span class="nv"> </span><span class="s">]]</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">ssh-keyscan</span><span class="nv"> </span><span class="s">-H</span><span class="nv"> </span><span class="s">"server.development.com"</span><span class="nv"> </span><span class="s">>></span><span class="nv"> </span><span class="s">~/.ssh/known_hosts'</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">[[</span><span class="nv"> </span><span class="s">-f</span><span class="nv"> </span><span class="s">/.dockerenv</span><span class="nv"> </span><span class="s">]]</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">ssh-keyscan</span><span class="nv"> </span><span class="s">-H</span><span class="nv"> </span><span class="s">"server.testing.com"</span><span class="nv"> </span><span class="s">>></span><span class="nv"> </span><span class="s">~/.ssh/known_hosts'</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">[[</span><span class="nv"> </span><span class="s">-f</span><span class="nv"> </span><span class="s">/.dockerenv</span><span class="nv"> </span><span class="s">]]</span><span class="nv"> </span><span class="s">&&</span><span class="nv"> </span><span class="s">ssh-keyscan</span><span class="nv"> </span><span class="s">-H</span><span class="nv"> </span><span class="s">"server.production.com"</span><span class="nv"> </span><span class="s">>></span><span class="nv"> </span><span class="s">~/.ssh/known_hosts'</span>
<span class="na">maven-build</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">maven:3-jdk-8</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span> <span class="s">mvn package -B -Dmaven.repo.local=.m2/repository</span>
<span class="na">artifacts</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">target/*.jar</span>
<span class="na">deploy-to-development</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">debian:latest</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/-M\./</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">scp target/*.jar root@server.development.com:/home/artivisi/aplikasi</span>
<span class="pi">-</span> <span class="s">ssh root@server.development.com service aplikasi restart</span>
<span class="na">deploy-to-testing</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">debian:latest</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/-RC\./</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">scp target/*.jar root@server.testing.com:/home/artivisi/aplikasi</span>
<span class="pi">-</span> <span class="s">ssh root@server.testing.com service aplikasi restart</span>
<span class="na">deploy-to-production</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">debian:latest</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/-RELEASE$/</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">scp target/*.jar root@server.production.com:/home/artivisi/aplikasi</span>
<span class="pi">-</span> <span class="s">ssh root@server.production.com service aplikasi restart</span>
</code></pre></div></div>
<p>Selamat mencoba, semoga sukses :D</p>
Instalasi Gitlab Runner Autoscale2017-04-26T07:00:00+07:00https://software.endy.muhardin.com/devops/instalasi-gitlab-runner-autoscale<p>Setelah kita berhasil <a href="http://software.endy.muhardin.com/devops/instalasi-gitlab-gce/">memasang Gitlab CE di Google Cloud Platform</a>, sekarang kita akan lanjutkan untuk memasang Gitlab Runner. Ada satu fitur menarik dari Gitlab Runner ini, yaitu <code class="language-plaintext highlighter-rouge">autoscale</code>. Fitur ini memungkinkan kita untuk menghemat biaya sewa VPS di penyedia layanan cloud seperti Digital Ocean, Amazon, Google, dan lainnya. Caranya adalah dengan membuat VPS untuk menjalankan build sesuai kebutuhan. Bila sedang ramai antrian, Gitlab akan membuat banyak VPS. Bila antrian sepi, VPS tersebut akan dihapus sehingga tidak menimbulkan biaya.</p>
<p>Berikut langkah-langkah untuk memasang Gitlab Runner dengan fitur <code class="language-plaintext highlighter-rouge">autoscale</code>:
<!--more--></p>
<ul>
<li><a href="#cache-registry">Setup CI Cache dan Docker Registry Proxy</a></li>
<li>
<p><a href="#setup-gitlab-runner">Setup Gitlab Runner</a></p>
<ul>
<li><a href="#membuat-vps-runner">Membuat VPS Runner</a></li>
<li><a href="#instalasi-docker-engine">Setup Docker Engine</a></li>
<li><a href="#instalasi-docker-machine">Setup Docker Machine</a></li>
<li><a href="#instalasi-gitlab-multi-runner">Instalasi Gitlab Multi Runner</a></li>
</ul>
</li>
<li><a href="#registrasi-gitlab-multi-runner">Registrasi Gitlab Runner</a></li>
<li><a href="#test-gitlab-runner">Test Gitlab Runner</a></li>
</ul>
<p>Sebelum kita mulai, kita bahas dulu tentang cara kerja Gitlab CI Runner. Banyak di antara kita yang sebelumnya terbiasa menggunakan aplikasi CI yang terpisah dengan source code repository seperti misalnya Jenkins, Travis CI, Circle CI, dan sebagainya. Untuk aplikasi CI yang standalone, skema cara kerjanya seperti ini:</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/ci-standalone.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/ci-standalone.jpg" alt="Skema CI Standalone" /></a></p>
<p>Biasanya untuk meningkatkan kapasitas build, aplikasi CI ini memiliki fitur slave, yaitu mesin lain yang khusus berfungsi untuk menjalankan build, terpisah dari <code class="language-plaintext highlighter-rouge">master</code>nya, yaitu mesin utama yang bertugas mengontrol proses build. Kalau digambarkan skemanya kira-kira seperti ini:</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/ci-master-slave.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/ci-master-slave.jpg" alt="Skema CI Master Slave" /></a></p>
<p>Untuk Gitlab, fitur CInya sudah terintegrasi dengan aplikasi source code repositorynya. Kita hanya perlu memasang <code class="language-plaintext highlighter-rouge">Runner</code> yang kira-kira fungsinya sama seperti <code class="language-plaintext highlighter-rouge">slave</code> pada diagram di atas.</p>
<p>Runner menggunakan <code class="language-plaintext highlighter-rouge">executor</code> untuk menjalankan proses build. Ada beberapa executor yang disediakan oleh Gitlab, yaitu:</p>
<ul>
<li>Shell</li>
<li>Docker</li>
<li>Docker Machine</li>
<li>Virtual Box</li>
<li>Parallels</li>
<li>SSH</li>
<li>Kubernetes</li>
</ul>
<p>Shell executor akan menjalankan proses build di dalam mesin Runner, sama seperti kita menjalankan build di laptop kita sendiri. Di laptop kita, agar proses build bisa berjalan, kita harus menyediakan semua yang dibutuhkan dalam proses build. Misalnya kita membuat aplikasi Java yang dibuild dengan Apache Maven dan menggunakan database MySQL, maka kita harus menginstal dulu:</p>
<ul>
<li>Java SDK</li>
<li>Apache Maven</li>
<li>Database MySQL</li>
</ul>
<p>Kemudian mengkonfigurasikan semuanya agar aplikasi kita bisa dibuild. Kurang lebih prosesnya sama seperti yang dijelaskan di <a href="http://software.endy.muhardin.com/java/project-bootstrap-01/">artikel project setup</a>.</p>
<p>Demikian juga halnya dengan Shell executor. Kita harus menginstal dan mengkonfigurasikan kebutuhan tersebut di mesin Runner. Untuk sistem Linux, caranya bisa dibaca <a href="http://software.endy.muhardin.com/java/persiapan-coding-java/">di sini</a>. Setelah melakukan persiapan tersebut, kita bisa lanjut menginstal runner.</p>
<p>Penggunaan shell executor relatif mudah kalau semua project kita menggunakan teknologi yang sama. Walaupun demikian, kita tetap perlu membuatkan user database dan databasenya untuk masing-masing project kita. Ini akan sangat merepotkan kalau projectnya banyak. Apalagi kalau project kita menggunakan framework dan bahasa pemrograman yang berbeda-beda.</p>
<p>Agar lebih mudah, kita bisa gunakan executor Docker. Dengan executor ini, proses build kita akan dijalankan dalam docker container. Bila belum paham apa itu Docker, bisa baca <a href="http://software.endy.muhardin.com/linux/intro-docker/">pembahasan tentang apa itu docker</a> dan <a href="http://software.endy.muhardin.com/devops/docker-workflow/">workflow development dengan Docker</a>.</p>
<p>Yang lebih canggih dari executor Docker adalah executor Docker Machine, yang akan kita bahas pada artikel ini. Executor ini akan membuatkan VPS khusus untuk masing-masing proses build, tidak seperti executor Docker biasa yang menjalankan semua project dalam satu host. Executor ini cocok dikombinasikan dengan layanan VPS modern yang umumnya sekarang menghitung biaya dalam satuan jam. Jadi kita cukup sewa VPS selama proses build jalan. Setelah build selesai, VPS didestroy dan kita tidak perlu bayar. Fitur ini di Gitlab CI disebut dengan istilah <code class="language-plaintext highlighter-rouge">autoscale</code>.</p>
<p>Executor yang lain tidak kita bahas, silahkan baca sendiri <a href="https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/executors/README.md">dokumentasinya</a> kalau penasaran.</p>
<p>Baiklah, mari kita mulai instalasi dan setup Gitlab Runner Autoscale.</p>
<p><a name="cache-registry"></a></p>
<h2 id="setup-ci-cache-dan-docker-registry-proxy">Setup CI Cache dan Docker Registry Proxy</h2>
<p>Kegiatan build kita umumnya dijalankan di docker container. Untuk menjalankan container, docker perlu mengunduh image dulu (misalnya image <code class="language-plaintext highlighter-rouge">mysql:latest</code> dan <code class="language-plaintext highlighter-rouge">maven:latest</code>), yang besarnya bisa mencapai ratusan MB per image. Agar build lebih cepat, kita ingin menyimpan image ini supaya build kedua dan seterusnya tidak perlu donlod ulang.</p>
<p>Demikian juga dengan dependensi dan library. Bila kita menggunakan Maven, Gradle, npm, dan sejenisnya, ada banyak file yang dia unduh. File-file ini umumnya tidak berubah, sehingga bisa kita simpan agar build selanjutnya tidak perlu donlod lagi.</p>
<p>Untuk dua keperluan di atas, kita membutuhkan dua aplikasi:</p>
<ul>
<li>docker registry</li>
<li>build cache</li>
</ul>
<p>Docker registry sudah ada docker imagenya. Tinggal kita jalankan saja dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -d -p 6000:5000 \
-e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
--restart always \
--name registry registry:2
</code></pre></div></div>
<p>Gitlab mendukung build cache dalam format penyimpanan Amazon S3. Buat kita yang tidak ingin langganan S3, sudah ada yang membuatkan aplikasi tiruannya, yaitu minio. Inipun sudah ada docker imagenya, bisa dijalankan dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -it --restart always -p 9005:9000 \
-v /.minio:/root/.minio -v /export:/export \
--name minio \
minio/minio:latest server /export
</code></pre></div></div>
<p>Agar lebih mudah, kita bisa gabungkan keduanya dalam docker compose sebagai berikut. Simpan konfigurasi ini dengan nama file <code class="language-plaintext highlighter-rouge">docker-compose.yml</code></p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2.1"</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">docker-registry</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">registry:2</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">6000:5000</span>
<span class="na">build-cache</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">minio/minio:latest</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/.minio:/root/.minio</span>
<span class="pi">-</span> <span class="s">/export:/export</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">9005:9000</span>
<span class="na">command</span><span class="pi">:</span> <span class="s">server /export</span>
</code></pre></div></div>
<p>Untuk menjalankannya, kita buat dulu docker host di Google Cloud Platform menggunakan <code class="language-plaintext highlighter-rouge">docker-machine</code>. Projectnya kita gunakan project <code class="language-plaintext highlighter-rouge">gitlab-family</code>, sama dengan instalasi Gitlab CE <a href="http://software.endy.muhardin.com/devops/instalasi-gitlab-gce/">di artikel sebelumnya</a></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine create --driver google --google-zone asia-southeast1-a --google-project gitlab-family docker-registry-cache
</code></pre></div></div>
<p>Kemudian kita login ke dalam docker host tersebut untuk membuat folder yang dibutuhkan oleh <code class="language-plaintext highlighter-rouge">minio</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine ssh docker-registry-cache
</code></pre></div></div>
<p>Buat folder yang dibutuhkan <code class="language-plaintext highlighter-rouge">minio</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkdir -p /export/runner
</code></pre></div></div>
<p>Mumpung di sana, sekalian saja update dan upgrade</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get update && sudo apt-get upgrade -y
</code></pre></div></div>
<p>Buka terminal satu lagi, kemudian kita jalankan <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose up -d
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating network "cloudautomation_default" with the default driver
Pulling build-cache (minio/minio:latest)...
latest: Pulling from minio/minio
627beaf3eaaf: Pull complete
ac75cd34934f: Pull complete
f2a61a9fdfc0: Pull complete
8ad25f7f1798: Pull complete
Digest: sha256:e061d9ca378755ebb8fd9887ec71e78891b85afc09181c2542953606bd486319
Status: Downloaded newer image for minio/minio:latest
Pulling docker-registry (registry:2)...
2: Pulling from library/registry
709515475419: Pull complete
df6e278d8f96: Pull complete
4b0b08c1b8f7: Pull complete
80119f43a01e: Pull complete
acf34ba23c50: Pull complete
Digest: sha256:412e3b6494f623a9f03f7f9f8b8118844deaecfea19e3a5f1ce54eed4f400296
Status: Downloaded newer image for registry:2
Creating cloudautomation_docker-registry_1
Creating cloudautomation_build-cache_1
</code></pre></div></div>
<p>Kita bisa lihat statusnya dengan perintah <code class="language-plaintext highlighter-rouge">docker ps</code>. Kalau semua berjalan normal, outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dd6e0e7511c8 minio/minio:latest "minio server /export" 39 seconds ago Up 38 seconds 0.0.0.0:9005->9000/tcp cloudautomation_build-cache_1
a6c743b43de8 registry:2 "/entrypoint.sh /e..." 40 seconds ago Up 38 seconds 0.0.0.0:6000->5000/tcp cloudautomation_docker-registry_1
</code></pre></div></div>
<p>Setelah jalan, kita perlu melihat konfigurasi <code class="language-plaintext highlighter-rouge">accessKey</code> dan <code class="language-plaintext highlighter-rouge">secret</code> untuk kita gunakan nanti. Konfigurasi ini ada dalam folder <code class="language-plaintext highlighter-rouge">/.minio/config.json</code>.</p>
<p>Kembali ke terminal yang sudah login ke docker host tadi, lalu tampilkan konfigurasi minio</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo cat /.minio/config.json
</code></pre></div></div>
<p>Kita butuh <code class="language-plaintext highlighter-rouge">accessKey</code> dan <code class="language-plaintext highlighter-rouge">secretKey</code> seperti ini</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"credential"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"accessKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"6X7LFVH3V7HU7SHHF60U"</span><span class="p">,</span><span class="w">
</span><span class="nl">"secretKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"8ftRtZDOSv/pXNiRe1yqKoQHCu5IRvsZfx35YijI"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Cache server dan docker registry kita sudah selesai disetup. Terakhir, kita butuh informasi alamat IP untuk dipasang di konfigurasi runner. Jalankan perintah berikut untuk melihat semua instance VPS kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances list
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
docker-registry-cache asia-southeast1-a n1-standard-1 10.148.0.3 35.185.181.204 RUNNING
gitlab-ce asia-southeast1-a n1-standard-1 10.148.0.2 35.185.188.31 RUNNING
</code></pre></div></div>
<p>Kita butuh <code class="language-plaintext highlighter-rouge">INTERNAL_IP</code> untuk dipasang di runner nantinya. Sebagai rekap, berikut informasi yang sudah kita dapatkan:</p>
<ul>
<li>
<p>Docker Registry Proxy</p>
<ul>
<li>IP dan Port : <code class="language-plaintext highlighter-rouge">10.148.0.3:6000</code></li>
</ul>
</li>
<li>
<p>Minio untuk Build Cache</p>
<ul>
<li>IP dan Port : <code class="language-plaintext highlighter-rouge">10.148.0.3:9005</code></li>
<li>Access Key : <code class="language-plaintext highlighter-rouge">6X7LFVH3V7HU7SHHF60U</code></li>
<li>Secret Key : <code class="language-plaintext highlighter-rouge">8ftRtZDOSv/pXNiRe1yqKoQHCu5IRvsZfx35YijI</code></li>
</ul>
</li>
</ul>
<p>Selanjutnya, kita bisa mulai setup runner.</p>
<p><a name="setup-gitlab-runner"></a></p>
<h2 id="setup-gitlab-runner">Setup Gitlab Runner</h2>
<p>Setup runner terdiri dari dua kegiatan, yaitu:</p>
<ul>
<li>instalasi runner, yang berisi: docker engine, docker-machine, dan gitlab-ci-multi-runner</li>
<li>registrasi runner</li>
</ul>
<p><a name="membuat-vps-runner"></a></p>
<h3 id="membuat-vps-runner">Membuat VPS Runner</h3>
<p>Untuk menjalankan runner, kita butuh satu VM lagi. Buat dulu dengan menggunakan GCloud SDK.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances create gitlab-runner --image-family ubuntu-1604-lts --image-project ubuntu-os-cloud
</code></pre></div></div>
<p>Di dalam VM ini, kita akan menginstal:</p>
<ul>
<li>Docker Engine</li>
<li>Docker Machine</li>
<li>Gitlab Runner</li>
</ul>
<p>Setelah VPS siap, login dengan ssh</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute ssh gitlab-runner
</code></pre></div></div>
<p><a name="instalasi-docker-engine"></a></p>
<h3 id="instalasi-docker-engine">Instalasi Docker Engine</h3>
<p>Petunjuk instalasi lengkapnya bisa dibaca <a href="https://docs.docker.com/engine/installation/linux/ubuntu/#install-using-the-repository">di dokumentasi resminya</a>. Sesuai petunjuk di website tersebut, pertama kita harus instal dulu software yang dibutuhkan :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
</code></pre></div></div>
<p>Tambahkan GPG key Docker</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
</code></pre></div></div>
<p>Pastikan fingerprintnya <code class="language-plaintext highlighter-rouge">9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88</code> dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-key fingerprint 0EBFCD88
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pub 4096R/0EBFCD88 2017-02-22
Key fingerprint = 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88
uid Docker Release (CE deb) <docker@docker.com>
sub 4096R/F273FCD8 2017-02-22
</code></pre></div></div>
<p>Selanjutnya, tambahkan repository Docker</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
</code></pre></div></div>
<p>Baru kita bisa install</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get update && sudo apt-get install docker-ce -y
</code></pre></div></div>
<p>Kita bisa test instalasinya dengan melihat versi docker yang terinstal</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker --version
</code></pre></div></div>
<p>Test juga dengan menjalankan container</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo docker run hello-world
</code></pre></div></div>
<p><a name="instalasi-docker-machine"></a></p>
<h3 id="install-docker-machine">Install Docker Machine</h3>
<p>Docker Machine adalah aplikasi untuk membuat Docker Engine di VPS lain. Kita butuh ini karena kita menggunakan fitur <code class="language-plaintext highlighter-rouge">autoscale</code>. Pada saat ada build yang harus dikerjakan, Gitlab menggunakan <code class="language-plaintext highlighter-rouge">docker-machine</code> untuk membuat VPS dan menjalankan build dalam docker container di VPS tersebut. Setelah build selesai, Gitlab kembali akan menggunakan <code class="language-plaintext highlighter-rouge">docker-machine</code> untuk menghapus VPS.</p>
<p>Petunjuk instalasi <code class="language-plaintext highlighter-rouge">docker-machine</code> paling lengkap bisa dibaca di <a href="https://docs.docker.com/machine/install-machine/#installing-machine-directly">website resminya</a>. Pada intinya, kita jalankan script berikut di command line:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -L https://github.com/docker/machine/releases/download/v0.10.0/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine &&
chmod +x /tmp/docker-machine &&
sudo cp /tmp/docker-machine /usr/local/bin/docker-machine
</code></pre></div></div>
<p>Test dengan melihat versi yang terinstal</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine --version
</code></pre></div></div>
<p>Jangan lupa untuk memeriksa dulu ke website resminya untuk memastikan versi yang diinstal sudah yang paling baru.</p>
<p>Agar <code class="language-plaintext highlighter-rouge">docker-machine</code> bisa berjalan dalam Google Compute Engine, kita perlu inisialisasi otentikasi Google dulu. Kalau tidak, kita akan mendapati error seperti ini pada saat membuat <code class="language-plaintext highlighter-rouge">docker-machine</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine create --driver google --google-project gitlab-family --google-zone asia-southeast1-a testcoba
</code></pre></div></div>
<p>Pesan errornya seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Running pre-create checks...
(testcoba) Check that the project exists
Error with pre-create check: "Project with ID \"gitlab-family\" not found. googleapi: Error 403: Insufficient Permission, insufficientPermissions"
</code></pre></div></div>
<p>Untuk mengatasinya, kita harus otentikasi dulu. Caranya, kita login dulu dengan Google Cloud SDK. Yang penting untuk diperhatikan, proses otentikasi ini harus kita lakukan sebagai user <code class="language-plaintext highlighter-rouge">root</code>, karena nantinya proses <code class="language-plaintext highlighter-rouge">gitlab-runner</code> akan berjalan dengan user <code class="language-plaintext highlighter-rouge">root</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo -i
</code></pre></div></div>
<p>Kemudian baru kita lakukan otentikasi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud auth application-default login
</code></pre></div></div>
<p>Kita akan disuruh untuk login di browser dengan URL yang diberikan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You are running on a Google Compute Engine virtual machine.
The service credentials associated with this virtual machine
will automatically be used by Application Default
Credentials, so it is not necessary to use this command.
If you decide to proceed anyway, your user credentials may be visible
to others with access to this virtual machine. Are you sure you want
to authenticate with your personal account?
Do you want to continue (Y/n)?
Go to the following link in your browser:
https://accounts.google.com/o/oauth2/auth?redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&prompt=select_account&response_type=code&client_id=764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&access_type=offline
</code></pre></div></div>
<p>Setelah login di web, kita akan diberikan kode otorisasi</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/gcloud-authcode.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/gcloud-authcode.png" alt="Kode Otorisasi" /></a></p>
<p>Masukkan di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter verification code: 4/yyu66l80diOCGNgMEjqH3Jj6UEK3w_QeJ9G1hmdWNx4
Credentials saved to file: [/home/endymuhardin/.config/gcloud/application_default_credentials.json]
These credentials will be used by any library that requests
Application Default Credentials.
</code></pre></div></div>
<p>Selanjutnya, kita bisa test apakah <code class="language-plaintext highlighter-rouge">docker-machine</code> bisa dijalankan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine create --driver google --google-project gitlab-family --google-zone asia-southeast1-a testcoba
</code></pre></div></div>
<p>Kali ini harusnya sukses, berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Running pre-create checks...
(testcoba) Check that the project exists
(testcoba) Check if the instance already exists
Creating machine...
(testcoba) Generating SSH Key
(testcoba) Creating host...
(testcoba) Opening firewall ports
(testcoba) Creating instance
(testcoba) Waiting for Instance
(testcoba) Uploading SSH Key
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with ubuntu(systemd)...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env testcoba
</code></pre></div></div>
<p>Kita bisa lihat apakah docker-machine sudah terbentuk dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine ls
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
testcoba - google Running tcp://35.186.158.152:2376 v17.04.0-ce
</code></pre></div></div>
<p>Setelah sukses, jangan lupa dihapus lagi agar tidak membebani tagihan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine rm testcoba
</code></pre></div></div>
<p>Selanjutnya, kita teruskan menginstal <code class="language-plaintext highlighter-rouge">gitlab-ci-multi-runner</code>.</p>
<p><a name="instalasi-gitlab-multi-runner"></a></p>
<h3 id="install-gitlab-runner">Install Gitlab Runner</h3>
<p>Petunjuk instalasi bisa dibaca <a href="https://docs.gitlab.com/runner/install/linux-repository.html">di sini</a>. Sesuai petunjuk di link barusan, kita bisa eksekusi satu baris perintah seperti ini untuk mendaftarkan repository Ubuntu</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash
</code></pre></div></div>
<p>Selanjutnya kita akan install Gitlab Runner</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install gitlab-ci-multi-runner
</code></pre></div></div>
<p><a name="registrasi-gitlab-multi-runner"></a></p>
<h2 id="registrasi-gitlab-runner">Registrasi Gitlab Runner</h2>
<p>Untuk melakukan registrasi, kita butuh token yang bisa didapatkan di menu <code class="language-plaintext highlighter-rouge">admin/runners</code> di aplikasi web Gitlab CE.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/runner-registration-token.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/runner-registration-token.png" alt="Registration Token" /></a>
]</p>
<p>Kemudian kita daftarkan Runner dengan token tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo gitlab-ci-multi-runner register
</code></pre></div></div>
<p>Kita akan ditanyai beberapa pertanyaan. Berikut adalah pertanyaan dan jawaban yang saya berikan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Running in system-mode.
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://gitlab.artivisi.id
Please enter the gitlab-ci token for this runner:
A6pwrshn7xoeVPvMYAEh
Please enter the gitlab-ci description for this runner:
[gitlab-runner]:
Please enter the gitlab-ci tags for this runner (comma separated):
Whether to lock Runner to current project [true/false]:
[false]:
Registering runner... succeeded runner=A6pwrshn
Please enter the executor: shell, virtualbox, docker+machine, docker-ssh+machine, kubernetes, docker, docker-ssh, parallels, ssh:
docker+machine
Please enter the default Docker image (e.g. ruby:2.1):
alpine:latest
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
</code></pre></div></div>
<p>Kita akan edit file konfigurasinya. Sebelumnya kita backup dulu config aslinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo cp /etc/gitlab-runner/config.toml /etc/gitlab-runner/config.bak
</code></pre></div></div>
<p>Kemudian kita edit file <code class="language-plaintext highlighter-rouge">/etc/gitlab-runner/config.toml</code> seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>concurrent = 1
check_interval = 0
[[runners]]
name = "gcloud-autoscale"
url = "https://gitlab.artivisi.id"
token = "5b6e2b425a2ebb04ab10de5344ec52"
executor = "docker+machine"
[runners.docker]
tls_verify = false
image = "alpine:latest"
privileged = true
disable_cache = false
volumes = ["/cache"]
shm_size = 0
[runners.cache]
Type = "s3"
ServerAddress = "10.148.0.3:9005"
AccessKey = "6X7LFVH3V7HU7SHHF60U"
SecretKey = "8ftRtZDOSv/pXNiRe1yqKoQHCu5IRvsZfx35YijI"
BucketName = "runner"
Insecure = true
Shared = true
[runners.machine]
IdleCount = 0
IdleTime = 600
MaxBuilds = 5
MachineDriver = "google"
MachineName = "%s"
MachineOptions = [
"google-project=gitlab-family",
"google-zone=asia-southeast1-a",
"google-machine-image=coreos-cloud/global/images/coreos-stable-1298-7-0-v20170401",
"google-machine-type=g1-small",
"engine-registry-mirror=http://10.148.0.3:6000"
]
</code></pre></div></div>
<p>Ada beberapa hal yang kita konfigurasi pada runner kita, yaitu:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">IdleCount = 0</code> : kalau tidak ada kerjaan build, tidak perlu bikin VPS</li>
<li><code class="language-plaintext highlighter-rouge">IdleTime = 600</code> : kalau ada VPS yang nganggur selama 600 detik (10 menit), segera remove</li>
<li><code class="language-plaintext highlighter-rouge">MaxBuilds = 5</code> : maksimal kerjaan yang jalan berbarengan adalah <code class="language-plaintext highlighter-rouge">5</code>. Kalau ada 7 permintaan, maka yang 2 antri dulu. Setting ini bisa kita gunakan untuk membatasi jumlah VPS yang akan dibuat oleh Gitlab sehingga tagihan tidak membengkak.</li>
<li><code class="language-plaintext highlighter-rouge">MachineDriver = "google"</code> : provider VPS yang kita gunakan. Kita bisa menggunakan <a href="https://docs.docker.com/machine/drivers/">berbagai provider yang didukung oleh <code class="language-plaintext highlighter-rouge">docker-machine</code></a></li>
<li><code class="language-plaintext highlighter-rouge">engine-registry-mirror</code> : lokasi registry mirror yang sudah kita install tadi.</li>
<li><code class="language-plaintext highlighter-rouge">runners.cache</code> : lokasi penyimpanan cache. Arahkan ke konfigurasi <code class="language-plaintext highlighter-rouge">minio</code> yang sudah kita buat tadi.</li>
</ul>
<p>Setelah diedit, restart Gitlab Runner</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo gitlab-ci-multi-runner restart
</code></pre></div></div>
<p>Kita bisa lihat hasilnya di antarmuka web Gitlab CE di menu <code class="language-plaintext highlighter-rouge">Admin > Runners</code></p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/runner-registered.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/runner-registered.png" alt="Runner Sukses Registrasi" /></a></p>
<p><a name="test-gitlab-runner"></a></p>
<h2 id="test-gitlab-runner">Test Gitlab Runner</h2>
<p>Ada beberapa hal yang perlu kita test, yaitu:</p>
<ul>
<li>build bisa dijalankan dengan baik</li>
<li>docker registry proxy bisa melakukan cache, sehingga build kedua dan seterusnya lebih cepat</li>
<li>build cache berfungsi dengan baik, ada file yang ditaruh ke <code class="language-plaintext highlighter-rouge">minio</code> dan build selanjutnya tidak perlu download dependensi</li>
</ul>
<p>Untuk melakukan test, kita bisa gunakan <a href="https://gitlab.com/endymuhardin/belajar-ci">contoh project <code class="language-plaintext highlighter-rouge">belajar-ci</code></a> yang sudah diuji dan <a href="https://gitlab.com/endymuhardin/belajar-ci/pipelines">berjalan baik di Gitlab versi hosted</a> yang dikelola tim Gitlab sendiri. Kita bisa create project di Gitlab hasil instalasi kita, kemudian push projectnya kesana.</p>
<p>Beberapa hal yang perlu kita amati:</p>
<ul>
<li>Build harusnya berjalan sukses, minimal job <code class="language-plaintext highlighter-rouge">maven-build</code>. Untuk job lain memang butuh konfigurasi credential agar bisa push ke DockerHub.</li>
<li>Build cache terisi, bisa dicek dengan cara ssh ke <code class="language-plaintext highlighter-rouge">docker-registry-cache</code>, kemudian lihat isi folder <code class="language-plaintext highlighter-rouge">/export/runner</code>. Harusnya ada isinya. Lihat juga log <code class="language-plaintext highlighter-rouge">maven-build</code> untuk build kedua dan seterusnya, harusnya dia tidak lagi mengunduh banyak <code class="language-plaintext highlighter-rouge">jar</code>. Di log <code class="language-plaintext highlighter-rouge">maven-build</code> seharusnya ada tulisan <code class="language-plaintext highlighter-rouge">Checking cache for default...</code> dan <code class="language-plaintext highlighter-rouge">Successfully extracted cache</code></li>
<li>Registry cache terisi. Bisa dicek dengan cara login ke docker containernya dengan perintah <code class="language-plaintext highlighter-rouge">docker exec -it namacontainer /bin/sh</code>. Nama container dapat diperoleh dengan perintah <code class="language-plaintext highlighter-rouge">docker ps</code>. Setelah berhasil login, coba liat isi folder <code class="language-plaintext highlighter-rouge">/var/lib/registry/</code>. Seharusnya di dalamnya ada folder <code class="language-plaintext highlighter-rouge">docker/registry/v2/repositories/library/</code> yang isinya image-image yang digunakan oleh proses build kita. Untuk project <code class="language-plaintext highlighter-rouge">belajar-ci</code> akan ada image <code class="language-plaintext highlighter-rouge">docker</code>, <code class="language-plaintext highlighter-rouge">mysql</code>, dan <code class="language-plaintext highlighter-rouge">maven</code>.</li>
</ul>
<p>Demikianlah cara setup Gitlab Runner. Sekarang server Gitlab kita selain bisa menjadi server version control Git, juga sudah bisa menangani proses Continuous Integration/Delivery. Dengan adanya fitur <code class="language-plaintext highlighter-rouge">autoscale</code> ini, VPS hanya dibuat pada saat dibutuhkan saja. Begitu tidak ada proses berjalan, VPSnya akan didestroy. Ini akan sangat menghemat biaya, apalagi layanan cloud jaman sekarang sudah mengenakan biaya dalam satuan jam. Bukan lagi bulanan.</p>
<p>Semoga bermanfaat.</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="http://www.akitaonrails.com/2016/08/03/moving-to-gitlab-yes-it-s-worth-it">http://www.akitaonrails.com/2016/08/03/moving-to-gitlab-yes-it-s-worth-it</a></li>
<li><a href="https://docs.gitlab.com/runner/install/autoscaling.html">https://docs.gitlab.com/runner/install/autoscaling.html</a></li>
<li><a href="https://github.com/jerryjj/gitlab-runner-gce">https://github.com/jerryjj/gitlab-runner-gce</a></li>
<li><a href="https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Maven.gitlab-ci.yml">https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Maven.gitlab-ci.yml</a></li>
</ul>
Instalasi Gitlab CE di Google Cloud Engine2017-04-20T07:00:00+07:00https://software.endy.muhardin.com/devops/instalasi-gitlab-gce<p>Jaman sekarang, version control system (VCS) sudah merupakan keharusan dalam proses pembuatan aplikasi. Tanpa VCS, kita tidak bisa bekerja dalam tim dengan baik. Bahkan kerja sendiri saja sulit untuk menerapkan release management yang baik tanpa VCS. Lagipula jaman sekarang VCS gratis. Kita tinggal daftar di <a href="https://gitlab.com">Gitlab</a> dan bikin project sebanyak-banyaknya dan mengundang kolaborator se-Indonesia Raya tanpa dikenakan biaya sepeser pun.</p>
<p>Walaupun demikian, buat banyak perusahaan tetap merasa lebih nyaman kalau source codenya tidak dikelola pihak lain. Tidak masalah, karena Gitlab juga menyediakan aplikasinya untuk kita install di server sendiri, atau istilah kerennya <code class="language-plaintext highlighter-rouge">on premise</code>. Mari kita coba setup Gitlab Community Edition di Google Cloud Platform. Bahkan Gitlab sudah menyediakan fitur Continuous Integration yang sangat komprehensif.</p>
<p>Dalam rangkaian artikel ini, kita akan memasang Gitlab lengkap dengan Gitlab CI, Runner, lengkap dengan build cache dan docker registry sendiri seperti pada diagram berikut</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/gitlab-sekeluarga.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/gitlab-sekeluarga.png" alt="Diagram Instalasi Gitlab Sekeluarga" /></a></p>
<!--more-->
<p>Pada artikel bagian pertama ini, kita akan menginstal Gitlab Community Edition yang berisi server untuk Git. Setup continuous integration akan kita bahas pada artikel berikutnya.</p>
<p>Ada beberapa langkah yang harus kita lakukan:</p>
<ul>
<li><a href="#membuat-project">Membuat Project di Google Cloud Platform</a></li>
<li><a href="#membuat-server">Membuat server dengan Google Compute Engine (GCE)</a></li>
<li><a href="#custom-domain">Mendaftarkan IP public dengan nama domain</a></li>
<li><a href="#instalasi-gitlab">Menginstal Gitlab</a></li>
<li><a href="#sertifikat-ssl">Membuat sertifikat SSL</a></li>
</ul>
<p><a name="membuat-project"></a></p>
<h2 id="membuat-project-di-gcp">Membuat Project di GCP</h2>
<p>Langkah pertama kita adalah membuat project baru. Gitlab ini nantinya akan terdiri dari banyak node untuk Git server, Runner server, Executor, build cache, dan docker registry. Karena terdiri dari banyak node, maka sebaiknya kita install di project tersendiri.</p>
<p>Untuk bisa membuat project, kita harus sudah <a href="https://cloud.google.com/">mendaftar di Google Cloud Platform</a> dan <a href="https://cloud.google.com/sdk/">menginstal Google Cloud SDK</a>. Kita tidak akan membahas pendaftaran dan instalasinya di sini.</p>
<p>Setelah mendaftar, lakukan login dengan perintah command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud auth login
</code></pre></div></div>
<p>Setelah itu, buat project baru</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud projects create gitlab-family
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Create in progress for [https://cloudresourcemanager.googleapis.com/v1/projects/gitlab-family].
Waiting for [operations/pc.6361857332348314590] to finish...done.
</code></pre></div></div>
<p>Kemudian pasang konfigurasi default untuk mengurangi ketikan di perintah selanjutnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud config set core/project gitlab-family
gcloud config set compute/region asia-southeast1
gcloud config set compute/zone asia-southeast1-a
</code></pre></div></div>
<p>Kita perlu mengaktifkan dulu Google Compute API di project kita supaya bisa membuat VPS. Kalau tidak diaktifkan, kita akan mendapat pesan error seperti ini pada saat akan membuat VPS.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: (gcloud.compute.instances.create) Could not fetch resource:
- Access Not Configured. Compute Engine API has not been used in project 964256271847 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/compute_component/overview?project=964256271847 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
</code></pre></div></div>
<p>Agar bisa mengaktifkan Compute API, kita harus mengijinkan Google menagih ke kartu kredit kita (<code class="language-plaintext highlighter-rouge">Enable Billing</code>). Kita bisa jalankan proses ini lewat web seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/enable-gcp-billing.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/enable-gcp-billing.png" alt="Enable Billing" /></a></p>
<p>atau lewat command line dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud alpha billing accounts projects link gitlab-family --account-id=003X89-ABC977-123XYZ
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>billingAccountName: billingAccounts/003X89-ABC977-123XYZ
billingEnabled: true
name: projects/gitlab-family/billingInfo
projectId: gitlab-family
</code></pre></div></div>
<p>Nilai <code class="language-plaintext highlighter-rouge">account-id</code> bisa didapatkan dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud alpha billing accounts list
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ID NAME OPEN
003X89-ABC977-123XYZ My Billing Account True
</code></pre></div></div>
<p>Selanjutnya, kita bisa mengaktifkan Compute Engine API. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud service-management enable compute-component.googleapis.com
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Waiting for async operation operations/projectSettings.6c411ce2-6e7e-490a-9fd4-8826d89b3b31 to complete...
Operation finished successfully. The following command can describe the Operation details:
gcloud service-management operations describe operations/projectSettings.6c411ce2-6e7e-490a-9fd4-8826d89b3b31
</code></pre></div></div>
<p>Barulah kita bisa lanjut ke tahap selanjutnya, membuat VPS.</p>
<p><a name="membuat-server"></a></p>
<h2 id="membuat-server-di-gce">Membuat Server di GCE</h2>
<p>Berikutnya, kita buat satu VPS untuk menampung aplikasi Gitlab CE. Perintahnya adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances create gitlab-ce --image-family ubuntu-1604-lts --image-project ubuntu-os-cloud
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Created [https://www.googleapis.com/compute/v1/projects/gitlab-family/zones/asia-southeast1-a/instances/gitlab-ce].
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
gitlab-ce asia-southeast1-a n1-standard-1 10.148.0.2 35.185.188.37 RUNNING
</code></pre></div></div>
<p>Secara default, kita akan dibuatkan VPS dengan spesifikasi <code class="language-plaintext highlighter-rouge">n1-standard-1</code>, yaitu 1 CPU dan 3.75GB memori. Untuk mengetahui jenis dan spesifikasi VPS, silahkan lihat <a href="https://cloud.google.com/compute/docs/machine-types">dokumentasinya</a>.</p>
<p>Kita bisa ssh ke mesin tersebut dengan menggunakan Google Cloud SDK</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute ssh gitlab-ce
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Updating project ssh metadata.../Updated [https://www.googleapis.com/compute/v1/projects/gitlab-family].
Updating project ssh metadata...done.
Warning: Permanently added 'compute.3943642946285722488' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.8.0-45-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
0 packages can be updated.
0 updates are security updates.
endymuhardin@gitlab-ce:~$
</code></pre></div></div>
<p>Nah kita sudah berada di dalam VPS tersebut.</p>
<p><a name="custom-domain"></a></p>
<h2 id="mendaftarkan-nama-domain">Mendaftarkan Nama Domain</h2>
<p>Supaya lebih bonafit, kita berikan nama domain ke server kita ini dengan cara mendaftarkan alamat IPnya ke DNS server. Tapi sebelum dikonfigurasi di DNS server, kita booking dulu alamat IPnya supaya tidak berubah-ubah. Soalnya secara default VPS kita tadi memiliki IP public yang <code class="language-plaintext highlighter-rouge">ephemeral</code>, yaitu bisa berubah kapan saja.</p>
<p>Kita booking dulu alamat IP yang static</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute addresses create gitlab-ip
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>For the following address name:
- [gitlab-ip]
choose a region or global:
[1] global
[2] region: asia-east1
[3] region: asia-northeast1
[4] region: asia-southeast1
[5] region: europe-west1
[6] region: us-central1
[7] region: us-east1
[8] region: us-west1
Please enter your numeric choice: 1
Created [https://www.googleapis.com/compute/v1/projects/gitlab-family/global/addresses/gitlab-ip].
---
address: 35.185.188.31
creationTimestamp: '2017-04-20T00:58:23.717-07:00'
description: ''
id: '6502175881003652336'
kind: compute#address
name: gitlab-ip
selfLink: https://www.googleapis.com/compute/v1/projects/gitlab-family/global/addresses/gitlab-ip
status: RESERVED
</code></pre></div></div>
<p>Agar bisa dipasang, yang lama harus dihapus dulu. Mari kita lihat konfigurasi network yang sekarang aktif</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances describe gitlab-ce
</code></pre></div></div>
<p>Hasilnya panjang, kita potong saja bagian yang relevan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>networkInterfaces:
- accessConfigs:
- kind: compute#accessConfig
name: external-nat
natIP: 35.185.188.37
type: ONE_TO_ONE_NAT
</code></pre></div></div>
<p>Kemudian, kita hapus dulu <code class="language-plaintext highlighter-rouge">accessConfig</code> tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances delete-access-config gitlab-ce --access-config-name external-nat
</code></pre></div></div>
<p>Hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Updated [https://www.googleapis.com/compute/v1/projects/gitlab-family/zones/asia-southeast1-a/instances/gitlab-ce].
</code></pre></div></div>
<p>Baru kita pasang IP address yang baru</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances add-access-config gitlab-ce --access-config-name external-nat --address 35.185.188.31
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Updated [https://www.googleapis.com/compute/v1/projects/gitlab-family/zones/asia-southeast1-a/instances/gitlab-ce]
</code></pre></div></div>
<p>Selanjutnya, kita daftarkan alamat IP tersebut ke DNS server kita</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gitlab.artivisi.id. IN A 35.185.188.31
</code></pre></div></div>
<p>Kemudian test ping untuk memastikan setting DNSnya benar</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ping gitlab.artivisi.id
PING gitlab.artivisi.id (35.185.188.31): 56 data bytes
64 bytes from 35.185.188.31: icmp_seq=0 ttl=57 time=137.929 ms
64 bytes from 35.185.188.31: icmp_seq=1 ttl=57 time=131.184 ms
64 bytes from 35.185.188.31: icmp_seq=2 ttl=57 time=131.666 ms
Request timeout for icmp_seq 3
^C
--- gitlab.artivisi.id ping statistics ---
5 packets transmitted, 3 packets received, 40.0% packet loss
round-trip min/avg/max/stddev = 131.184/133.593/137.929/3.072 ms
</code></pre></div></div>
<p>Selanjutnya, kita instal aplikasi Gitlab CE.</p>
<p><a name="instalasi-gitlab"></a></p>
<h2 id="instalasi-gitlab">Instalasi Gitlab</h2>
<p>Login dulu ke servernya melalui SSH</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute ssh gitlab-ce
</code></pre></div></div>
<p>Lalu ikuti <a href="https://about.gitlab.com/downloads/#ubuntu1604">panduan di website Gitlab</a>. Kita tidak akan ulangi di sini karena cukup mudah diikuti.</p>
<p>Setelah selesai, jangan diakses dulu lewat browser, karena kita akan diminta memasukkan root password. Setup dulu SSLnya supaya password root kita bisa dientri dengan aman.</p>
<p><a name="sertifikat-ssl"></a></p>
<h2 id="membuat-sertifikat-ssl">Membuat Sertifikat SSL</h2>
<p>Login dulu melalui SSH ke VPS kita</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute ssh gitlab-ce
</code></pre></div></div>
<p>Kemudian, install Lets Encrypt</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install letsencrypt -y
</code></pre></div></div>
<p>Buatkan folder agar letsencrypt bisa melakukan verifikasi nama domain.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkdir -p /var/www/letsencrypt
</code></pre></div></div>
<p>Kemudian konfigurasi Gitlab supaya membolehkan folder tersebut diakses dari luar. Tambahkan baris berikut di <code class="language-plaintext highlighter-rouge">/etc/gitlab/gitlab.rb</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nginx['custom_gitlab_server_config'] = "location ^~ /.well-known { root /var/www/letsencrypt; }"
</code></pre></div></div>
<p>Lalu restart Gitlab</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo gitlab-ctl reconfigure
</code></pre></div></div>
<p>Kita juga perlu membuka akses ke port <code class="language-plaintext highlighter-rouge">80</code> dan <code class="language-plaintext highlighter-rouge">443</code> yang biasa digunakan webserver.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute firewall-rules create allow-http --allow tcp:80
gcloud compute firewall-rules create allow-https --allow tcp:443
</code></pre></div></div>
<p>Sekarang kita bisa menyuruh Lets Encrypt untuk melakukan validasi domain</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo letsencrypt certonly -a webroot -w /var/www/letsencrypt -d gitlab.artivisi.id
</code></pre></div></div>
<p>Kita akan dimintai alamat email untuk keperluan recovery. Masukkan alamat email kita. Lalu ikut langkah-langkahnya sampai selesai.</p>
<p>Supaya konfigurasi SSL kita lebih baik, tambahkan parameter Diffie Hellman</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo openssl dhparam -out /etc/gitlab/ssl/dhparams.pem 4096
</code></pre></div></div>
<p>Outputnya seperti ini, kita harus bersabar menunggu dia selesai</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Generating DH parameters, 4096 bit long safe prime, generator 2
This is going to take a long time
.................................................+............................................+.......+.........................................................................+.........................................................................................................................................................................++*++*
</code></pre></div></div>
<p>Setelah selesai, kita akan mengkonfigurasikan Gitlab agar membaca file sertifikat dan private key. Edit lagi <code class="language-plaintext highlighter-rouge">/etc/gitlab/gitlab.rb</code> dan masukkan baris berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>external_url 'https://gitlab.artivisi.id'
nginx['redirect_http_to_https'] = true
nginx['ssl_certificate'] = "/etc/letsencrypt/live/gitlab.artivisi.id/fullchain.pem"
nginx['ssl_certificate_key'] = "/etc/letsencrypt/live/gitlab.artivisi.id/privkey.pem"
nginx['ssl_dhparam'] = "/etc/gitlab/ssl/dhparams.pem"
</code></pre></div></div>
<p>Lalu restart Gitlab sekali lagi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo gitlab-ctl reconfigure
</code></pre></div></div>
<p>SSL kita sudah terpasang dengan baik. Kita bisa cek di <a href="https://www.ssllabs.com/ssltest/">situs pemeriksaan SSL</a> untuk melihat berapa nilai konfigurasi kita.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/hasil-ssl-test.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/hasil-ssl-test.png" alt="Hasil SSL Test" /></a></p>
<p>Jangan lupa untuk memasang script auto-renewal. Masukkan baris berikut di crontab.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 */12 * * * sleep $((RANDOM*3600/32768)) && /usr/bin/letsencrypt renew >> /var/log/le-renew.log
5 */12 * * * /usr/bin/gitlab-ctl restart nginx
</code></pre></div></div>
<p>Sekarang instalasi Gitlab kita sudah aman. Kita bisa coba akses ke <code class="language-plaintext highlighter-rouge">https://gitlab.artivisi.id</code> dan menyelesaikan setup password</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/setup-password.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/setup-password.png" alt="Setup Password" /></a></p>
<p>Setelah itu, kita bisa login dan mulai berkarya :D</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/setelah-login.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-family/setelah-login.png" alt="Setelah Login" /></a></p>
<h2 id="penutup">Penutup</h2>
<p>Server Gitlab CE kita sudah beroperasi. Sekarang kita bisa membuat user, project, dan mulai menggunakan Git untuk version control. Pada artikel selanjutnya, kita akan <a href="http://software.endy.muhardin.com/devops/instalasi-gitlab-runner-autoscale/">mengkonfigurasikan Gitlab Runner</a> supaya proses Continuous Delivery kita bisa dijalankan. Stay tuned …</p>
<p>Semoga bermanfaat</p>
Membuat Aplikasi yang Cluster Ready2017-04-18T07:00:00+07:00https://software.endy.muhardin.com/java/cluster-ready-app<p>Beberapa tahun yang lalu, kita sudah membahas tentang <a href="http://software.endy.muhardin.com/aplikasi/konsep-clustering/">konsep clustering aplikasi</a>. Ada beberapa konsep dan istilah penting di sana seperti:</p>
<ul>
<li>Dua jenis aplikasi failover</li>
<li>Session Affinity atau Sticky/Non-Sticky Session</li>
</ul>
<p>Kita tidak akan bahas lagi tentang istilah tersebut, silahkan baca artikel terdahulu untuk memahaminya. Pada artikel kali ini, kita akan membahas tentang implementasi konsep tersebut dalam aplikasi kita, sehingga diharapkan pembaca sudah paham artinya.</p>
<p>Memasang aplikasi yang sama di beberapa mesin (baik fisik ataupun virtual) disebut dengan istilah clustering atau replication. Kita akan menggunakan istilah replikasi pada artikel ini.</p>
<p>Contoh kode program dibuat menggunakan Java. Akan tetapi, prinsip-prinsipnya bisa juga diterapkan pada aplikasi yang menggunakan bahasa pemrograman lain.</p>
<!--more-->
<p>Clustering bukan sesuatu yang sederhana. Baik secara konfigurasi, pembuatan aplikasi, dan secara biaya. Bandingkan deployment kita yang biasanya seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/clustering-aplikasi/tanpa-clustering.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2017/clustering-aplikasi/tanpa-clustering.jpg" alt="Tanpa Clustering" /></a></p>
<p>Menjadi seperti ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/clustering-aplikasi/dengan-clustering.jpg"><img src="https://software.endy.muhardin.com/images/uploads/2017/clustering-aplikasi/dengan-clustering.jpg" alt="Dengan Clustering" /></a></p>
<p>Umumnya, orang melakukan replikasi atau clustering karena dua alasan:</p>
<ul>
<li>Failover : bila satu server mati, ada server cadangan yang siap mengambil alih</li>
<li>Scalability : bila satu server sudah tidak kuat menangani request yang banyak, upgrade hardware (scale up) sudah mentok, maka kita bisa menambah server (scale out)</li>
</ul>
<p>Buat kita programmer, tidak masalah apapun alasannya. Tetap saja kita harus melakukan antisipasi agar aplikasi yang kita buat bisa dicluster dengan baik. Secara garis besar, ada dua hal yang harus kita perhatikan:</p>
<ul>
<li>data di memory : misalnya instance variable, session variable, dan static variable</li>
<li>data di disk : misalnya file hasil upload, file log, file report hasil generate, file temporary, dan sebagainya</li>
</ul>
<p>Pada saat dilakukan replikasi, akan ada banyak instance aplikasi yang berjalan di banyak mesin. Request yang datang bisa diarahkan ke mesin mana saja. Request pertama untuk user tertentu (misalnya pilih barang), belum tentu diarahkan ke mesin yang sama dengan request selanjutnya (misalnya bayar). Dengan demikian, kita tidak bisa menyimpan data pilihan barang di request pertama dalam memori mesin pertama. Karena pada request berikutnya, bisa jadi ditangani oleh mesin lain yang berbeda. Bila datanya disimpan di memori mesin pertama, maka pada waktu request bayar ditangani mesin kedua, dia tidak punya data pilihan produknya.</p>
<p>Demikian juga dengan penyimpanan data di disk. Bila request upload file dihandle dengan cara menulis langsung ke local disk, request berikutnya bisa jadi ditangani mesin lain yang tidak memiliki file tersebut di local disknya.</p>
<p>Seperti kita lihat pada gambar di atas, kita punya enam instance aplikasi web. Berarti ada kemungkinan satu banding enam request kedua akan ditangani server yang sama dengan request pertama. Oleh karena itu, aplikasi web kita harus menyimpan data (baik memori maupun disk) di tempat yang bisa diakses keenam instance tersebut.</p>
<h2 id="data-di-memory">Data di Memory</h2>
<p>Untuk masalah data di memory, solusi yang biasa dipakai adalah memindahkan penyimpanan data ke database server. Bisa menggunakan database relasional seperti MySQL atau PostgreSQL, bisa juga menggunakan database NoSQL seperti memcached, redis, atau lainnya.</p>
<p>Bila kita menggunakan Spring Framework, memindahkan session variables ke database server bisa dilakukan dengan mudah. Caranya :</p>
<ul>
<li>tambahkan dependensi <code class="language-plaintext highlighter-rouge">spring-session-jdbc</code></li>
<li>tambahkan anotasi <code class="language-plaintext highlighter-rouge">@EnableJdbcHttpSession</code></li>
<li>buat skema tabel buat menyimpan data session</li>
</ul>
<p>Berikut dependensi di <code class="language-plaintext highlighter-rouge">pom.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.session<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-session-jdbc<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Anotasi <code class="language-plaintext highlighter-rouge">@EnableJdbcHttpSession</code> bisa kita pasang di main class</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@SpringBootApplication</span>
<span class="nd">@EnableJdbcHttpSession</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BelajarCiApplication</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">BelajarCiApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Bila kita menggunakan MySQL, skema databasenya sebagai berikut</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">SPRING_SESSION</span> <span class="p">(</span>
<span class="n">SESSION_ID</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">CREATION_TIME</span> <span class="nb">BIGINT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">LAST_ACCESS_TIME</span> <span class="nb">BIGINT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">MAX_INACTIVE_INTERVAL</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">PRINCIPAL_NAME</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">),</span>
<span class="k">CONSTRAINT</span> <span class="n">SPRING_SESSION_PK</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">SESSION_ID</span><span class="p">)</span>
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">SPRING_SESSION_IX1</span> <span class="k">ON</span> <span class="n">SPRING_SESSION</span> <span class="p">(</span><span class="n">LAST_ACCESS_TIME</span><span class="p">);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">SPRING_SESSION_ATTRIBUTES</span> <span class="p">(</span>
<span class="n">SESSION_ID</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">ATTRIBUTE_NAME</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">200</span><span class="p">),</span>
<span class="n">ATTRIBUTE_BYTES</span> <span class="nb">BLOB</span><span class="p">,</span>
<span class="k">CONSTRAINT</span> <span class="n">SPRING_SESSION_ATTRIBUTES_PK</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">SESSION_ID</span><span class="p">,</span> <span class="n">ATTRIBUTE_NAME</span><span class="p">),</span>
<span class="k">CONSTRAINT</span> <span class="n">SPRING_SESSION_ATTRIBUTES_FK</span> <span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">SESSION_ID</span><span class="p">)</span> <span class="k">REFERENCES</span> <span class="n">SPRING_SESSION</span><span class="p">(</span><span class="n">SESSION_ID</span><span class="p">)</span> <span class="k">ON</span> <span class="k">DELETE</span> <span class="k">CASCADE</span>
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">SPRING_SESSION_ATTRIBUTES_IX1</span> <span class="k">ON</span> <span class="n">SPRING_SESSION_ATTRIBUTES</span> <span class="p">(</span><span class="n">SESSION_ID</span><span class="p">);</span>
</code></pre></div></div>
<p>Sedangkan untuk PostgreSQL, skemanya seperti ini</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">SPRING_SESSION</span> <span class="p">(</span>
<span class="n">SESSION_ID</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">CREATION_TIME</span> <span class="nb">BIGINT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">LAST_ACCESS_TIME</span> <span class="nb">BIGINT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">MAX_INACTIVE_INTERVAL</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">PRINCIPAL_NAME</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">),</span>
<span class="k">CONSTRAINT</span> <span class="n">SPRING_SESSION_PK</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">SESSION_ID</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">SPRING_SESSION_IX1</span> <span class="k">ON</span> <span class="n">SPRING_SESSION</span> <span class="p">(</span><span class="n">LAST_ACCESS_TIME</span><span class="p">);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">SPRING_SESSION_ATTRIBUTES</span> <span class="p">(</span>
<span class="n">SESSION_ID</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">36</span><span class="p">),</span>
<span class="n">ATTRIBUTE_NAME</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">200</span><span class="p">),</span>
<span class="n">ATTRIBUTE_BYTES</span> <span class="n">BYTEA</span><span class="p">,</span>
<span class="k">CONSTRAINT</span> <span class="n">SPRING_SESSION_ATTRIBUTES_PK</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">SESSION_ID</span><span class="p">,</span> <span class="n">ATTRIBUTE_NAME</span><span class="p">),</span>
<span class="k">CONSTRAINT</span> <span class="n">SPRING_SESSION_ATTRIBUTES_FK</span> <span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">SESSION_ID</span><span class="p">)</span> <span class="k">REFERENCES</span> <span class="n">SPRING_SESSION</span><span class="p">(</span><span class="n">SESSION_ID</span><span class="p">)</span> <span class="k">ON</span> <span class="k">DELETE</span> <span class="k">CASCADE</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">SPRING_SESSION_ATTRIBUTES_IX1</span> <span class="k">ON</span> <span class="n">SPRING_SESSION_ATTRIBUTES</span> <span class="p">(</span><span class="n">SESSION_ID</span><span class="p">);</span>
</code></pre></div></div>
<p>Selain menambah dependensi, anotasi <code class="language-plaintext highlighter-rouge">@EnableJdbcHttpSession</code>, dan menambah tabel di database, tidak ada perubahan lain yang perlu kita lakukan di kode program kita.</p>
<h2 id="data-di-disk">Data di Disk</h2>
<p>Agar aplikasi web kita bisa direplikasi, pada prinsipnya kita harus sediakan tempat penyimpanan yang bisa diakses semua instance. Ada beberapa alternatif di sini:</p>
<ul>
<li>menggunakan shared filesystem</li>
<li>menggunakan storage service</li>
</ul>
<p>Solusi shared filesystem bisa diimplementasikan dengan berbagai produk, diantaranya:</p>
<ul>
<li>GlusterFS</li>
<li>Ceph</li>
<li>NFS</li>
<li>OCFS2</li>
</ul>
<p>Untuk lebih detail mengenai implementasinya bisa dibaca <a href="https://www.safaribooksonline.com/library/view/high-performance-drupal/9781449358013/ch10.html">di sini</a>.</p>
<p>Solusi filesystem ini tidak mengharuskan kita mengubah kode program. Aplikasi kita melihat shared filesystem ini sebagai folder biasa dalam sistem operasi. Kita bisa baca tulis file seperti biasa. Nanti filesystemnya yang akan mengurus replikasinya ke berbagai node. Kita hanya perlu melakukan <code class="language-plaintext highlighter-rouge">mounting</code> shared folder tersebut ke folder di masing-masing node.</p>
<p>Selain solusi shared filesystem, kita juga bisa menggunakan aplikasi terpisah yang bertugas menjadi file server. Setiap mau menulis file, kita upload filenya ke file server. Demikian juga kalau kita ingin melihat daftar file atau mengambil isi file, kita bisa mengakses file server tersebut. Protokol yang umum digunakan jaman sekarang adalah HTTP.</p>
<p>Kita bisa instal sendiri file servernya, atau kita bisa gunakan layanan cloud yang sekarang banyak tersedia di internet. Ada Amazon S3, Dropbox, Google Cloud Storage, dan lainnya. Sebagai contoh, berikut implementasi menulis file ke layanan Google Cloud Storage</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">BUCKET_NAME</span> <span class="o">=</span> <span class="s">"belajar-ci"</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Storage</span> <span class="n">storage</span> <span class="o">=</span> <span class="nc">StorageOptions</span><span class="o">.</span><span class="na">getDefaultInstance</span><span class="o">().</span><span class="na">getService</span><span class="o">();</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">String</span> <span class="n">nama</span><span class="o">,</span> <span class="nc">InputStream</span> <span class="n">contentStream</span><span class="o">)</span> <span class="o">{</span>
<span class="n">storage</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="nc">BlobInfo</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">(</span><span class="no">BUCKET_NAME</span><span class="o">,</span> <span class="n">nama</span><span class="o">).</span><span class="na">build</span><span class="o">(),</span>
<span class="n">contentStream</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Untuk melihat daftar file, berikut kode programnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">>></span> <span class="nf">daftarFile</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">>></span> <span class="n">hasil</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="nc">Page</span><span class="o"><</span><span class="nc">Blob</span><span class="o">></span> <span class="n">daftarFile</span> <span class="o">=</span> <span class="n">storage</span><span class="o">.</span><span class="na">list</span><span class="o">(</span><span class="no">BUCKET_NAME</span><span class="o">,</span> <span class="nc">Storage</span><span class="o">.</span><span class="na">BlobListOption</span><span class="o">.</span><span class="na">currentDirectory</span><span class="o">());</span>
<span class="n">daftarFile</span><span class="o">.</span><span class="na">getValues</span><span class="o">().</span><span class="na">forEach</span><span class="o">(</span> <span class="n">blob</span> <span class="o">-></span> <span class="o">{</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">fileInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TreeMap</span><span class="o"><>();</span>
<span class="n">fileInfo</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"nama"</span><span class="o">,</span> <span class="n">blob</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="n">fileInfo</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"ukuran"</span><span class="o">,</span> <span class="n">blob</span><span class="o">.</span><span class="na">getSize</span><span class="o">());</span>
<span class="n">hasil</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">fileInfo</span><span class="o">);</span>
<span class="o">});</span>
<span class="k">return</span> <span class="n">hasil</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dan untuk mengambil isi file, kode programnya seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">InputStream</span> <span class="nf">ambil</span><span class="o">(</span><span class="nc">String</span> <span class="n">nama</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ByteArrayInputStream</span><span class="o">(</span>
<span class="n">storage</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(</span><span class="nc">BlobId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="no">BUCKET_NAME</span><span class="o">,</span> <span class="n">nama</span><span class="o">)));</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Jangan lupa untuk memasang dependensi di <code class="language-plaintext highlighter-rouge">pom.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.google.cloud<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>google-cloud-storage<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.11.0-beta<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Agar aplikasi kita bisa berjalan dengan baik, kita perlu membuatkan dulu bucketnya di Google Cloud Storage. Kita membutuhkan aplikasi command line <code class="language-plaintext highlighter-rouge">gsutil</code> yang ada dalam paket Google Cloud SDK. Cara setupnya sudah kita bahas pada <a href="http://software.endy.muhardin.com/devops/deploy-google-container-engine/">artikel terdahulu</a>.</p>
<p>Pertama, kita buat dulu bucketnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gsutil mb gs://belajar-ci
</code></pre></div></div>
<p>Setelah itu, kita bisa melihat bucket apa saja yang sudah ada dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gsutil ls
</code></pre></div></div>
<p>Kita akan mendapatkan bucket kita sudah terdaftar</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gs://belajar-ci/
</code></pre></div></div>
<h2 id="menjalankan-replikasi">Menjalankan Replikasi</h2>
<p>Dulu, membuat instalasi clustering dan failover seperti gambar di atas relatif sulit. Kita harus instal semua node satu persatu, memasang alamat IP, mengatur subnet, menghubungkan antar node dengan <code class="language-plaintext highlighter-rouge">keepalived</code> atau <code class="language-plaintext highlighter-rouge">heartbeat</code>, konfigurasi load balancer dengan <code class="language-plaintext highlighter-rouge">HAProxy</code> atau <code class="language-plaintext highlighter-rouge">Nginx</code>, dan banyak urusan lainnya. Sekarang, dengan teknologi cloud, mau replikasi berapa node pun bisa dilakukan dengan satu baris perintah.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/clustering-aplikasi/kagebunshin.jpg" alt="Shadow Clone Jutsu" /></p>
<h3 id="kubernetes">Kubernetes</h3>
<p>Berikut perintahnya bila kita deploy ke Kubernetes. Pertama kita lihat dulu kondisi awalnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl get all -l app=belajar-ci
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME READY STATUS RESTARTS AGE
po/belajar-ci-app-4282971413-j4x55 1/1 Running 1 8m
po/belajar-ci-db-588835421-xh32q 1/1 Running 0 8m
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/belajar-ci-app-service 10.47.254.61 <pending> 80:30446/TCP 8m
svc/belajar-ci-db-service 10.47.243.72 <none> 3306/TCP 8m
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/belajar-ci-app 1 1 1 1 8m
deploy/belajar-ci-db 1 1 1 1 8m
NAME DESIRED CURRENT READY AGE
rs/belajar-ci-app-4282971413 1 1 1 8m
rs/belajar-ci-db-588835421 1 1 1 8m
</code></pre></div></div>
<p>Kita lihat bahwa saat ini aplikasi kita terdiri dari satu instance web dan satu instance database. Mari kita replikasi aplikasi webnya menjadi … hmmmm …. kita coba saja 27 node :D</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl scale --replicas=27 deploy/belajar-ci-app
</code></pre></div></div>
<p>Beberapa detik kemudian, keluar outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deployment "belajar-ci-app" scaled
</code></pre></div></div>
<p>Setelah selesai, kita lihat lagi output dari <code class="language-plaintext highlighter-rouge">kubectl get all -l app=belajar-ci</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME READY STATUS RESTARTS AGE
po/belajar-ci-app-4282971413-06wnz 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-19364 1/1 Running 0 1m
po/belajar-ci-app-4282971413-1vd2g 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-2m76g 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-2mvr6 1/1 Running 0 1m
po/belajar-ci-app-4282971413-36sqq 1/1 Running 0 1m
po/belajar-ci-app-4282971413-3c2gc 1/1 Running 0 1m
po/belajar-ci-app-4282971413-5xn38 1/1 Running 0 1m
po/belajar-ci-app-4282971413-70mhg 1/1 Running 0 1m
po/belajar-ci-app-4282971413-9j7m7 1/1 Running 0 1m
po/belajar-ci-app-4282971413-bzxwz 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-cvbkg 1/1 Running 0 1m
po/belajar-ci-app-4282971413-dr9w0 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-flffm 1/1 Running 0 1m
po/belajar-ci-app-4282971413-fqmr9 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-j4x55 1/1 Running 1 12m
po/belajar-ci-app-4282971413-k8597 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-lgj07 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-ltsvd 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-md16l 1/1 Running 0 1m
po/belajar-ci-app-4282971413-mjfj7 1/1 Running 0 1m
po/belajar-ci-app-4282971413-s4vn1 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-sfgwt 0/1 Pending 0 1m
po/belajar-ci-app-4282971413-wgbg7 1/1 Running 0 1m
po/belajar-ci-app-4282971413-wr6vf 1/1 Running 0 1m
po/belajar-ci-app-4282971413-wxpg5 1/1 Running 0 1m
po/belajar-ci-app-4282971413-z3jqj 1/1 Running 0 1m
po/belajar-ci-db-588835421-xh32q 1/1 Running 0 12m
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/belajar-ci-app-service 10.47.254.61 <pending> 80:30446/TCP 12m
svc/belajar-ci-db-service 10.47.243.72 <none> 3306/TCP 12m
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/belajar-ci-app 27 27 27 16 12m
deploy/belajar-ci-db 1 1 1 1 12m
NAME DESIRED CURRENT READY AGE
rs/belajar-ci-app-4282971413 27 27 16 12m
rs/belajar-ci-db-588835421 1 1 1 12m
</code></pre></div></div>
<p>Aplikasi web kita sudah punya 27 instance, tapi beberapa diantaranya masih dalam proses inisialisasi. Kita bisa jalankan perintah tersebut beberapa menit kemudian untuk melihat hasilnya. Tentunya jumlah replikasi ini harus disesuaikan dengan kemampuan dan jumlah node yang ada ya. Jangan nanti mau replikasi 100 instance, tapi cuma sedia 2 atau 3 VM ukuran small saja :P</p>
<h3 id="heroku">Heroku</h3>
<p>Di Heroku juga sama, kita bisa replikasi dengan satu perintah:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku ps:scale web=27
</code></pre></div></div>
<h3 id="pivotal-cloud-foundry">Pivotal Cloud Foundry</h3>
<p>Demikian juga di Pivotal Cloud Foundry, untuk mereplikasi menjadi 27 instance, jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf scale belajar-ci -i 27
</code></pre></div></div>
<h3 id="mengetes-replikasi">Mengetes Replikasi</h3>
<p>Untuk melihat apakah aplikasi kita sudah jalan, kita bisa akses aplikasinya. Saya sudah membuatkan halaman depan yang menampilkan hostname yang sedang melayani request.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/clustering-aplikasi/info-host.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/clustering-aplikasi/info-host.png" alt="Info Hostname" /></a></p>
<p>Klik tombol reload beberapa kali. Kita akan melihat bahwa isi <code class="language-plaintext highlighter-rouge">Hostname</code> dan <code class="language-plaintext highlighter-rouge">IP Address Local</code> akan berganti-ganti sesuai node yang melayani request. Kita juga bisa coba mengisi Session Variable dan Upload File untuk memastikan bahwa datanya tetap ada walaupun kita dilayani node yang berganti-ganti.</p>
<h2 id="penutup">Penutup</h2>
<p>Di tahun 2017 ini replication dan failover sudah sangat mudah. Dengan satu kali perintah saja, kita sudah bisa menambah instance aplikasi. Bahkan berbagai penyedia layanan cloud sudah menyediakan fasilitas <code class="language-plaintext highlighter-rouge">autoscaling</code>, yaitu secara otomatis menambah instance pada saat aplikasi kita mencapai pemakaian CPU/RAM tertentu. Informasi lebih lanjut mengenai autoscaling dapat dibaca di masing-masing penyedia:</p>
<ul>
<li><a href="https://blog.heroku.com/heroku-autoscaling">Heroku</a></li>
<li><a href="https://cloud.google.com/container-engine/docs/clusters/operations#create_a_cluster_with_autoscaling">Google Container Engine</a></li>
<li><a href="https://docs.run.pivotal.io/appsman-services/autoscaler/using-autoscaler.html">Pivotal Cloud Foundry</a></li>
<li><a href="https://aws.amazon.com/autoscaling/">Amazon</a></li>
</ul>
<p>Pastikan kita membuat aplikasi yang sudah siap direplikasi dengan memperhatikan hal-hal yang kita bahas di atas. Dengan demikian, kita bisa memanfaatkan kecanggihan teknologi cloud secara maksimal.</p>
<p>Seperti biasa, kode program bisa diakses <a href="https://github.com/endymuhardin/belajar-ci">di Github</a></p>
<p>Semoga bermanfaat …</p>
Otomasi Deployment Kubernetes dengan Gitlab CI2017-04-12T07:00:00+07:00https://software.endy.muhardin.com/devops/gitlab-ci-kubernetes-gke<p>Jaman sekarang, solusi Continuous Integration dan Delivery sudah banyak sekali pilihannya. Yang sudah kita bahas diantaranya:</p>
<ul>
<li><a href="http://software.endy.muhardin.com/aplikasi/cruise-control/">Cruise Control : ini adalah bapak moyangnya CI/D tools</a></li>
<li><a href="http://software.endy.muhardin.com/java/luntbuild/">Luntbuild</a></li>
<li><a href="http://software.endy.muhardin.com/java/deploy-jenkins-vps/">Jenkins</a></li>
<li><a href="http://software.endy.muhardin.com/java/project-bootstrap-02/">TravisCI</a></li>
</ul>
<p>Dan yang belum kita coba diantaranya:</p>
<ul>
<li>CircleCI</li>
<li>Bamboo</li>
<li>dan mungkin masih banyak lagi yang saya belum dengar</li>
</ul>
<p>Pada artikel kali ini, kita akan coba GitlabCI.</p>
<!--more-->
<p>Dibanding para kompetitornya, GitlabCI memiliki beberapa keunggulan, diantaranya:</p>
<ul>
<li>berbasis file, bisa dikelola dengan version control, tidak perlu klak klik, otomatis terbackup</li>
<li>integrated dengan version control Gitlab</li>
<li>proses buildnya bisa jalan dalam Docker container, memudahkan kita untuk melakukan setup environment</li>
<li>bisa jalan di local</li>
<li>support multiple environment (dev,test,prod)</li>
<li>bisa jalan di branch tertentu (hanya monitor branch master, dev, dsb)</li>
<li>manual trigger (misal : deploy ke production)</li>
<li>paralel execution</li>
<li>review apps</li>
</ul>
<p>Dari sekian keunggulan di atas, tiga poin paling atas sangat penting (menurut saya). Kalau Anda pernah pakai model GUI seperti Jenkins, tentu paham masalah utamanya adalah bagaimana membackup konfigurasi build kita. Dengan CI berbasis teks seperti Travis dan Gitlab, hal ini tidak menjadi masalah karena konfigurasi build dicommit bersama dengan source code kita. Terasa lebih natural dan selaras dengan workflow pemrograman kita.</p>
<p>Apapun produk CI yang kita gunakan, dia pasti harus terhubung dengan version control server kita. Baik untuk mengambil source code terbaru, mentrigger proses build ketika ada developer push, dan melaporkan hasil buildnya. Belum lagi kalau kita menggunakan proses code review atau workflow pull request, tentu ada lagi tambahan trigger pada event tersebut. Pada saat menggunakan Jenkins, kita harus menginstal berbagai macam plugin untuk mengakomodasi kebutuhan tadi. Setup awalnya sendiri sudah jadi tambahan kerjaan, belum lagi effort untuk update plugin dan backup konfigurasinya.</p>
<p>Jaman sekarang, dukungan Docker adalah sesuatu yang wajib. Tanpa docker, kita harus membuat install script dengan Ansible, Puppet, Chef, Bash script, atau apapun tools configuration management favorit Anda. Tambahan lagi yang harus dimaintain. Dengan Docker, cukup bikin image turunan dari yang sudah ada, beres.</p>
<p>Kita semua tentu pernah mengalami kejadian berikut. Semua test berjalan lancar di local, tapi ternyata gagal waktu dijalankan dalam proses CI. Dengan GitlabCI, hal ini tidak perlu terjadi lagi. Kita bisa menjalankan build script Gitlab di komputer kita, tidak perlu menunggu GitlabCI menjalankannya (dan gagal) di server. Kita akan bahas ini di bagian Runner di bawah.</p>
<p>Baiklah, mari kita langsung lihat saja file konfigurasinya. GitlabCI meminta kita untuk membuat file bernama <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> dalam folder project kita. Nantinya pada saat dipush, Gitlab akan membaca isi file tersebut dan menjalankannya. Berikut isi filenya</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">docker:latest</span>
<span class="na">services</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">docker:dind</span>
<span class="pi">-</span> <span class="s">mysql:latest</span>
<span class="na">variables</span><span class="pi">:</span>
<span class="na">DOCKER_DRIVER</span><span class="pi">:</span> <span class="s">overlay</span>
<span class="na">SPRING_PROFILES_ACTIVE</span><span class="pi">:</span> <span class="s">docker,localstorage</span>
<span class="na">MYSQL_ROOT_PASSWORD</span><span class="pi">:</span> <span class="s">admin</span>
<span class="na">MYSQL_DATABASE</span><span class="pi">:</span> <span class="s">belajar</span>
<span class="na">MYSQL_USER</span><span class="pi">:</span> <span class="s">belajar</span>
<span class="na">MYSQL_PASSWORD</span><span class="pi">:</span> <span class="s">java</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">build</span>
<span class="pi">-</span> <span class="s">package</span>
<span class="pi">-</span> <span class="s">deploy</span>
<span class="na">maven-build</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">maven:3-jdk-8</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span> <span class="s">mvn package -B</span>
<span class="na">artifacts</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">target/*.jar</span>
<span class="na">docker-build</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">package</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "$GCLOUD_CREDENTIAL" > gcloud-credential.json</span>
<span class="pi">-</span> <span class="s">docker build -t $CI_PROJECT_PATH:$CI_COMMIT_SHA .</span>
<span class="pi">-</span> <span class="s">docker tag $CI_PROJECT_PATH:$CI_COMMIT_SHA $CI_PROJECT_PATH:latest</span>
<span class="pi">-</span> <span class="s">docker login -u endymuhardin -p $DOCKERHUB_PASSWORD</span>
<span class="pi">-</span> <span class="s">docker push $CI_PROJECT_PATH</span>
<span class="pi">-</span> <span class="s">docker logout</span>
<span class="pi">-</span> <span class="s">rm gcloud-credential.json</span>
<span class="na">k8s-deploy</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">google/cloud-sdk</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "$GCLOUD_KEY" > key.json</span>
<span class="pi">-</span> <span class="s">gcloud auth activate-service-account --key-file key.json</span>
<span class="pi">-</span> <span class="s">gcloud config set compute/zone asia-east1-a</span>
<span class="pi">-</span> <span class="s">gcloud config set project belajar-ci</span>
<span class="pi">-</span> <span class="s">gcloud config set container/use_client_certificate True</span>
<span class="pi">-</span> <span class="s">gcloud container clusters get-credentials belajar-ci</span>
<span class="pi">-</span> <span class="s">kubectl apply -f k8s</span>
<span class="pi">-</span> <span class="s">kubectl set image deploy/belajar-ci-app belajar-ci=endymuhardin/belajar-ci:latest</span>
<span class="pi">-</span> <span class="s">rm key.json</span>
</code></pre></div></div>
<p>Mari kita bahas satu persatu.</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">image: docker:latest</code> artinya kita ingin menjalankan seluruh proses build dalam Docker container. Kita akan gunakan versi terbaru (latest)</p>
</li>
<li>
<p>services : adalah aplikasi yang ingin kita jalankan dalam container tersebut.</p>
<ul>
<li>
<p>Service pertama adalah <code class="language-plaintext highlighter-rouge">dind</code>, singkatan dari <code class="language-plaintext highlighter-rouge">docker in docker</code>. Ini gunanya untuk menjalankan docker di dalam docker. Kita butuh ini karena ada proses pembuatan docker image dalam rangkaian build kita.</p>
</li>
<li>
<p>Service kedua adalah MySQL. Ini sama seperti deklarasi container dalam Docker Compose yang sudah kita bahas di <a href="http://software.endy.muhardin.com/devops/docker-workflow/">artikel terdahulu</a>. Container yang menjalankan proses MySQL ini akan didaftarkan dengan hostname <code class="language-plaintext highlighter-rouge">mysql</code>. Dengan demikian, aplikasi kita bisa mengaksesnya dengan JDBC URL <code class="language-plaintext highlighter-rouge">jdbc:mysql://mysql/belajar</code></p>
</li>
</ul>
</li>
<li>
<p>Blok <code class="language-plaintext highlighter-rouge">variables</code> adalah deklarasi environment variable. Kita butuh ini untuk menginisialisasi container MySQL dan mengatur profile aplikasi Spring Boot</p>
</li>
<li>
<p>Blok <code class="language-plaintext highlighter-rouge">stages</code> mendefinisikan tahapan build. Isinya bebas saja, tapi saya gunakan istilah yang umum digunakan supaya mudah dipahami, yaitu <code class="language-plaintext highlighter-rouge">build</code>, <code class="language-plaintext highlighter-rouge">package</code>, dan <code class="language-plaintext highlighter-rouge">deploy</code>. GitlabCI akan menjalankan job-job secara berurutan sesuai urutan deklarasi dalam blok ini</p>
</li>
<li>
<p>Blok-blok selanjutnya adalah definisi job yang harus dijalankan GitlabCI. Namanya bebas saja, tidak ada keyword tertentu yang harus dipakai.</p>
</li>
</ul>
<h3 id="stage-build">Stage Build</h3>
<p>Pertama, kita jalankan proses build Maven dalam job <code class="language-plaintext highlighter-rouge">maven-build</code>. Perintah yang dijalankan sama dengan yang biasa kita pakai di laptop/pc kita, yaitu <code class="language-plaintext highlighter-rouge">mvn package</code>. Tambahkan opsi <code class="language-plaintext highlighter-rouge">-B</code> supaya Maven tidak mengeluarkan prompt pertanyaan.</p>
<p>Job ini berjalan dalam stage <code class="language-plaintext highlighter-rouge">build</code> yang kita deklarasikan paling atas dalam blok <code class="language-plaintext highlighter-rouge">stages</code> tadi. Kita bisa mendeklarasikan banyak job dalam stage yang sama. GitlabCI akan menjalankan semua job dalam stage yang sama secara berbarengan.</p>
<p>Agar build bisa berjalan, tentu kita butuh environment yang sudah terinstal Java dan Maven. Oleh karena itu, kita suruh GitlabCI untuk menjalankannya dalam docker container <code class="language-plaintext highlighter-rouge">maven:3-jdk-8</code>. Mudah kan, tidak perlu setup apa-apa.</p>
<p>Setelah job <code class="language-plaintext highlighter-rouge">maven-build</code> selesai, kita ingin menyimpan hasilnya untuk kita gunakan di tahap selanjutnya. Kita deklarasikan file mana yang mau disimpan dengan blok <code class="language-plaintext highlighter-rouge">artifacts</code></p>
<h3 id="stage-package">Stage Package</h3>
<p>Dalam stage ini, kita membuat docker image dan kemudian menguploadnya ke Dockerhub. Penjelasan detailnya sudah kita bahas di <a href="http://software.endy.muhardin.com/devops/docker-workflow/">artikel sebelumnya</a>. Rangkaian perintahnya sama, tinggal kita tulis saja.</p>
<p>Dalam perintah <code class="language-plaintext highlighter-rouge">docker login</code>, kita butuh password. Tentu saya tidak bisa tulis passwordnya dalam file ini, karena nanti akan terbaca oleh semua orang yang punya akses ke source code. Solusinya, kita deklarasikan variabel <code class="language-plaintext highlighter-rouge">DOCKERHUB_PASSWORD</code> di Gitlab pada menu <code class="language-plaintext highlighter-rouge">Settings > CI/CD Pipelines</code> seperti ini</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-ci/gitlab-ci-variables.png" alt="Setting Secret Variabel" /></p>
<p>Variabel tersebut bisa kita akses di <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> dengan sintaks <code class="language-plaintext highlighter-rouge">$DOCKERHUB_PASSWORD</code></p>
<p>Sebetulnya Gitlab juga menyediakan private Docker registry untuk menghosting docker image kita. Tapi saya belum berhasil mengkombinasikannya dengan GKE. Jadi ya sementara ini kita upload ke DockerHub saja dulu.</p>
<h3 id="stage-deploy">Stage Deploy</h3>
<p>Pada stage ini, kita akan mendeploy aplikasi dengan Kubernetes yang dihosting di Google Container Engine (GKE). Langkah-langkahnya sudah kita bahas mendetail <a href="http://software.endy.muhardin.com/devops/deploy-google-container-engine/">di artikel sebelumnya</a>. Tinggal kita pindahkan saja kesini.</p>
<p>Yang agak berbeda adalah proses login ke Google Cloud. Loginnya tidak menggunakan username/password seperti lazimnya aplikasi lain. Dia butuh file credential. Biasanya kita tidak butuh file ini pada saat mendeploy dari laptop, karena kita sudah melakukan login dengan Google Cloud SDK di laptop kita.</p>
<p>File ini didapatkan dengan cara mengakses <a href="https://console.cloud.google.com">Google Cloud Console</a>, kemudian masuk ke menu <code class="language-plaintext highlighter-rouge">API Manager > Credentials</code>. Setelah itu, klik tombol <code class="language-plaintext highlighter-rouge">Create Credentials</code> dan pilih <code class="language-plaintext highlighter-rouge">Service account key</code></p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-ci/create-service-account-file.png" alt="Generate Credentials File" /></p>
<p>Buka file tersebut dengan text editor, copy isinya, dan gunakan untuk membuat variabel di Gitlab. Misalnya nama variabelnya kita set menjadi <code class="language-plaintext highlighter-rouge">GCLOUD_KEY</code>.</p>
<p>Pada waktu build, kita tulis kembali isi variabel tersebut ke dalam file dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo "$GCLOUD_KEY" > key.json
</code></pre></div></div>
<p>Selanjutnya, kita gunakan file tersebut dalam proses login.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud auth activate-service-account --key-file key.json
</code></pre></div></div>
<p>Jangan lupa untuk menghapusnya lagi setelah selesai.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm key.json
</code></pre></div></div>
<h3 id="menjalankan-proses-build">Menjalankan Proses Build</h3>
<p>Proses build akan berjalan setiap kali ada push ke Gitlab. Kita bisa membatasi proses build hanya untuk branch tertentu dengan keyword <code class="language-plaintext highlighter-rouge">only</code>.</p>
<p>Contoh kasus, misalnya kita punya 3 branch yang akan dideploy ke lokasi berbeda.</p>
<ul>
<li>branch <code class="language-plaintext highlighter-rouge">development</code> deploy ke Heroku</li>
<li>branch <code class="language-plaintext highlighter-rouge">testing</code> deploy ke docker machine di DigitalOcean</li>
<li>branch <code class="language-plaintext highlighter-rouge">master</code> deploy ke Kubernetes cluster di GKE</li>
</ul>
<p>Berikut konfigurasinya</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">deploy-ke-heroku</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span> <span class="s">development</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">development</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">https://belajar-ci-endy.herokuapp.com</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">git push https://heroku:$(HEROKU_API_KEY)@git.heroku.com/git.heroku.com/belajar-ci-endy.git development</span>
<span class="na">deploy-ke-digitalocean</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span> <span class="s">testing</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">testing</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">https://server-di-do.com</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">scp target/*.jar root@server-di-do.com:/opt/</span>
<span class="pi">-</span> <span class="s">ssh root@server-di-do.com service belajar-ci restart</span>
<span class="na">deploy-ke-gke</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
<span class="na">only</span><span class="pi">:</span> <span class="s">master</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">production</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">http://104.155.193.131/</span>
<span class="na">when</span><span class="pi">:</span> <span class="s">manual</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo "$GCLOUD_KEY" > key.json</span>
<span class="pi">-</span> <span class="s">gcloud auth activate-service-account --key-file key.json</span>
<span class="pi">-</span> <span class="s">gcloud config set compute/zone asia-east1-a</span>
<span class="pi">-</span> <span class="s">gcloud config set project belajar-ci</span>
<span class="pi">-</span> <span class="s">gcloud config set container/use_client_certificate True</span>
<span class="pi">-</span> <span class="s">gcloud container clusters get-credentials belajar-ci</span>
<span class="pi">-</span> <span class="s">kubectl apply -f k8s</span>
<span class="pi">-</span> <span class="s">kubectl set image deploy/belajar-ci-app belajar-ci=endymuhardin/belajar-ci:latest</span>
<span class="pi">-</span> <span class="s">rm key.json</span>
</code></pre></div></div>
<p>Dengan konfigurasi di atas, kita dapat:</p>
<ol>
<li>Menyuruh semua programmer commit ke branch development</li>
<li>Setelah aplikasi terdeploy ke Heroku, kita bisa segera melakukan testing menyeluruh</li>
<li>Begitu dinyatakan oke, merge development ke testing. Aplikasi akan segera terdeploy ke Digital Ocean.</li>
<li>Lakukan tes lagi di Digital Ocean. Bisa functional test oleh tester, performance test, security test, dan sebagainya.</li>
<li>Begitu oke, merge ke production. Kita lihat disana ada setting manual. Setting itu membutuhkan orang untuk login ke Gitlab dan menekan tombol Oke. Begitu ditekan, aplikasi akan terdeploy ke cluster Kubernetes di GKE.</li>
</ol>
<p>Hal seperti ini bisa saja kita lakukan dengan Jenkins. Tapi jauh lebih repot. Bayangkan berapa project yang kita akan create, belum lagi setup pipeline, install plugin SCP, Docker, dan lainnya.</p>
<p>Untuk mengetahui lebih lanjut tentang deployment ke berbagai environment, silahkan baca <a href="https://docs.gitlab.com/ce/ci/environments.html">dokumentasi resminya</a>.</p>
<h2 id="review-apps">Review Apps</h2>
<p>Bila kita menjalankan workflow code review, biasanya para programmer akan membuat branch khusus untuk pekerjaan yang sedang dikerjakan (misalnya fix bugs, tambah fitur, dsb). Bila pekerjaan mereka sudah selesai, mereka akan membuat <code class="language-plaintext highlighter-rouge">Merge Request</code>. Supervisor atau senior programmer kemudian akan menarik merge request tersebut ke komputernya, mendeploy aplikasinya, dan mengetes hasil pekerjaan tersebut. Bila hasilnya oke, maka branch tersebut bisa dimerge ke branch utama. Bila kurang oke, programmer pembuatnya bisa disuruh untuk melakukan revisi.</p>
<p>Hal ini tentu merepotkan kalau harus dilakukan berulang kali. Apalagi kalau setup aplikasinya rada ribet. Untuk memudahkan proses review ini, Gitlab CI menyediakan <a href="https://docs.gitlab.com/ce/ci/review_apps/index.html">fitur Review Apps</a>.</p>
<p>Fitur ini akan mendeploy aplikasi ke lokasi yang unik untuk masing-masing <code class="language-plaintext highlighter-rouge">Merge Request</code>. Tentunya kita harus siapkan target deployment yang mampu menampung instance aplikasi sebanyak <code class="language-plaintext highlighter-rouge">Merge Request</code> yang open. Dengan fitur ini, reviewer bisa langsung klik URL yang ada di Gitlab dan mengakses aplikasi yang sudah terdeploy. Bila sudah selesai review, Gitlab akan menghapus instance tersebut. Contohnya bisa dilihat di <a href="https://gitlab.com/gitlab-examples/review-apps-nginx">contoh repositorynya</a>.</p>
<h2 id="gitlab-runner">Gitlab Runner</h2>
<p>Dari tadi kita hanya membahas konfigurasi build dan apa yang akan terjadi. Ada satu bagian yang kurang, yaitu siapa yang menjalankannya.</p>
<p>Di Gitlab, komponen yang bertugas menjalankan build adalah Gitlab Runner. Biasanya Runner ini kita setup di mesin berbeda. Ada beberapa jenis Runner:</p>
<ul>
<li><a href="https://docs.gitlab.com/runner/executors/docker.html">docker biasa</a></li>
<li>docker autoscale</li>
<li>virtualbox</li>
<li>dan lain-lain. Lengkapnya bisa dibaca di <a href="https://docs.gitlab.com/ce/ci/runners/README.html">dokumentasinya</a>.</li>
</ul>
<p>Cara instalasinya tidak kita bahas di sini. Silahkan lihat langsung ke <a href="https://docs.gitlab.com/runner/install/">dokumentasi resminya</a></p>
<p>Beberapa fitur menarik dari Runner ini antara lain:</p>
<ul>
<li>
<p>Docker. Gitlab bisa menjalankan job kita dalam docker container. Ini akan memudahkan kita dalam melakukan setup environment. Tidak perlu lagi menyiapkan VPS atau membuat install script. Sebagai contoh, untuk aplikasi Java kita cukup menyatakan <code class="language-plaintext highlighter-rouge">image: maven:3-jdk-8</code>. Database server yang dibutuhkan aplikasi juga tidak perlu repot menginstalnya. Cukup sebutkan di blok <code class="language-plaintext highlighter-rouge">services</code> bahwa kita butuh docker container dengan image <code class="language-plaintext highlighter-rouge">mysql:latest</code>.</p>
</li>
<li>
<p>Autoscaling. Gitlab akan secara otomatis menambah host pada saat antrian build besar, dan otomatis mematikan host pada saat tidak ada antrian build. Bila kita membuat host secara virtual di layanan cloud, ini akan sangat memudahkan dan juga menghemat biaya. Daripada kita sewa host secara permanen dan banyak nganggurnya, lebih baik kita sewa sesuai kebutuhan. Apalagi di jaman sekarang berbagai layanan cloud seperti Digital Ocean, Linode, dan sebagainya, menghitung biaya dalam satuan jam. Informasi lebih rinci mengenai autoscaling bisa dibaca di <a href="https://docs.gitlab.com/runner/install/autoscaling.html">dokumentasi resminya</a>.</p>
</li>
<li>
<p>Multi Runner Exec. Gitlab bisa menjalankan script <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> di laptop kita sendiri. Kita bisa <a href="https://docs.gitlab.com/runner/install/osx.html">install Gitlab Runner di laptop kita</a>, kemudian jalankan perintah <code class="language-plaintext highlighter-rouge">gitlab-ci-multi-runner exec docker maven-build</code>. Hal penting yang harus diperhatikan, kalau menggunakan executor <code class="language-plaintext highlighter-rouge">docker</code>, docker enginenya harus berada di localhost. Tidak bisa menggunakan docker host yang ada di Digital Ocean ataupun di VirtualBox misalnya. Kalau tidak ada docker di local, maka kita bisa gunakan executor <code class="language-plaintext highlighter-rouge">shell</code> dengan perintah berikut <code class="language-plaintext highlighter-rouge">gitlab-runner exec shell maven-build</code>.</p>
</li>
</ul>
<h2 id="penutup">Penutup</h2>
<p>Di jaman serba cepat seperti sekarang ini, segala hal harus dioptimasi, termasuk workflow pembuatan aplikasi. Dengan adanya teknologi container seperti Docker, teknologi cloud hosting seperti Digital Ocean, Heroku, Amazon, dkk, teknologi clustering seperti Kubernetes, Mesos, dkk, cara kita menjalankan development juga harus berubah.</p>
<p>Sudah tidak jamannya lagi kita menyiapkan mesin fisik secara manual, menginstal sistem operasi dan kelengkapannya, baru mendeploy aplikasi. Jaman sekarang, cukup kita paketkan aplikasi dalam container, kemudian deploy otomatis tiap ada perubahan kode program. Ini akan memangkas waktu dari programmer selesai coding sampai aplikasi bisa digunakan user, yang tadinya berminggu-minggu menjadi beberapa menit saja.</p>
<p>Walaupun demikian, rangkaian otomasi ini belum selesai. Di <a href="http://software.endy.muhardin.com/java/cluster-ready-app">artikel selanjutnya</a>, kita akan menyiapkan aplikasi kita agar siap direplikasi. Setelah itu, kita akan mencoba kemudahan replikasi dengan satu perintah saja. Fitur ini sudah umum tersedia di mana-mana, seperti misalnya Amazon AWS, Google Cloud, Heroku, Pivotal Web Service, dan mayoritas penyedia layanan cloud lain.</p>
<p>Untuk melihat kode program aplikasinya, silahkan langsung menuju repositorynya <a href="https://gitlab.com/endymuhardin/belajar-ci">di Gitlab</a>. Hasil build bisa dilihat di <a href="https://gitlab.com/endymuhardin/belajar-ci/pipelines">tab Pipelines</a></p>
<p>Stay tuned …</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="https://docs.gitlab.com/ce/ci/quick_start/">https://docs.gitlab.com/ce/ci/quick_start/</a></li>
<li><a href="https://docs.gitlab.com/ce/ci/yaml/README.html">https://docs.gitlab.com/ce/ci/yaml/README.html</a></li>
<li><a href="https://docs.gitlab.com/ce/ci/environments.html">https://docs.gitlab.com/ce/ci/environments.html</a></li>
<li><a href="https://docs.gitlab.com/ce/ci/review_apps/index.html">https://docs.gitlab.com/ce/ci/review_apps/index.html</a></li>
<li><a href="https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/">https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/</a></li>
</ul>
Mendeploy Docker Container ke Google Container Engine2017-04-10T07:00:00+07:00https://software.endy.muhardin.com/devops/deploy-google-container-engine<p>Pada <a href="http://software.endy.muhardin.com/devops/docker-workflow/">artikel sebelumnya</a>, kita telah membuat docker image, merangkainya dengan docker compose, kemudian menjalankannya di Docker Engine di Digital Ocean. Di artikel ini, kita akan coba naikkan level kekiniannya dengan mendeploy ke Google Container Engine yang biasa disingkat GKE. Ingat!! Pakai <code class="language-plaintext highlighter-rouge">K</code> ya. Bukan GCE, karena singkatan GCE sudah dipakai oleh Google Compute Engine.</p>
<p>Untuk mendeploy ke GKE, kita akan menggunakan <a href="https://kubernetes.io/">Kubernetes</a>, bukan Docker Compose.</p>
<blockquote>
<p>Apa bedanya?</p>
</blockquote>
<p>Singkatnya begini, dalam Docker Compose, kita berbicara di level instance. Aplikasi web kita satu instance, database server satu instance. Beda ya, instance dengan node. Node itu bisa disebut satu mesin. Dalam satu mesin bisa menampung banyak instance aplikasi kita.</p>
<p>Bila aplikasi web kita digandakan menjadi beberapa instance (mungkin untuk alasan failover, mungkin untuk meningkatkan kapasitas melayani request), maka itu disebut replikasi atau clustering. Untuk mengelola replikasi ini, terutama replikasi docker container, ada beberapa alternatif solusi yang tersedia, diantaranya:</p>
<ul>
<li>Docker Swarm</li>
<li>Kubernetes</li>
<li>Apache Mesos</li>
<li>dan sebagainya, termasuk bikin sendiri.</li>
</ul>
<p>Pada artikel ini, kita akan mendeploy aplikasi menggunakan Kubernetes.</p>
<blockquote>
<p>Kenapa pilih Kubernetes?</p>
</blockquote>
<p>Alasan utama sih sebenarnya karena saya pengen belajar Kubernetes, biar kekinian :D
Selain itu, karena kita ingin deploy ke layanan cloud yang disediakan oleh Google. <a href="http://software.endy.muhardin.com/java/project-bootstrap-03/">Ke Heroku kan sudah</a>, <a href="http://software.endy.muhardin.com/java/deploy-jenkins-vps/">ke Digital Ocean sudah</a>, <a href="http://software.endy.muhardin.com/java/deploy-jenkins-pivotal/">ke Pivotal sudah</a>, nah jadi sekarang kita coba produknya Google.</p>
<blockquote>
<p>Kok gak ke Amazon? Kan terkenal juga?</p>
</blockquote>
<p>Ya simply karena jatah gratisan Amazon saya sudah habis. Jadi kita maksimalkan yang masih gratis dulu :D</p>
<p>Mari kita mulai …</p>
<!--more-->
<p>Pada artikel ini, kita batasi dulu untuk deployment satu instance saja. Sebetulnya Kubernetes lebih ditujukan untuk mengelola <a href="http://blog.kubernetes.io/2017/03/scalability-updates-in-kubernetes-1.6.html">deployment ratusan ribu instance di ribuan node</a>. Sayangnya aplikasi contoh kita belum cluster-ready, jadi sementara kita tunda dulu clusteringnya. Cukup kita tiru saja konfigurasi docker compose kemarin, yaitu satu instance database, satu instance aplikasi, kemudian kita gunakan persistent volume untuk menyimpan data agar tidak hilang pada saat container dihapus.</p>
<p>Lain waktu kita akan perbaiki aplikasinya supaya cluster-ready, sehingga nanti tinggal kita scale-out dengan mudah.</p>
<h2 id="apa-itu-kubernetes">Apa itu Kubernetes</h2>
<p>Singkatnya begini, Kubernetes adalah aplikasi cluster management open source yang disponsori oleh Google. Aplikasi ini berasal dari aplikasi internal yang digunakan Google (namanya Borg) untuk mengelola cluster mereka sendiri. Janji surganya adalah, karena berasal dari pengalaman Google belasan tahun dalam mengelola cluster, maka Kubernetes ini akan merupakan kristalisasi dari best-practices dan lesson-learned dari belasan tahun tersebut.</p>
<p>Secara bisnis, Kubernetes ini <a href="http://container-solutions.com/why-kubernetes-makes-sense/">merupakan senjata andalan Google</a> untuk mendongkrak peringkatnya di pasar cloud hosting, yang saat ini jauh berada di bawah Amazon dan Azure-nya Microsoft. Ini bisa dilihat dari <a href="https://medium.com/google-cloud/why-k8s-on-gke-a644d2d611c1">betapa mudahnya kita menggunakan Kubernetes di GKE</a>. Bandingkan dengan susahnya setup Kubernetes di tempat lain.</p>
<p>Sebelum lebih jauh, kita pahami dulu beberapa istilah dalam Kubernetes:</p>
<ul>
<li>
<p>Pod : adalah satu grup container instance. Kita bisa menjalankan beberapa container (misalnya aplikasi web + redis cache + logging service) dalam satu pod. Antar container dalam satu pod bisa saling mengakses dengan menggunakan alamat <code class="language-plaintext highlighter-rouge">localhost</code>. Anggap saja <code class="language-plaintext highlighter-rouge">pod</code> seperti laptop yang kita pakai coding. Untuk mengakses database dari aplikasi kita, biasanya kita pakai alamat <code class="language-plaintext highlighter-rouge">localhost</code></p>
</li>
<li>
<p>Node : adalah representasi dari satu mesin. Mesin ini bisa saja mesin virtual (seperti VPS atau dropletnya DigitalOcean) atau fisik. Tapi kita seharusnya tidak memusingkan virtual vs fisik. Yang perlu kita pahami adalah satu <code class="language-plaintext highlighter-rouge">node</code> bisa berisi banyak <code class="language-plaintext highlighter-rouge">pod</code>. Kubernetes nanti yang akan memilihkan <code class="language-plaintext highlighter-rouge">pod</code> mana akan jalan di <code class="language-plaintext highlighter-rouge">node</code> mana. Dia juga bebas memindahkan <code class="language-plaintext highlighter-rouge">pod</code> dari <code class="language-plaintext highlighter-rouge">node</code> yang sibuk ke <code class="language-plaintext highlighter-rouge">node</code> lain yang terlihat santai.</p>
</li>
<li>
<p>Service : merupakan mekanisme untuk mengekspos pod kita ke dunia luar. Aplikasi kita yang berjalan dalam pod tidak memiliki alamat IP yang tetap, karena <code class="language-plaintext highlighter-rouge">node</code> tempat dia berjalan bisa pindah-pindah. Agar bisa diakses oleh aplikasi lain atau oleh user, kita perlu alamat IP yang tetap. Service menyediakan alamat IP yang tetap, yang nantinya akan kita arahkan ke <code class="language-plaintext highlighter-rouge">pod</code> kita dengan menggunakan <code class="language-plaintext highlighter-rouge">selector</code>.</p>
</li>
<li>
<p>Label : adalah seperangkat informasi metadata untuk mencari <code class="language-plaintext highlighter-rouge">pod</code> tertentu. Sebagai contoh, label yang biasa digunakan misalnya:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">app=belajar</code> : kita buat label <code class="language-plaintext highlighter-rouge">app</code> yang isinya adalah nama aplikasi. Semua container, pod, dan service yang menjadi bagian dari aplikasi <code class="language-plaintext highlighter-rouge">belajar</code> kita beri label <code class="language-plaintext highlighter-rouge">app=belajar</code>. Ini akan memudahkan kita untuk mencari siapa saja yang ikut terlibat dalam aplikasi <code class="language-plaintext highlighter-rouge">belajar</code>.</li>
<li><code class="language-plaintext highlighter-rouge">stage=production</code> : label <code class="language-plaintext highlighter-rouge">stage</code> bisa kita gunakan untuk menentukan berbagai konfigurasi environment deployment aplikasi kita, misalnya <code class="language-plaintext highlighter-rouge">development</code>, <code class="language-plaintext highlighter-rouge">testing</code>, <code class="language-plaintext highlighter-rouge">performancetest</code>, <code class="language-plaintext highlighter-rouge">securitytest</code>, dan <code class="language-plaintext highlighter-rouge">production</code></li>
<li><code class="language-plaintext highlighter-rouge">jenis=frontend</code> : kita bisa membuat label jenis aplikasi, misalnya <code class="language-plaintext highlighter-rouge">frontend</code>, <code class="language-plaintext highlighter-rouge">cache</code>, <code class="language-plaintext highlighter-rouge">database</code>, <code class="language-plaintext highlighter-rouge">fileserver</code>, dan sebagainya.</li>
</ul>
</li>
<li>
<p>Selector : adalah filtering menggunakan label. Misalnya kita ingin mencari semua instance <code class="language-plaintext highlighter-rouge">database</code> untuk aplikasi <code class="language-plaintext highlighter-rouge">belajar</code> yang berjalan di <code class="language-plaintext highlighter-rouge">production</code>.</p>
</li>
</ul>
<p>Di Google Cloud Platform, segala infrastruktur yang dibutuhkan untuk menjalankan Kubernetes sudah tersedia. Sehingga kita cukup belajar cara pakainya saja, tidak perlu pusing memikirkan cara setupnya :D</p>
<h2 id="setup-google-cloud-platform">Setup Google Cloud Platform</h2>
<p>Untuk mendapatkan akses ke Google Cloud Platform, langsung saja <a href="https://cloud.google.com/">mendaftar ke websitenya</a>. Tidak perlu saya jelaskan ya, cukup klik saja tombol Free Trial. Biasanya yang gratis-gratis gampang dicari kok :P</p>
<p>Saya agak lupa apakah perlu kartu kredit atau tidak. Lagipula kartu kredit saya sudah pernah didaftarkan waktu beli aplikasi di Play Store. Jadi mungkin sudah terintegrasi sehingga saya tidak dimintai lagi.</p>
<p>Setelah selesai membuat akun, buat project baru di dalam web console Google Cloud. Project di artikel ini namanya <code class="language-plaintext highlighter-rouge">belajar-ci</code>. Membuat project juga tidak akan saya pandu, silahkan cari sendiri tombolnya.</p>
<h3 id="setup-google-cloud-sdk">Setup Google Cloud SDK</h3>
<p>Setelah mendapat akun, berikutnya kita instal Google Cloud SDK. Sebetulnya bisa saja kita klak klik di web console. Tapi saya kurang suka. Selain lambat (menggerakkan mouse lebih lambat daripada mengetik command), juga sulit diotomasi nantinya di proses Continuous Integration.</p>
<p>Instalasi Google Cloud SDK juga tidak perlu kita bahas ya. Silahkan ikuti panduan <a href="https://cloud.google.com/sdk/docs/quickstarts">di sini</a>.</p>
<p>Kita harus login dulu menggunakan Google Cloud SDK agar bisa mengakses akun kita di Google Cloud. Buka command prompt, dan lakukan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud init
</code></pre></div></div>
<p>Kita akan dipandu untuk melakukan proses login.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Welcome! This command will take you through the configuration of gcloud.
Your current configuration has been set to: [default]
You can skip diagnostics next time by using the following flag:
gcloud init --skip-diagnostics
Network diagnostic detects and fixes local network connection issues.
Checking network connection...done.
Reachability Check passed.
Network diagnostic (1/1 checks) passed.
You must log in to continue. Would you like to log in (Y/n)?
</code></pre></div></div>
<p>Ketik saja enter untuk memilih Yes. Kita akan dibukakan browser untuk melanjutkan proses login</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Your browser has been opened to visit:
https://accounts.google.com/o/oauth2/auth?redirect_uri=http%3A%2F%2Flocalhost%3A8085%2F&prompt=select_account&response_type=code&client_id=32555940559.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fappengine.admin+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcompute+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&access_type=offline
</code></pre></div></div>
<p>Bila kita belum login Google, kita harus login dulu. Tapi biasanya orang sudah membuka GMail di browsernya, sehingga bisa langsung ke laman otorisasi</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-ci-gke/otorisasi-cloud-sdk.png" alt="Otorisasi Cloud SDK" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You are logged in as: [endy.muhardin@gmail.com].
Pick cloud project to use:
[1] belajar-ci
[2] belajar-oauth-sso
Please enter numeric choice or text value (must exactly match list
item): 1
</code></pre></div></div>
<p>Setelah otorisasi, kita akan disuruh pilih project mana yang akan kita kerjakan. Bila nanti mau pindah project, kita bisa melakukan setup ulang.</p>
<p>Pilih 1, yaitu project yang sudah kita buat setelah pendaftaran tadi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Your current project has been set to: [belajar-ci].
Do you want to configure Google Compute Engine
(https://cloud.google.com/compute) settings (Y/n)?
Which Google Compute Engine zone would you like to use as project
default?
If you do not specify a zone via a command line flag while working
with Compute Engine resources, the default is assumed.
[1] asia-east1-a
[2] asia-east1-c
[3] asia-east1-b
[4] asia-northeast1-c
[5] asia-northeast1-a
[6] asia-northeast1-b
[7] europe-west1-d
[8] europe-west1-b
[9] europe-west1-c
[10] us-central1-b
[11] us-central1-a
[12] us-central1-f
[13] us-central1-c
[14] us-east1-b
[15] us-east1-d
[16] us-east1-c
[17] us-west1-a
[18] us-west1-b
[19] Do not set default zone
Please enter numeric choice or text value (must exactly match list
item): 1
</code></pre></div></div>
<p>Pilih zona default. Di sini saya pilih 1 agar servernya lebih dekat ke Indonesia.</p>
<blockquote>
<p>Hari ini saya cek, sudah ada opsi zone Singapura yang lebih dekat lagi. Kodenya <code class="language-plaintext highlighter-rouge">asia-southeast1-a</code> dan <code class="language-plaintext highlighter-rouge">asia-southeast1-b</code></p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Your project default Compute Engine zone has been set to [asia-east1-a].
You can change it by running [gcloud config set compute/zone NAME].
Your Google Cloud SDK is configured and ready to use!
* Commands that require authentication will use endy.muhardin@gmail.com by default
* Commands will reference project `belajar-ci` by default
* Compute Engine commands will use region `asia-east1` by default
* Compute Engine commands will use zone `asia-east1-a` by default
Run `gcloud help config` to learn how to change individual settings
This gcloud configuration is called [default]. You can create additional configurations if you work with multiple accounts and/or projects.
Run `gcloud topic configurations` to learn more.
Some things to try next:
* Run `gcloud --help` to see the Cloud Platform services you can interact with. And run `gcloud help COMMAND` to get help on any gcloud command.
* Run `gcloud topic -h` to learn about advanced features of the SDK like arg files and output formatting
</code></pre></div></div>
<p>Berikutnya, kita siapkan file credentials agar Kubernetes nantinya bisa melakukan deployment.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud auth application-default login
</code></pre></div></div>
<p>Kita akan dibukakan browser lagi dan dimintai persetujuan</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/gitlab-ci-gke/otorisasi-auth-library.png" alt="Otorisasi Kubernetes" /></p>
<p>Allow saja supaya bisa lanjut. Setelah diapprove, kita akan mendapatkan file authentication seperti disebutkan dalam output command line tadi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Your browser has been opened to visit:
https://accounts.google.com/o/oauth2/auth?redirect_uri=http%3A%2F%2Flocalhost%3A8085%2F&prompt=select_account&response_type=code&client_id=764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&access_type=offline
Credentials saved to file: [/Users/endymuhardin/.config/gcloud/application_default_credentials.json]
These credentials will be used by any library that requests
Application Default Credentials.
</code></pre></div></div>
<h3 id="instalasi-kubernetes">Instalasi Kubernetes</h3>
<p>Agar dapat berinteraksi dengan kubernetes cluster, kita harus melakukan instalasi terlebih dulu. Kita bisa menginstalnya melalui Google Cloud SDK dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud components install kubectl
</code></pre></div></div>
<p>Atau kita bisa install langsung di sistem operasi dengan perintah berikut (MacOS)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install kubectl
</code></pre></div></div>
<p>Saya menggunakan metode instalasi <code class="language-plaintext highlighter-rouge">gcloud</code> untuk memastikan versi <code class="language-plaintext highlighter-rouge">kubectl</code> kompatibel dengan Google Container Engine (GKE). Berikut adalah outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Your current Cloud SDK version is: 149.0.0
Installing components from version: 149.0.0
┌─────────────────────────────────────────────────┐
│ These components will be installed. │
├────────────────────────────┬─────────┬──────────┤
│ Name │ Version │ Size │
├────────────────────────────┼─────────┼──────────┤
│ kubectl │ │ │
│ kubectl (Mac OS X, x86_64) │ 1.5.4 │ 11.4 MiB │
└────────────────────────────┴─────────┴──────────┘
For the latest full release notes, please visit:
https://cloud.google.com/sdk/release_notes
Do you want to continue (Y/n)?
╔════════════════════════════════════════════════════════════╗
╠═ Creating update staging area ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: kubectl ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: kubectl (Mac OS X, x86_64) ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Creating backup and activating new installation ═╣
╚════════════════════════════════════════════════════════════╝
Performing post processing steps...done.
Update done!
</code></pre></div></div>
<h3 id="membuat-cluster">Membuat Cluster</h3>
<p>Pembuatan cluster bisa dilakukan melalui tampilan web Google Cloud Platform ataupun command line <code class="language-plaintext highlighter-rouge">gcloud</code>. Saya lebih suka pendekatan ketik melalui command line daripada klik di web. Berikut adalah perintah untuk membuat cluster baru.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud container clusters create belajar-ci
</code></pre></div></div>
<p>Outputnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating cluster belajar-ci...done.
Created [https://container.googleapis.com/v1/projects/belajar-ci/zones/asia-east1-a/clusters/belajar-ci].
kubeconfig entry generated for belajar-ci.
NAME ZONE MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
belajar-ci asia-east1-a 1.5.6 104.155.233.44 n1-standard-1 1.5.6 3 RUNNING
</code></pre></div></div>
<h3 id="menyiapkan-volume">Menyiapkan Volume</h3>
<p>Konfigurasi kubernetes yang saya buat menggunakan persistent volume supaya datanya tidak hilang pada saat container dimatikan, baik karena upgrade, masalah terhadap node, pindah node, dan sebagainya. Persistent volume sudah disediakan Google Cloud Platform, kita tinggal buat saja dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute disks create --size=10GB --zone=asia-east1-a belajar-ci-pv-1
</code></pre></div></div>
<p>Persistent volume ini akan terlihat seperti partisi harddisk biasa dalam container kita. Sehingga bisa kita mount ke folder mana saja di dalam sistem operasi container. Kita akan bahas lebih lanjut deklarasi dan penggunaannya di bawah nanti.</p>
<p>Perintah di atas akan membuat partisi harddisk dengan ukuran 10GB yang berlokasi di region <code class="language-plaintext highlighter-rouge">asia-east1-a</code>. Saya samakan dengan setting default project kita di atas.</p>
<p>Output perintah di atas seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Created [https://www.googleapis.com/compute/v1/projects/belajar-ci/zones/asia-east1-a/disks/belajar-ci-pv-1].
NAME ZONE SIZE_GB TYPE STATUS
belajar-ci-pv-1 asia-east1-a 10 pd-standard READY
New disks are unformatted. You must format and mount a disk before it
can be used. You can find instructions on how to do this at:
https://cloud.google.com/compute/docs/disks/add-persistent-disk#formatting
</code></pre></div></div>
<p>Walaupun disitu dinyatakan bahwa kita perlu memformat partisi tersebut, abaikan saja. Konfigurasi kita nanti akan otomatis melakukan formatting.</p>
<p>Buat satu lagi untuk menampung hasil upload di aplikasi web. Caranya sama, ganti saja <code class="language-plaintext highlighter-rouge">belajar-ci-pv-1</code> menjadi <code class="language-plaintext highlighter-rouge">belajar-ci-pv-2</code>.</p>
<p>Selesai sudah proses setup Google Cloud SDK, instalasi Kubernetes, dan pembuatan cluster lengkap dengan persistent volumenya. Sekarang kita lihat konfigurasi deploymentnya.</p>
<h2 id="konfigurasi-deployment">Konfigurasi Deployment</h2>
<p>Agar rapi, konfigurasi kita pisahkan menjadi 3 file :</p>
<ul>
<li>konfigurasi persistent volume</li>
<li>konfigurasi database</li>
<li>konfigurasi aplikasi web</li>
</ul>
<p>Mari kita lihat satu persatu</p>
<h3 id="persistent-volume">Persistent Volume</h3>
<p>Pada dasarnya, konfigurasi ini berisi deklarasi untuk persistent disk yang sudah kita buat pada langkah sebelumnya. Kita perlu mendeklarasikannya supaya bisa dipakai di konfigurasi lainnya. Berikut <a href="https://github.com/endymuhardin/belajar-ci/blob/master/k8s/persistent-volume.yml">isi file <code class="language-plaintext highlighter-rouge">persistent-volume.yml</code></a>. Nama file bebas, silahkan pilih yang Anda suka.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># gcloud compute disks create --size=10GB --zone=asia-east1-a belajar-ci-pv-1</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">PersistentVolume</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci-pv-1</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">capacity</span><span class="pi">:</span>
<span class="na">storage</span><span class="pi">:</span> <span class="s">10Gi</span>
<span class="na">accessModes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ReadWriteOnce</span>
<span class="na">gcePersistentDisk</span><span class="pi">:</span>
<span class="na">pdName</span><span class="pi">:</span> <span class="s">belajar-ci-pv-1</span>
<span class="na">fsType</span><span class="pi">:</span> <span class="s">ext4</span>
<span class="nn">---</span>
<span class="c1"># gcloud compute disks create --size=10GB --zone=asia-east1-a belajar-ci-pv-2</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">PersistentVolume</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci-pv-2</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">capacity</span><span class="pi">:</span>
<span class="na">storage</span><span class="pi">:</span> <span class="s">10Gi</span>
<span class="na">accessModes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ReadWriteOnce</span>
<span class="na">gcePersistentDisk</span><span class="pi">:</span>
<span class="na">pdName</span><span class="pi">:</span> <span class="s">belajar-ci-pv-2</span>
<span class="na">fsType</span><span class="pi">:</span> <span class="s">ext4</span>
</code></pre></div></div>
<p>Kita mendeklarasikan dua persistent volume, sesuai command kita jalankan sebelumnya. Isi file sudah cukup jelas, yaitu menyebutkan:</p>
<ul>
<li>metadata : nama persistent volume, kita buat supaya nanti gampang dicari dengan Selector</li>
<li><code class="language-plaintext highlighter-rouge">spec.capacity.storage</code> : kapasitas disk. Kita gunakan saja 10GB supaya hemat biaya</li>
<li>
<p><code class="language-plaintext highlighter-rouge">spec.accessModes</code> : ijin akses container untuk menggunakan volume ini. Ada tiga opsi:</p>
<ul>
<li>read only many (ROX) : bisa dibaca banyak node, tapi tidak boleh ditulis. Hanya bisa baca isinya</li>
<li>read write once (RWO) : bisa digunakan secara read-write hanya oleh satu node</li>
<li>read write many (RWX) : bisa dimount oleh banyak node, semuanya boleh menulis data</li>
</ul>
<p>access modes ini hanya dijalankan salah satu saja, tidak boleh misalnya kita pakai ROX dan RWX sekaligus. Lebih detail bisa dibaca <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes">di dokumentasinya</a></p>
</li>
<li><code class="language-plaintext highlighter-rouge">gcePersistentDisk.pdName</code> : mapping ke volume asli yang kita buat dengan perintah <code class="language-plaintext highlighter-rouge">gcloud compute disks create</code> tadi.</li>
<li><code class="language-plaintext highlighter-rouge">gcePersistentDisk.fsType</code> : jenis filesystem</li>
</ul>
<h3 id="database-server">Database Server</h3>
<p>Konfigurasi database kita definisikan di file <code class="language-plaintext highlighter-rouge">dbserver.yml</code>. File ini terdiri dari 3 bagian:</p>
<ul>
<li>persistent volume claim : yaitu request untuk persistent volume dengan</li>
<li>konfigurasi instance MySQL</li>
<li>mengekspos instance MySQL menjadi service, agar bisa diakses oleh aplikasi web</li>
</ul>
<p>Berikut konfigurasi persistent volume claim</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">PersistentVolumeClaim</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci-pv-claim-db</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">accessModes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ReadWriteOnce</span>
<span class="na">resources</span><span class="pi">:</span>
<span class="na">requests</span><span class="pi">:</span>
<span class="na">storage</span><span class="pi">:</span> <span class="s">10Gi</span>
</code></pre></div></div>
<p>Penjelasannya:</p>
<ul>
<li>
<p>kind : adalah jenis konfigurasi. Dalam hal ini, kita membuat konfigurasi untuk PersistentVolumeClaim, yaitu permintaan terhadap persistent volume. Sekali sudah diberikan terhadap claim, maka volume tersebut tidak bisa lagi di-claim orang lain. Kecuali tipe volumenya <code class="language-plaintext highlighter-rouge">Read Write Many</code>.</p>
</li>
<li>metadata : nama dan label supaya mudah diquery oleh selector dan pod</li>
<li>spec.accessModes : volume ini kita mount dengan mode ReadWriteOnce, artinya hanya ada satu pod yang bisa menggunakannya (mount) dalam mode baca tulis (read write).</li>
<li>spec.resources.request.storage : kapasitas yang dibutuhkan Kubernetes akan melihat ketersediaan volume, dan memberikannya kepada yang meminta claim. Yang harus diperhatikan di sini, bisa saja volume berkapasitas 200GB diberikan pada yang meminta 20GB saja.</li>
</ul>
<p>Selanjutnya, kita lihat konfigurasi MySQL Instance</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">extensions/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci-db</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">template</span><span class="pi">:</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">tier</span><span class="pi">:</span> <span class="s">db</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">mysql</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">mysql:latest</span>
<span class="na">args</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">--ignore-db-dir=lost+found"</span>
<span class="na">volumeMounts</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">persistent-storage-db</span>
<span class="na">mountPath</span><span class="pi">:</span> <span class="s">/var/lib/mysql</span>
<span class="na">env</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name </span><span class="pi">:</span> <span class="s">MYSQL_RANDOM_ROOT_PASSWORD</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">yes'</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">MYSQL_DATABASE</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">belajar'</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">MYSQL_USER</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">belajar'</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">MYSQL_PASSWORD</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">java'</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">persistent-storage-db</span>
<span class="na">persistentVolumeClaim</span><span class="pi">:</span>
<span class="na">claimName</span><span class="pi">:</span> <span class="s">belajar-ci-pv-claim-db</span>
</code></pre></div></div>
<p>Penjelasannya :</p>
<ul>
<li>kind : Deployment. Ini adalah konfigurasi untuk jenis pod atau container instance.</li>
<li>spec.replicas : jumlah instance yang diinginkan. Kita cukup menyebutkan mau berapa instance, dan kemudian Kubernetes akan membuatkannya dan mendistribusikannya di folder yang sesuai.</li>
<li>spec.spec.containers : konfigurasi docker container. Kita akan gunakan docker image mysql:latest, memberikan persistent volume yang akan dimount ke folder /var/lib/mysql. Kita juga sediakan environment variable untuk mengatur nama database, username, dan password untuk digunakan di aplikasi kita.</li>
<li>spec.volumes : konfigurasi untuk menggunakan PVC yang sudah kita deklarasi di atas.</li>
</ul>
<p>Berikutnya adalah konfigurasi service, agar database kita bisa diakses dari aplikasi web</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci-db-service</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">tier</span><span class="pi">:</span> <span class="s">db</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">port</span><span class="pi">:</span> <span class="m">3306</span>
</code></pre></div></div>
<p>Tidak ada yang istimewa di sini. Selain label, kita cuma mendeklarasikan bahwa port 3306 akan diakses oleh pod lain. Service ini bisa terarah ke pod database kita karena di selectornya disebutkan bahwa dia akan terhubung ke pod yang memiliki label <code class="language-plaintext highlighter-rouge">app=belajar-ci</code> dan <code class="language-plaintext highlighter-rouge">tier=db</code></p>
<h3 id="aplikasi-web">Aplikasi Web</h3>
<p>Secara struktur, konfigurasi aplikasi web tidak jauh berbeda. Hanya beda di image saja dan service agar bisa diakses dari internet. File konfigurasinya kita simpan di file <code class="language-plaintext highlighter-rouge">appserver.yml</code>.</p>
<p>Ini adalah konfigurasi PVCnya</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">PersistentVolumeClaim</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci-pv-claim-app</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">accessModes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ReadWriteOnce</span>
<span class="na">resources</span><span class="pi">:</span>
<span class="na">requests</span><span class="pi">:</span>
<span class="na">storage</span><span class="pi">:</span> <span class="s">10Gi</span>
</code></pre></div></div>
<p>Sama seperti database, ini adalah request volume sebesar 10 GB. Tidak ada yang baru di sini, mari lanjut ke konfigurasi pod.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">extensions/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci-app</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">template</span><span class="pi">:</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">tier</span><span class="pi">:</span> <span class="s">web</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">endymuhardin/belajar-ci</span>
<span class="na">imagePullPolicy</span><span class="pi">:</span> <span class="s">Always</span>
<span class="na">volumeMounts</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">persistent-storage-app</span>
<span class="na">mountPath</span><span class="pi">:</span> <span class="s">/var/lib/belajar-ci</span>
<span class="na">env</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">SPRING_DATASOURCE_URL</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">jdbc:mysql://belajar-ci-db-service/belajar'</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">UPLOAD_LOCATION</span>
<span class="na">value</span><span class="pi">:</span> <span class="s">/var/lib/belajar-ci</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">persistent-storage-app</span>
<span class="na">persistentVolumeClaim</span><span class="pi">:</span>
<span class="na">claimName</span><span class="pi">:</span> <span class="s">belajar-ci-pv-claim-app</span>
</code></pre></div></div>
<p>Kita bahas yang berbeda saja:</p>
<ul>
<li>spec.container.image : kita ambil dari image yang sudah kita push ke DockerHub.</li>
<li>imagePullPolicy : karena aplikasi masih versi development, maka kita suruh kubernetes untuk selalu mengambil versi terbaru.</li>
<li>SPRING_DATASOURCE_URL : hostname database kita arahkan ke nama service database. Urusan alamat IP nanti akan ditangani Kubernetes.</li>
</ul>
<p>Sisanya sama, yaitu metadata dan setting volume.</p>
<p>Selanjutnya, konfigurasi Service supaya aplikasi web kita ini bisa diakses dari internet.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci-app-service</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">tier</span><span class="pi">:</span> <span class="s">web</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">LoadBalancer</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>
<span class="na">targetPort</span><span class="pi">:</span> <span class="m">8080</span>
</code></pre></div></div>
<p>Pada konfigurasi di atas, kita membuat Service yang akan menghubungkan dunia luar dengan pod yang memiliki selector <code class="language-plaintext highlighter-rouge">app=belajar-ci</code> dan <code class="language-plaintext highlighter-rouge">tier=web</code>. Selector ini mengarah ke deployment aplikasi web kita. Di situ juga kita sebutkan bahwa port <code class="language-plaintext highlighter-rouge">80</code> dari luar akan diteruskan ke port <code class="language-plaintext highlighter-rouge">8080</code> di aplikasi web kita.</p>
<h2 id="menjalankan-deployment">Menjalankan Deployment</h2>
<p>Ketiga file konfigurasi tadi kita satukan dalam satu folder yang bernama <code class="language-plaintext highlighter-rouge">k8s</code>. Nama foldernya sebetulnya bebas saja. Lalu kita jalankan dengan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply -f k8s
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service "belajar-ci-app-service" created
persistentvolumeclaim "belajar-ci-pv-claim-app" created
deployment "belajar-ci-app" created
service "belajar-ci-db-service" created
persistentvolumeclaim "belajar-ci-pv-claim-db" created
deployment "belajar-ci-db" created
persistentvolume "belajar-ci-pv-1" created
persistentvolume "belajar-ci-pv-2" created
</code></pre></div></div>
<p>Perintah di atas akan langsung selesai, walaupun sebenarnya kubernetes belum selesai menjalankan semua container dan services yang kita mau. Untuk melihat statusnya, jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl get deployment,pod,svc,endpoints,pvc -l app=belajar-ci
</code></pre></div></div>
<p>Perintah di atas menampilkan status untuk object <code class="language-plaintext highlighter-rouge">deployment</code>, <code class="language-plaintext highlighter-rouge">pod</code>, <code class="language-plaintext highlighter-rouge">service</code>, <code class="language-plaintext highlighter-rouge">endpoints</code>, dan <code class="language-plaintext highlighter-rouge">persistent volume claim</code> yang memiliki label <code class="language-plaintext highlighter-rouge">app=belajar-ci</code>. Pemasangan label ini akan kita bahas di bagian konfigurasi di bawah. Berikut adalah output dari perintah di atas</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/belajar-ci-app 1 1 1 0 5s
deploy/belajar-ci-db 1 1 1 0 2s
NAME READY STATUS RESTARTS AGE
po/belajar-ci-app-2571813143-c8l5l 0/1 Pending 0 5s
po/belajar-ci-db-588835421-3tv1m 0/1 Pending 0 2s
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/belajar-ci-app-service 10.47.250.205 <pending> 80:30057/TCP 7s
svc/belajar-ci-db-service 10.47.253.28 <none> 3306/TCP 4s
NAME ENDPOINTS AGE
ep/belajar-ci-app-service <none> 7s
ep/belajar-ci-db-service <none> 4s
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
pvc/belajar-ci-pv-claim-app Pending 6s
pvc/belajar-ci-pv-claim-db Pending 3s
</code></pre></div></div>
<p>Kita bisa lihat bahwa statusnya masih pending semua. Coba jalankan lagi beberapa kali sampai statusnya berubah.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/belajar-ci-app 1 1 1 1 18m
deploy/belajar-ci-db 1 1 1 1 18m
NAME READY STATUS RESTARTS AGE
po/belajar-ci-app-2571813143-c8l5l 1/1 Running 0 18m
po/belajar-ci-db-588835421-3tv1m 1/1 Running 0 18m
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/belajar-ci-app-service 10.47.252.194 104.155.193.131 80:30660/TCP 1m
svc/belajar-ci-db-service 10.47.253.28 <none> 3306/TCP 18m
NAME ENDPOINTS AGE
ep/belajar-ci-app-service 10.44.0.3:8080 1m
ep/belajar-ci-db-service 10.44.2.4:3306 18m
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
pvc/belajar-ci-pv-claim-app Bound belajar-ci-pv-2 10Gi RWO 18m
pvc/belajar-ci-pv-claim-db Bound belajar-ci-pv-1 10Gi RWO 18m
</code></pre></div></div>
<p>Kita bisa lihat bahwa kolom <code class="language-plaintext highlighter-rouge">EXTERNAL-IP</code> sudah terisi, tidak lagi <code class="language-plaintext highlighter-rouge">pending</code>. Artinya, kita sudah bisa mengakses aplikasi kita di IP tersebut. Coba arahkan ke <code class="language-plaintext highlighter-rouge">http://104.155.193.131/api/product/</code>, harusnya kita mendapatkan output data dari database seperti ini</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"content"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"p001"</span><span class="p">,</span><span class="w">
</span><span class="nl">"code"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"P-001"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"Product 001"</span><span class="p">,</span><span class="w">
</span><span class="nl">"price"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mf">101001.01</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="p">],</span><span class="w">
</span><span class="nl">"last"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalElements"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalPages"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"sort"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"first"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"numberOfElements"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"size"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w">
</span><span class="nl">"number"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Untuk mengetes <code class="language-plaintext highlighter-rouge">persistent volume</code>, kita coba insert record baru ke aplikasi dengan mengirim HTTP request ke server dengan ketentuan:</p>
<ul>
<li>method : POST</li>
<li>url : http://104.155.193.131/api/product/</li>
<li>Content Type : application/json</li>
<li>
<p>Request Body</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"code"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"P-999"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"Product 999"</span><span class="p">,</span><span class="w">
</span><span class="nl">"price"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mf">909009.99</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div> </div>
</li>
</ul>
<p>HTTP request bisa dikirim menggunakan aplikasi seperti Postman atau Rest Console.</p>
<p>Pastikan datanya masuk dengan mengakses lagi <code class="language-plaintext highlighter-rouge">http://104.155.193.131/api/product/</code> dari browser. Harusnya datanya sudah bertambah.</p>
<p>Data ini akan terus ada selama gcloud disk masih ada. Kita bisa menghapus semua aplikasi kita dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl delete deployment,pod,svc,endpoints,pvc -l app=belajar-ci
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deployment "belajar-ci-app" deleted
deployment "belajar-ci-db" deleted
service "belajar-ci-app-service" deleted
service "belajar-ci-db-service" deleted
persistentvolumeclaim "belajar-ci-pv-claim" deleted
</code></pre></div></div>
<p>Hapus juga persistent volume dalam cluster.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl delete pv belajar-ci-pv
</code></pre></div></div>
<p>Outputnya :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>persistentvolume "belajar-ci-pv" deleted
</code></pre></div></div>
<p>Perintah tadi akan menghapus semua komponen aplikasi. Kita bahkan juga bisa menghapus clusternya sekalian</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud container clusters delete belajar-ci
</code></pre></div></div>
<p>Outputnya :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The following clusters will be deleted.
- [belajar-ci] in [asia-east1-a]
Do you want to continue (Y/n)?
Deleting cluster belajar-ci...done.
Deleted [https://container.googleapis.com/v1/projects/belajar-ci/zones/asia-east1-a/clusters/belajar-ci].
</code></pre></div></div>
<p>Walaupun clusternya sudah kita hapus, tapi disknya tetap ada. Coba kita cek</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute disks list
</code></pre></div></div>
<p>Outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME ZONE SIZE_GB TYPE STATUS
belajar-ci-pv-1 asia-east1-a 10 pd-standard READY
belajar-ci-pv-2 asia-east1-a 10 pd-standard READY
</code></pre></div></div>
<p>Selama disk ini tidak kita hapus, data produk sebanyak 2 record tadi akan tetap ada. Bisa dibuktikan dengan mengulangi pembuatan cluster dan deployment aplikasi. Setelah mendapatkan <code class="language-plaintext highlighter-rouge">EXTERNAL-IP</code> (yang belum tentu sama dengan IP terdahulu), kita bisa akses aplikasinya dan mendapati ada 2 data produk di sana.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah deployment aplikasi ke Google Container Engine (GKE) dengan menggunakan Kubernetes. Setelah kita bisa melakukannya secara manual, pada artikel berikutnya kita akan <a href="https://software.endy.muhardin.com/devops/gitlab-ci-kubernetes-gke">otomasi menggunakan proses Continuous Delivery</a>.</p>
<p>Seperti biasa, seluruh kode program bisa diakses <a href="http://github.com/endymuhardin/belajar-ci">di Github</a></p>
<p>Semoga bermanfaat, stay tuned…</p>
Workflow Pengembangan Aplikasi dengan Docker2017-04-03T07:00:00+07:00https://software.endy.muhardin.com/devops/docker-workflow<p>Pada <a href="http://software.endy.muhardin.com/linux/intro-docker/">artikel terdahulu</a>, kita sudah membahas apa itu docker, bagaimana cara instalasinya, dan cara membuat docker image sederhana. Kali ini, kita akan lanjutkan tentang cara pemakaian docker untuk mendistribusikan aplikasi yang sudah kita buat.</p>
<p>Di artikel ini, kita akan membahas tentang:</p>
<ul>
<li>Use case / kegunaan docker dalam pengembangan aplikasi</li>
<li>Workflow distribusi aplikasi dengan Docker</li>
<li>Best practices arsitektur aplikasi yang menggunakan docker</li>
</ul>
<!--more-->
<h2 id="kegunaan-docker">Kegunaan Docker</h2>
<p>Sebelum menggunakan suatu teknologi, terutama kita harus mempertanyakan terlebih dulu:</p>
<blockquote>
<p>Apa manfaatnya bila saya menggunakan teknologi ini?
Apa keunggulannya dibandingkan cara yang biasa saya gunakan?</p>
</blockquote>
<p>Untuk menjawab pertanyaan di atas, mari kita lihat dulu workflow yang biasa kita gunakan mulai dari coding sampai aplikasi bisa digunakan user (naik production). Ada beberapa langkah berikut, yaitu:</p>
<ol>
<li>
<p>Mempaketkan aplikasi sesuai dengan bahasa pemrograman / framework yang digunakan. Kalau kita pakai Java, hasil akhir dari aplikasi kita biasanya berupa file <code class="language-plaintext highlighter-rouge">jar</code> atau <code class="language-plaintext highlighter-rouge">war</code>.</p>
</li>
<li>
<p>Menyiapkan environment untuk menjalankan aplikasi kita. Yang biasanya terdiri dari:</p>
<ul>
<li>menyiapkan server, baik berupa fisik maupun virtual</li>
<li>menginstal sistem operasi</li>
<li>menginstal runtime bahasa pemrograman yang kita gunakan, misalnya Java SDK, <code class="language-plaintext highlighter-rouge">mod_php</code>, <code class="language-plaintext highlighter-rouge">mod_passanger</code>, dan lainnya</li>
<li>menginstal layanan yang digunakan aplikasi, misalnya database server, mail server, message broker, dan sebagainya</li>
<li>mengkonfigurasi layanan tambahan tersebut (setting pemakaian resource, membuat username/password, membuat database/queue/topic, dsb)</li>
<li>mengintegrasikannya dengan aplikasi kita</li>
</ul>
</li>
<li>
<p>Menjalankan aplikasi. Biasanya aplikasi dideploy dalam berbagai environment (development, testing, staging, production).</p>
</li>
</ol>
<p>Berbagai kegiatan di atas, terutama di poin #2, secara tradisional dilakukan oleh seorang sysadmin. Dia melakukan instalasi, menjalankan rangkaian perintah command line, copy/paste script, dan sebagainya. Semuanya dilakukan dengan manual.</p>
<p>Kelemahan dari cara manual ini adalah:</p>
<ul>
<li>tidak repeatable. Bila disuruh mengulangi, belum tentu sysadmin tersebut bisa menjalankannya dengan sama persis. Perbedaan satu opsi perintah bisa menjadikan hasil akhirnya berbeda.</li>
<li>rawan terjadi kesalahan.</li>
<li>sulit diotomasi</li>
</ul>
<p>Untuk mengatasi masalah-masalah tersebut, kita bisa menggunakan Docker container. Docker container adalah satu paket image yang sudah berisi:</p>
<ul>
<li>sistem operasi</li>
<li>platform runtime</li>
<li>aplikasi kita</li>
<li>beberapa konfigurasi yang bersifat static</li>
</ul>
<p>Aplikasi yang sudah kita paketkan menjadi docker image bisa langsung dijalankan tanpa harus melalui 3 langkah di atas. Untuk layanan tambahan seperti database server, mail server, dan sebagainya, kita juga bisa mendapatkan docker image yang siap pakai. Image-image ini biasanya dibuat langsung oleh pembuat aplikasi tersebut. Misalnya image <code class="language-plaintext highlighter-rouge">ubuntu</code> dibuat langsung oleh karyawan Canonical. Karena mereka yang paham luar-dalam tentang aplikasinya, maka kita bisa yakin bahwa image yang dibuat berkualitas baik.</p>
<p>Proses pembuatan docker image ini juga bisa kita otomasi, sehingga bisa dijalankan dalam siklus continuous integration seperti yang sudah kita bahas di artikel terdahulu, misalnya <a href="http://software.endy.muhardin.com/java/jenkins-gitlab/">dengan Jenkins</a> atau <a href="http://software.endy.muhardin.com/java/project-bootstrap-02/">menggunakan Travis</a>. Adapun memasukkan pembuatan docker image dalam proses CI ini akan kita bahas pada artikel tersendiri di kemudian hari.</p>
<p>Jadi, dengan menggunakan Docker, kita bisa menjalankan aplikasi kita dengan lebih cepat, lebih konsisten, repeatable, dan minim campur tangan manusia.</p>
<h2 id="workflow-docker">Workflow Docker</h2>
<p>Sebagai contoh kasus, kita akan membuat docker image untuk aplikasi contoh yang sudah sering kita tampilkan pada artikel terdahulu, yaitu <a href="https://github.com/endymuhardin/belajar-ci">aplikasi <code class="language-plaintext highlighter-rouge">belajar-ci</code></a>. Aplikasi ini sudah memiliki beberapa kelengkapan standar aplikasi pada umumnya, yaitu:</p>
<ul>
<li>menggunakan database MySQL atau PostgreSQL</li>
<li>berupa aplikasi web</li>
<li>ada fitur CRUD (create read update delete)</li>
</ul>
<p>Workflow development sampai deploymentnya adalah sebagai berikut:</p>
<ol>
<li>Kita lakukan coding seperti biasa, testing, dan kita coba jalankan di laptop/PC sendiri</li>
<li>Setelah sukses, kita akan membuat docker image</li>
<li>Upload docker image tersebut ke docker repository. Yaitu database penyimpanan image-image docker.</li>
<li>Siapkan server untuk menjalankan aplikasi kita</li>
<li>Jalankan database server dari docker image ke server yang sudah disiapkan</li>
<li>Jalankan aplikasi kita dari docker image ke server tersebut</li>
<li>Test aplikasi apakah sudah bisa diakses</li>
</ol>
<p>Untuk langkah pertama, tentu tidak perlu kita demokan lagi. Mari kita langsung jalankan langkah kedua.</p>
<h3 id="membuat-docker-image">Membuat Docker Image</h3>
<p>Docker image adalah aplikasi kita yang sudah lengkap dengan berbagai kebutuhan agar dia bisa jalan. Untuk aplikasi Java, berarti kita membutuhkan :</p>
<ul>
<li>sistem operasi. Silahkan pilih yang Anda sukai. Biasanya saya menggunakan keluarga Debian seperti Debian itu sendiri atau Ubuntu.</li>
<li>Java Runtime. Silahkan pilih mau pakai OpenJDK atau Oracle JDK.</li>
</ul>
<p>Karena docker image adalah produk akhir, kita tidak perlu menyertakan kelengkapan kompilasi seperti Maven atau Gradle. Bila ini kita sertakan, maka image kita akan menjadi terlalu besar dan kurang efisien. Kita ingin image yang seramping dan seminimal mungkin.</p>
<p>Docker image dibuat berlapis-lapis. Untuk menjalankan aplikasi Java kita, tidak perlu kita melakukan instalasi sistem operasi dan Java SDK. Cukup cari image dasar yang sudah memiliki keduanya. Untuk contoh ini, kita akan gunakan <a href="https://hub.docker.com/_/openjdk/">image bernama <code class="language-plaintext highlighter-rouge">openjdk:latest</code></a>. Kita akan membuat image aplikasi kita di atas image ini.</p>
<p>Spesifikasi image yang akan dibuat ditulis dalam file <code class="language-plaintext highlighter-rouge">Dockerfile</code>. Berikut adalah isi <code class="language-plaintext highlighter-rouge">Dockerfile</code> untuk aplikasi kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM openjdk:latest
ADD target/belajar-ci.jar /opt/app.jar
RUN bash -c 'touch /opt/app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/opt/app.jar"]
</code></pre></div></div>
<p>Mari kita bahas baris per baris:</p>
<ol>
<li>
<p>Baris pertama : <code class="language-plaintext highlighter-rouge">FROM openjdk:latest</code> artinya image kita ini akan dibangun di atas image <code class="language-plaintext highlighter-rouge">openjdk</code> dengan tag <code class="language-plaintext highlighter-rouge">latest</code>. Ada berbagai varian yang disediakan oleh pembuat image ini, lebih lengkapnya bisa dipilih <a href="https://hub.docker.com/_/openjdk/">di lamannya dalam Docker Hub</a>.</p>
</li>
<li>
<p>Baris kedua, <code class="language-plaintext highlighter-rouge">ADD target/belajar-ci.jar /opt/app.jar</code> artinya kita mengambil file <code class="language-plaintext highlighter-rouge">target/belajar-ci.jar</code> yang dihasilkan dari proses compile dan memasukkannya ke dalam container image dengan nama file <code class="language-plaintext highlighter-rouge">/opt/app.jar</code>.</p>
</li>
<li>
<p>Baris ketiga, kita melakukan <code class="language-plaintext highlighter-rouge">touch</code> terhadap file aplikasi kita supaya Docker mengubah metadata tanggal perubahan terakhir file (modification time). Metadata ini dibutuhkan bila ada file <code class="language-plaintext highlighter-rouge">html</code>, <code class="language-plaintext highlighter-rouge">css</code>, atau file static lain di aplikasi kita yang ingin dicache.</p>
</li>
<li>
<p>Kita menjalankan aplikasi pada saat docker image dijalankan, yaitu dengan perintah <code class="language-plaintext highlighter-rouge">java -Djava.security.egd=file:/dev/./urandom -jar /opt/app.jar</code>. Bila ada opsi lain seperti pengaturan memori (<code class="language-plaintext highlighter-rouge">Xms</code> dan <code class="language-plaintext highlighter-rouge">Xmx</code>), kita bisa tambahkan di sini.</p>
</li>
</ol>
<p>Seperti dijelaskan di atas, yang kita sertakan hanyalah hasil akhir saja, yaitu file <code class="language-plaintext highlighter-rouge">jar</code> yang ada di dalam folder <code class="language-plaintext highlighter-rouge">target</code>. Oleh karena itu, sebelum membuat image, pastikan kita sudah menjalankan maven build dengan perintah <code class="language-plaintext highlighter-rouge">mvn clean package</code>.</p>
<p>Untuk bisa menjalankan docker build, kita harus sudah memiliki docker machine. Bila belum ada, kita bisa buat di local dengan menggunakan VirtualBox</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine create --driver virtualbox docker-vbox
</code></pre></div></div>
<p>atau di Digital Ocean dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine create --driver digitalocean --digitalocean-size 1gb --digitalocean-access-token yaddayaddayadda docker-ocean
</code></pre></div></div>
<p>Setelah membuat docker machine, inisialisasi dulu environment variable di laptop kita</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eval $(docker-machine env docker-ocean)
</code></pre></div></div>
<p>Ganti <code class="language-plaintext highlighter-rouge">docker-ocean</code> dengan <code class="language-plaintext highlighter-rouge">docker-vbox</code> atau apapun nama yang kita berikan pada command <code class="language-plaintext highlighter-rouge">create</code> tadi.</p>
<p>Setelah <code class="language-plaintext highlighter-rouge">docker-machine</code> siap dan proses build Maven selesai, kita bisa membuat Docker image dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build -t endymuhardin/belajar-ci .
</code></pre></div></div>
<blockquote>
<p>PERHATIAN !!! Jangan lupa ada titik di paling belakang. Banyak error disebabkan lupa pakai titik</p>
</blockquote>
<p>Titik di belakang artinya proses build dilakukan di folder tempat sekarang command prompt berada. Docker akan mencari file bernama <code class="language-plaintext highlighter-rouge">Dockerfile</code> di folder ini. Demikian juga, file yang kita <code class="language-plaintext highlighter-rouge">ADD</code> dalam <code class="language-plaintext highlighter-rouge">Dockerfile</code> dicari di folder bernama <code class="language-plaintext highlighter-rouge">target</code> relatif dari lokasi folder kerja (yaitu <code class="language-plaintext highlighter-rouge">.</code> atau folder tempat command prompt berada).</p>
<blockquote>
<p>PERINGATAN !!! Perintah di atas membutuhkan koneksi internet upstream yang tinggi. Pastikan Anda menggunakan paket internet non-kuota berkecepatan upload yang tinggi.</p>
</blockquote>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Sending build context to Docker daemon 32.37 MB
Step 1/4 : FROM openjdk:latest
latest: Pulling from library/openjdk
6d827a3ef358: Pull complete
2726297beaf1: Pull complete
7d27bd3d7fec: Pull complete
e61641c845ed: Pull complete
cce4cca5b76b: Pull complete
6826227500b0: Pull complete
c03b117ffd91: Pull complete
821a1547b435: Pull complete
Digest: sha256:766764155b350a6fe09c3e9592901523c0c7fd969575e431c5c3373988a1b169
Status: Downloaded newer image for openjdk:latest
---> 4c3d59cc5179
Step 2/4 : ADD target/belajar-ci.jar /opt/app.jar
---> 57d2499a7644
Removing intermediate container 80a87d7b2244
Step 3/4 : RUN bash -c 'touch /opt/app.jar'
---> Running in e909cf439453
---> ddb2da23bea0
Removing intermediate container e909cf439453
Step 4/4 : ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar /opt/app.jar
---> Running in 8870d12261ba
---> 73bbe644ddd0
Removing intermediate container 8870d12261ba
Successfully built 73bbe644ddd0
</code></pre></div></div>
<h3 id="mengupload-image">Mengupload Image</h3>
<p>Aplikasi kita sudah menjadi docker image yang siap dijalankan. Kita bisa upload image ini ke docker registry agar bisa digunakan orang lain. Docker registry yang resmi adalah <a href="https://hub.docker.com">Docker Hub</a>. Untuk bisa mengupload kesana, kita harus punya username dan password dulu. Untuk paket gratisan, kita bisa mendapatkan satu repository private yang gratis. Bila kita butuh lebih banyak, kita bisa gunakan <a href="https://hub.docker.com/billing-plans/">paket berbayar</a>.</p>
<p>Setelah mendaftar dan punya akun, kita bisa mengupload image kita tadi.</p>
<p>Login dulu sebelum upload</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: endymuhardin
Password:
Login Succeeded
</code></pre></div></div>
<p>Setelah itu, upload.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker push endymuhardin/belajar-ci
The push refers to a repository [docker.io/endymuhardin/belajar-ci]
e8f4377e3c8c: Pushed
159fcd9952aa: Pushed
f275a9671865: Mounted from library/openjdk
d41a2873a531: Mounted from library/openjdk
f3d32870c777: Mounted from library/openjdk
6c88f2235003: Mounted from library/openjdk
ca026307de2c: Mounted from library/openjdk
e6562eb04a92: Mounted from library/openjdk
596280599f68: Mounted from library/openjdk
5d6cbe0dbcf9: Mounted from library/openjdk
latest: digest: sha256:4f4d1f5b831f61040fbc25266c897f025b4cdd1f05ca6c5730a56984cbed1688 size: 2424
</code></pre></div></div>
<p>Hasilnya bisa kita lihat di <a href="https://hub.docker.com/r/endymuhardin/belajar-ci/tags/">halaman repository kita di DockerHub</a></p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/docker-workflow/dockerhub-repository.png" alt="Repository Belajar CI" /></p>
<h3 id="menggabungkan-docker-container-dengan-compose">Menggabungkan Docker Container dengan Compose</h3>
<p>Bila pembaca teliti, di <code class="language-plaintext highlighter-rouge">Dockerfile</code> kita tadi tidak ada kita melakukan instalasi database. Padahal aplikasi kita membutuhkan database supaya bisa bekerja dengan baik. Docker image memang sebaiknya tidak rangkap jabatan. Aplikasi ya aplikasi saja, jangan dicampur dengan database. Kita menjalankan image database secara terpisah, kemudian kita sambungkan dengan aplikasi kita.</p>
<p>Untuk memudahkan kita menjalankan beberapa container sekaligus dan menghubungkannya, Docker sudah menyediakan fasilitas yang disebut dengan <code class="language-plaintext highlighter-rouge">Docker Compose</code>. Kita membuat file konfigurasi untuk menjalankan beberapa container sekaligus (misalnya aplikasi kita dan database MySQL) dan menghubungkannya. Berikut adalah file konfigurasinya, kita beri nama <code class="language-plaintext highlighter-rouge">docker-compose.yml</code></p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2.1"</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">mysql</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">mysql:latest</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">MYSQL_ROOT_PASSWORD=admin</span>
<span class="pi">-</span> <span class="s">MYSQL_DATABASE=belajar</span>
<span class="pi">-</span> <span class="s">MYSQL_USER=belajar</span>
<span class="pi">-</span> <span class="s">MYSQL_PASSWORD=java</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/opt/data:/var/lib/mysql</span>
<span class="na">belajarci-app</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">endymuhardin/belajar-ci</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">SPRING_PROFILES_ACTIVE=docker</span>
<span class="na">depends_on</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">mysql</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">80:8080</span>
</code></pre></div></div>
<p>Berikut penjelasan dari file tersebut:</p>
<ul>
<li>
<p>Kita menggunakan konfigurasi versi <code class="language-plaintext highlighter-rouge">2.1</code>. Saat ini versinya sudah mencapai 3. Tapi kita gunakan versi 2.1 yang lebih banyak disupport.</p>
</li>
<li>
<p>Kita mendeklarasikan 2 container service, yaitu database <code class="language-plaintext highlighter-rouge">mysql</code> dan aplikasi kita sendiri yaitu <code class="language-plaintext highlighter-rouge">belajarci-app</code>.</p>
</li>
<li>
<p>Untuk container database, kita mengambil image <code class="language-plaintext highlighter-rouge">mysql:latest</code> dari DockerHub. Kita berikan environment variable untuk menginisialisasi database yang dibutuhkan aplikasi kita. Dokumentasi lengkap cara inisialisasinya bisa dibaca sendiri <a href="https://hub.docker.com/_/mysql/">di DockerHub</a>.</p>
</li>
<li>
<p>Kita mendeklarasikan volume, yaitu mapping folder di host ke folder dalam container. Ini kita lakukan supaya filenya persistent (ada terus). Bila tidak kita mapping ke host, maka pada waktu container dihapus, datanya ikut terhapus. Tentu kita tidak mau hal ini terjadi pada isi database kita. Oleh karena itu, folder <code class="language-plaintext highlighter-rouge">/var/lib/mysql</code> yang biasanya digunakan MySQL untuk menyimpan data, kita mapping ke folder <code class="language-plaintext highlighter-rouge">/opt/data</code> di mesin host.</p>
</li>
<li>
<p>Untuk container aplikasi, kita berikan environment variable dengan nama <code class="language-plaintext highlighter-rouge">SPRING_PROFILES_ACTIVE</code> yang isinya <code class="language-plaintext highlighter-rouge">docker</code>. Ini menyebabkan Spring Boot akan membaca file konfigurasi yang bernama <code class="language-plaintext highlighter-rouge">application-docker.properties</code>. Isinya hanya satu baris sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> spring.datasource.url=jdbc:mysql://mysql/belajar
</code></pre></div> </div>
</li>
<li>
<p>Pada file konfigurasi di atas, kita membutuhkan ada database server yang berjalan dengan nama host <code class="language-plaintext highlighter-rouge">mysql</code>. Docker secara otomatis akan memberi hostname masing-masing container dengan nama servicenya, yaitu <code class="language-plaintext highlighter-rouge">mysql</code> untuk service pertama, dan <code class="language-plaintext highlighter-rouge">belajarci-app</code> untuk service kedua.</p>
</li>
<li>
<p>Agar container aplikasi kita bisa tahu alamat IP container database, kita gunakan konfigurasi <code class="language-plaintext highlighter-rouge">depends_on</code>. Ini akan menyebabkan docker menulis entri di file <code class="language-plaintext highlighter-rouge">/etc/hosts</code> container aplikasi yang berisi mapping hostname <code class="language-plaintext highlighter-rouge">mysql</code> ke alamat IP container database.</p>
</li>
<li>
<p>Terakhir, kita mapping port aplikasi kita yang berjalan di <code class="language-plaintext highlighter-rouge">8080</code> ke port <code class="language-plaintext highlighter-rouge">80</code> di mesin host. Sehingga kita bisa akses aplikasinya dengan url <code class="language-plaintext highlighter-rouge">http://ip-docker-machine/api/product/</code>.</p>
</li>
</ul>
<p>Alamat IP docker-machine bisa didapatkan dengan perintah <code class="language-plaintext highlighter-rouge">docker-machine env docker-ocean</code>. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://138.197.101.3:2376"
export DOCKER_CERT_PATH="/Users/endymuhardin/.docker/machine/machines/docker-ocean"
export DOCKER_MACHINE_NAME="docker-ocean"
# Run this command to configure your shell:
# eval $(docker-machine env docker-ocean)
</code></pre></div></div>
<p>Dengan demikian, aplikasi kita bisa diakses di http://138.197.101.3/api/product/</p>
<h3 id="menjalankan-docker-compose">Menjalankan Docker Compose</h3>
<p>Kita bisa menjalankan rangkaian container kita tadi dengan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker-compose up
</code></pre></div></div>
<p>Dengan perintah di atas, <code class="language-plaintext highlighter-rouge">docker-compose</code> akan menjalankan container <code class="language-plaintext highlighter-rouge">mysql</code> dan <code class="language-plaintext highlighter-rouge">belajarci-app</code>, kemudian menghubungkan keduanya. Kita bisa test hasilnya dengan mengakses alamat IP docker host seperti sudah dijelaskan di atas. Untuk mematikan container, kita bisa tekan <code class="language-plaintext highlighter-rouge">Ctrl-C</code>.</p>
<p>Bila kita ingin <code class="language-plaintext highlighter-rouge">docker-compose</code> sebagai background service (supaya kita bisa mematikan command prompt), tambahkan opsi <code class="language-plaintext highlighter-rouge">-d</code> seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker-compose up -d
</code></pre></div></div>
<p>Bila ada perubahan terhadap kode program aplikasi kita, jalankan proses build maven/gradle seperti biasa</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mvn clean package
</code></pre></div></div>
<p>Kemudian rebuild docker image dan kemudian push. Setelah itu restart container web kita</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker-compose up --no-deps belajarci-app
</code></pre></div></div>
<p>Opsi <code class="language-plaintext highlighter-rouge">--no-deps</code> digunakan agar service lain yang bergantung pada <code class="language-plaintext highlighter-rouge">belajarci-app</code> tidak ikut direstart.</p>
<h3 id="memeriksa-volume-mapping">Memeriksa Volume Mapping</h3>
<p>Pada konfigurasi di atas, kita menggunakan volume mapping agar data dari MySQL kita tersimpan di mesin fisik.</p>
<p>Kita bisa memastikan bahwa MySQL benar-benar menulis ke folder <code class="language-plaintext highlighter-rouge">/opt/data</code> di host dengan cara login ke <code class="language-plaintext highlighter-rouge">docker-machine</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine ssh docker-ocean
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-66-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
2 packages can be updated.
0 updates are security updates.
</code></pre></div></div>
<p>Kemudian, kita lihat isi folder <code class="language-plaintext highlighter-rouge">/opt/data</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@docker-ocean:~# ls -l /opt/data/
total 188480
-rw-r----- 1 999 docker 56 Apr 3 11:28 auto.cnf
drwxr-x--- 2 999 docker 4096 Apr 3 11:28 belajar
-rw------- 1 999 docker 1679 Apr 3 11:28 ca-key.pem
-rw-r--r-- 1 999 docker 1074 Apr 3 11:28 ca.pem
-rw-r--r-- 1 999 docker 1078 Apr 3 11:28 client-cert.pem
-rw------- 1 999 docker 1679 Apr 3 11:28 client-key.pem
-rw-r----- 1 999 docker 1328 Apr 3 11:28 ib_buffer_pool
-rw-r----- 1 999 docker 79691776 Apr 3 11:28 ibdata1
-rw-r----- 1 999 docker 50331648 Apr 3 11:29 ib_logfile0
-rw-r----- 1 999 docker 50331648 Apr 3 11:28 ib_logfile1
-rw-r----- 1 999 docker 12582912 Apr 3 11:28 ibtmp1
drwxr-x--- 2 999 docker 4096 Apr 3 11:28 mysql
drwxr-x--- 2 999 docker 4096 Apr 3 11:28 performance_schema
-rw------- 1 999 docker 1679 Apr 3 11:28 private_key.pem
-rw-r--r-- 1 999 docker 451 Apr 3 11:28 public_key.pem
-rw-r--r-- 1 999 docker 1078 Apr 3 11:28 server-cert.pem
-rw------- 1 999 docker 1675 Apr 3 11:28 server-key.pem
drwxr-x--- 2 999 docker 12288 Apr 3 11:28 sys
</code></pre></div></div>
<p>Yang biasa menggunakan MySQL pasti familiar dengan nama-nama file dan folder di atas. Ya benar, itu mirip dengan isi folder <code class="language-plaintext highlighter-rouge">/var/lib/mysql</code> yang biasa kita temui di instalasi MySQL standar.</p>
<p>Folder <code class="language-plaintext highlighter-rouge">/opt/data</code> ini akan terus ada selama belum kita hapus secara manual, walaupun containernya kita destroy.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah cara pemaketan aplikasi dengan menggunakan Docker. Seperti kita sudah lihat, dengan cara ini aplikasi kita menjadi lebih mudah dideploy. Tidak perlu lagi repot-repot menginstal Java SDK, database MySQL, konfigurasi user/password database, membuat database, dan lainnya. Instalasi kita di atas juga sudah memisahkan antara host aplikasi dan host database. Walaupun secara fisik masih berada di mesin yang sama, akan tetapi aplikasi kita melihat database berada di mesin berbeda.</p>
<p>Bila nantinya aplikasi kita akan naik production, sysadmin cukup menginisialisasi database container dengan username/password/nama-database yang berbeda dengan yang digunakan developer.</p>
<p>Docker ini sudah didukung berbagai provider cloud yang populer seperti:</p>
<ul>
<li>Digital Ocean</li>
<li>Amazon</li>
<li>Google</li>
<li>Microsoft Azure</li>
<li>dan sebagainya</li>
</ul>
<p>Dengan adanya Docker, kita sebagai developer bisa mendeliver bukan saja aplikasi kita, tapi lengkap dengan environment tempat dia dijalankan, sehingga sysadmin/operation bisa langsung menjalankannya dengan mudah. Mereka tinggal memilih provider hosting yang paling disukai.</p>
<p>Pada artikel selanjutnya insya Allah kita akan membahas tentang <a href="https://software.endy.muhardin.com/devops/gitlab-ci-kubernetes-gke">otomasi proses ini dengan Continuous Integration</a>, dan kemudian clustering dan replikasi aplikasi kita yang sudah dipaketkan dalam container. Stay tuned ;)</p>
Mendeploy Aplikasi dari Jenkins ke VPS2017-03-18T07:00:00+07:00https://software.endy.muhardin.com/java/deploy-jenkins-vps<p>Pada <a href="http://software.endy.muhardin.com/java/deploy-jenkins-pivotal/">artikel sebelumnya</a>, kita telah membahas tentang cara mendeploy dari Jenkins ke layanan PaaS, seperti <a href="https://run.pivotal.io">Pivotal Web Services</a>. Deployment ke PaaS relatif lebih mudah, karena platform (Java SDK, database MySQL) sudah tersedia, sehingga kita tinggal mengunggah dan menjalankan aplikasi saja.</p>
<p>Kali ini, kita akan coba deploy ke layanan IaaS yang lebih sedikit fiturnya. Kita hanya mendapatkan server yang baru terinstal sistem operasi saja. Sehingga kita harus mempersiapkan segala sesuatu yang dibutuhkan oleh aplikasi kita supaya berjalan dengan baik.</p>
<p>Sebagai ilustrasi, saya akan gunakan VPS di Digital Ocean yang relatif mudah digunakan dan terjangkau harganya. Penagihannya juga dihitung perjam, sehingga kalau para pembaca ingin mencoba, bisa langsung praktek dan segera destroy setelah selesai agar tagihan tidak besar. Kita bisa gunakan paket memori 1GB dengan harga $0.015/jam.</p>
<ul id="markdown-toc">
<li><a href="#persiapan-platform" id="markdown-toc-persiapan-platform">Persiapan Platform</a> <ul>
<li><a href="#persiapan-database" id="markdown-toc-persiapan-database">Persiapan Database</a></li>
</ul>
</li>
<li><a href="#deployment-manual" id="markdown-toc-deployment-manual">Deployment Manual</a> <ul>
<li><a href="#executable-jar-file" id="markdown-toc-executable-jar-file">Executable JAR file</a></li>
</ul>
</li>
<li><a href="#deployment-dengan-jenkins" id="markdown-toc-deployment-dengan-jenkins">Deployment dengan Jenkins</a> <ul>
<li><a href="#setup-ssh-keypair" id="markdown-toc-setup-ssh-keypair">Setup SSH Keypair</a></li>
<li><a href="#post-build-action" id="markdown-toc-post-build-action">Post Build Action</a></li>
</ul>
</li>
<li><a href="#penutup" id="markdown-toc-penutup">Penutup</a></li>
<li><a href="#referensi" id="markdown-toc-referensi">Referensi</a></li>
</ul>
<!--more-->
<h2 id="persiapan-platform">Persiapan Platform</h2>
<p>Langkah pertama, kita buat dulu droplet di Digital Ocean. Bagi yang belum punya akunnya bisa <a href="http://www.digitalocean.com/?refcode=c5449509c33a">mendaftar di sini</a>. Kemudian kita buat droplet dengan ukuran 1 GB.</p>
<p>Pembuatan droplet bisa dilakukan melalui antarmuka webnya ataupun melalui <a href="https://www.digitalocean.com/community/tutorials/how-to-use-doctl-the-official-digitalocean-command-line-client">aplikasi <code class="language-plaintext highlighter-rouge">doctl</code></a> yang bisa dijalankan melalui command line. Perintah <code class="language-plaintext highlighter-rouge">doctl</code>nya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>doctl compute droplet create belajar-ci --size 1gb --image ubuntu-16-04-x64 --region nyc1 --ssh-keys 5b:0f:70:36:15:c4:8c:2f:23:06:7c:15:53:b3:ca:28
</code></pre></div></div>
<p>Selanjutnya, kita login melalui ssh ke droplet tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>doctl compute ssh belajar-ci
</code></pre></div></div>
<p>Kita update dulu software yang terinstal</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get update && apt-get upgrade -y
</code></pre></div></div>
<p>Kemudian, kita instalasi paket-paket berikut:</p>
<ul>
<li>Java SDK</li>
<li>PostgreSQL Server</li>
<li>Haveged</li>
</ul>
<p>dengan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install openjdk-8-jdk-headless postgresql haveged -y
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Haveged</code> ini adalah generator entropi. Entropi ini salah satunya dibutuhkan untuk menghasilkan random number untuk keperluan kriptografi. Pada komputer desktop, entropi dapat diambil dari gerakan mouse atau ketikan keyboard. Tapi di server, tidak ada mouse dan keyboard, sehingga butuh waktu lama untuk menghasilkan entropi. Bila kekurangan entropi, aplikasi kita akan terlihat lemot atau hang.</p>
<p>Ini pernah terjadi pada saya. Awalnya saya kira spec VPS kurang. Tapi setelah ditambahkan jadi 2GB pun tetap sama. Kemudian saya cek google</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/springboot-digitalocean/01-google-tomcat-slow.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/springboot-digitalocean/01-google-tomcat-slow.png" alt="Tomcat Slow Digital Ocean" /></a></p>
<p>Klik link paling atas</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/springboot-digitalocean/02-digitalocean-thread.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/springboot-digitalocean/02-digitalocean-thread.png" alt="Forum Digital Ocean" /></a></p>
<p>Scroll ke bawah, nanti ketemu ini</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/springboot-digitalocean/03-answer-1.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/springboot-digitalocean/03-answer-1.png" alt="Solusi di Forum Digital Ocean" /></a></p>
<p>Scroll ke bawah, ketemu lagi testimoni user lain. Bahkan di VPS yang termurah ($5/bulan) juga sudah bisa ngebut.</p>
<p><a href="https://software.endy.muhardin.com/images/uploads/2017/springboot-digitalocean/04-answer-verification.png"><img src="https://software.endy.muhardin.com/images/uploads/2017/springboot-digitalocean/04-answer-verification.png" alt="Testimoni Solusi" /></a></p>
<p>Penjelasan lebih lanjut mengenai apa itu <code class="language-plaintext highlighter-rouge">haveged</code> dan kelayakannya untuk generate secure random bisa dibaca di <a href="https://security.stackexchange.com/questions/34523/is-it-appropriate-to-use-haveged-as-a-source-of-entropy-on-virtual-machines">diskusi Stack Overflow</a>.</p>
<p>Kita juga harus menyiapkan user di sistem operasi untuk menjalankan aplikasi kita. Jangan menjalankan aplikasi dengan user <code class="language-plaintext highlighter-rouge">root</code>, karena mendatangkan resiko keamanan. User yang kita buat ini merupakan <code class="language-plaintext highlighter-rouge">system user</code>, yaitu user khusus dengan ketentuan:</p>
<ul>
<li>tidak boleh login</li>
<li>tidak memiliki grup (grup diset menjadi <code class="language-plaintext highlighter-rouge">nogroup</code>)</li>
<li>tidak bisa mengakses command line (shell diset <code class="language-plaintext highlighter-rouge">/bin/false</code>)</li>
<li>memiliki home folder</li>
</ul>
<p>Untuk membuat user seperti ini, kita gunakan opsi <code class="language-plaintext highlighter-rouge">--system</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adduser --system artivisi
</code></pre></div></div>
<h3 id="persiapan-database">Persiapan Database</h3>
<p>Edit konfigurasi PostgreSQL (biasanya ada di file <code class="language-plaintext highlighter-rouge">/etc/postgresql/9.5/main/pg_hba.conf</code>) menjadi sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local all postgres peer
local all all password
host all all 127.0.0.1/32 password
host all all ::1/128 password
</code></pre></div></div>
<p>Restart PostgreSQL supaya konfigurasi tadi diaplikasikan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service postgresql restart
</code></pre></div></div>
<p>Berikutnya, kita buat user database untuk aplikasi kita. Ganti user dulu menjadi user <code class="language-plaintext highlighter-rouge">postgres</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo su - postgres
</code></pre></div></div>
<p>Buat user dengan privilege membuat database sendiri (opsi <code class="language-plaintext highlighter-rouge">-d</code>)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createuser -d -P aplikasidb
</code></pre></div></div>
<p>Kita akan dimintai password. Masukkan <code class="language-plaintext highlighter-rouge">test123</code> untuk passwordnya.</p>
<p>Selanjutnya, kita bisa kembali menjadi user <code class="language-plaintext highlighter-rouge">root</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exit
</code></pre></div></div>
<p>Buat database untuk aplikasi kita</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PGPASSWORD=test123 createdb -Uaplikasidb -Oaplikasidb -Eutf8 aplikasidb
</code></pre></div></div>
<h2 id="deployment-manual">Deployment Manual</h2>
<p>Sebelum kita otomasi dengan Jenkins, terlebih dulu kita lakukan deployment secara manual untuk memastikan prosedurnya.</p>
<h3 id="executable-jar-file">Executable JAR file</h3>
<p>Kita harus membuat aplikasi kita menjadi file <code class="language-plaintext highlighter-rouge">jar</code> yang bisa didaftarkan menjadi <code class="language-plaintext highlighter-rouge">system service</code>, supaya bisa start otomatis setiap kali server dinyalakan. Spring Boot telah menyediakan fasilitasnya, kita cukup nyalakan dengan mengaktifkan opsi <code class="language-plaintext highlighter-rouge">executable</code>. Edit <code class="language-plaintext highlighter-rouge">pom.xml</code> dan buat konfigurasi seperti ini.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><build></span>
<span class="nt"><finalName></span>${project.artifactId}<span class="nt"></finalName></span>
<span class="nt"><plugins></span>
<span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><configuration></span>
<span class="nt"><executable></span>true<span class="nt"></executable></span>
<span class="nt"></configuration></span>
<span class="nt"></plugin></span>
<span class="nt"></plugins></span>
<span class="nt"></build></span>
</code></pre></div></div>
<p>Selanjutnya build dulu aplikasinya di laptop kita sendiri.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd belajar-ci
mvn clean package -DskipTests
</code></pre></div></div>
<p>Aplikasi yang siap dijalankan ada di folder <code class="language-plaintext highlighter-rouge">target</code>. Kita upload aplikasinya dengan perintah <code class="language-plaintext highlighter-rouge">scp</code>. Di *nix ini sudah tersedia di commandline. Pengguna Windows bisa pakai WinSCP.</p>
<p>Untuk bisa mengirim file, kita membutuhkan alamat IP server yang dituju. Gunakan aplikasi command line Digital Ocean untuk mendapatkan alamat IPnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>doctl compute droplet list
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>42753653 belajar-ci 162.243.7.44 1024 1 30 nyc2 Ubuntu 16.04.2 x64 active
</code></pre></div></div>
<p>Sekarang kita bisa mengunggah filenya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp target/*.jar root@162.243.7.44:/home/artivisi/
</code></pre></div></div>
<p>Untuk mendaftarkan menjadi <code class="language-plaintext highlighter-rouge">system service</code>, kita akan gunakan mekanisme <code class="language-plaintext highlighter-rouge">systemd</code> yang ada di Linux. Buat file konfigurasinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=belajarci
After=syslog.target
[Service]
User=artivisi
ExecStart=/home/artivisi/belajar-ci.jar
SuccessExitStatus=143
Environment=SPRING_PROFILES_ACTIVE=postgresql
[Install]
WantedBy=multi-user.target
</code></pre></div></div>
<p>Dan letakkan di server, dalam folder <code class="language-plaintext highlighter-rouge">/etc/systemd/system/</code> dengan nama file <code class="language-plaintext highlighter-rouge">belajarci.service</code>.</p>
<p>Bila kita edit file tersebut (misalnya menambahkan variabel, dsb), jangan lupa reload <code class="language-plaintext highlighter-rouge">systemd</code> dengan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl daemon-reload
</code></pre></div></div>
<p>Ada dua file konfigurasi yang bisa kita buat dalam <code class="language-plaintext highlighter-rouge">/home/artivisi</code>, yaitu:</p>
<ul>
<li>application.properties : berisi file konfigurasi aplikasi, misalnya setting database production. Isi file ini akan menimpa konfigurasi application.properties yang terbundel dalam jar</li>
<li>belajar-ci.conf : berisi file konfigurasi untuk menjalankan aplikasi. Misalnya setting memori, environment variable, dan sebagainya. Nama file ini disamakan dengan nama file jar.</li>
</ul>
<p>Isi <code class="language-plaintext highlighter-rouge">application.properties</code> misalnya seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server.port=12345
server.ssl.key-store=belajar-ci.jks
server.ssl.key-store-password=IniPasswordKeyStore
server.ssl.key-password=IniPasswordPrivateKey
spring.datasource.username=belajardbuser
spring.datasource.password=AbCdefXZY3322434221
</code></pre></div></div>
<p>Dan berikut isi <code class="language-plaintext highlighter-rouge">belajar-ci.conf</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JAVA_OPTS=-Xmx32M
</code></pre></div></div>
<p>Selanjutnya, agar bisa dijalankan, set dulu permission file aplikasi <code class="language-plaintext highlighter-rouge">/home/artivisi/belajar-ci.jar</code> agar menjadi executable.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x /home/artivisi/belajar-ci.jar
</code></pre></div></div>
<p>Kemudian kita coba aktifkan servicenya. Jalankan perintah ini di server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl enable belajarci.service
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Created symlink from /etc/systemd/system/multi-user.target.wants/belajarci.service to /etc/systemd/system/belajarci.
</code></pre></div></div>
<p>Kita coba jalankan, dan langsung pantau lognya untuk melihat apakah ada error.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service belajarci start && tail -f /var/log/syslog
</code></pre></div></div>
<p>Kalau tidak ada error di log, coba test browse ke aplikasinya, yaitu <code class="language-plaintext highlighter-rouge">http://162.243.7.44:8080/api/product/</code></p>
<p>Harusnya kita akan mendapatkan output seperti ini</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"content"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"p001"</span><span class="p">,</span><span class="w">
</span><span class="nl">"code"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"P-001"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"Product 001"</span><span class="p">,</span><span class="w">
</span><span class="nl">"price"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mf">101001.01</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="p">],</span><span class="w">
</span><span class="nl">"last"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalPages"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalElements"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"size"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w">
</span><span class="nl">"number"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sort"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"first"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"numberOfElements"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Bila kita mengedit file <code class="language-plaintext highlighter-rouge">belajarci.service</code>, kita harus me-reload systemctl supaya perubahannya diproses</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl daemon-reload
</code></pre></div></div>
<p>Baru setelah itu kita restart</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service belajarci restart
</code></pre></div></div>
<h2 id="deployment-dengan-jenkins">Deployment dengan Jenkins</h2>
<p>Setelah deployment manual berhasil, kita akan mengotomasinya supaya deployment selanjutnya dilakukan oleh Jenkins. Kita ingat-ingat lagi langkah-langkah deploymentnya:</p>
<ol>
<li>Matikan aplikasi di server tujuan</li>
<li>Drop database dan create database. Ini untuk memastikan bahwa databasenya fresh. Ingat, <code class="language-plaintext highlighter-rouge">JANGAN DILAKUKAN DI SERVER PRODUCTION !!!</code></li>
<li>Upload file aplikasi</li>
<li>Jalankan kembali aplikasinya</li>
</ol>
<h3 id="setup-ssh-keypair">Setup SSH Keypair</h3>
<p>Kita perlu mendaftarkan public key SSH milik Jenkins di server tujuan. Ini kita lakukan supaya Jenkins tidak perlu mengetik username dan password untuk login ke server. Public keynya sudah kita buat pada <a href="http://software.endy.muhardin.com/java/jenkins-gitlab/">artikel terdahulu</a>.</p>
<p>Upload public key Jenkins ke server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp jenkins.pub root@162.243.7.44:/root/
</code></pre></div></div>
<p>Kemudian login ke server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh root@162.243.7.44
</code></pre></div></div>
<p>Di server, kita sudah menemui file yang barusan kita upload. Daftarkan isi filenya ke daftar public key yang diijinkan untuk login tanpa password.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat /root/jenkins.pub >> /root/.ssh/authorized_keys
</code></pre></div></div>
<p>Di sisi Jenkins, kita juga perlu mendaftarkan private key sekali lagi, agar bisa digunakan plugin <code class="language-plaintext highlighter-rouge">Publish Over SSH</code>. Pastikan pluginnya sudah terinstal.</p>
<p>Masuk ke menu <code class="language-plaintext highlighter-rouge">Manage Jenkins</code> > <code class="language-plaintext highlighter-rouge">Configure System</code>. Scroll ke bagian paling bawah, di sana ada konfigurasi untuk plugin <code class="language-plaintext highlighter-rouge">Publish Over SSH</code>. Isikan private key kita di sana.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-do/ssh-private-key.png" alt="Konfigurasi Private Key" /></p>
<p>Di bawahnya, masukkan informasi server kita. Bisa menggunakan alamat IP, atau nama domain bila sudah diset.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-do/server-tujuan-deployment.png" alt="Konfigurasi Server Tujuan" /></p>
<h3 id="post-build-action">Post Build Action</h3>
<p>Selanjutnya, masuk ke konfigurasi project dalam Jenkins. Kita akan tambahkan <code class="language-plaintext highlighter-rouge">Post Build Action</code> seperti pada waktu <a href="http://software.endy.muhardin.com/java/deploy-jenkins-pivotal/">kita mendeploy ke Pivotal</a>.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-do/konfigurasi-post-build-action.png" alt="Konfigurasi Private Key" /></p>
<p>Di situ kita isikan:</p>
<ul>
<li>Nama server tujuan, pilih seperti yang sudah kita konfigurasi di menu <code class="language-plaintext highlighter-rouge">Manage Jenkins</code> > <code class="language-plaintext highlighter-rouge">Configure System</code> tadi.</li>
<li>File yang ingin dikirim. Samakan saja dengan perintah <code class="language-plaintext highlighter-rouge">scp</code> kita pada saat deploy manual.</li>
<li>Folder tujuan. Sama seperti perintah <code class="language-plaintext highlighter-rouge">scp</code> di atas.</li>
<li>Perintah yang dijalankan pada saat deploy. Ini isinya seperti 4 langkah deployment yang sudah kita rekap tadi.</li>
</ul>
<p>Selesai sudah. Coba jalankan build secara manual. Perhatikan console outputnya, pastikan berjalan dengan sukses.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah cara deployment ke VPS dengan Jenkins. Memang tidak semudah mendeploy ke layanan PaaS. Cara ini bisa dipakai bila kita punya development server di LAN, ataupun bila manajemen memutuskan untuk punya server sendiri (colocation) atau menyewa layanan IaaS.</p>
<p>Semoga bermanfaat.</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="https://caffinc.github.io/2015/05/automatically-updating-fat-jar-with-jenkins-publish-over-ssh/">Automatically updating fat jar with Jenkins Publish over SSH</a></li>
<li><a href="https://wiki.jenkins-ci.org/display/JENKINS/Publish+Over+SSH+Plugin">Publish Over SSH Plugin</a></li>
<li><a href="https://wiki.jenkins-ci.org/display/JENKINS/Publish+Over">Publish Over Plugin</a></li>
</ul>
Mendeploy Aplikasi dari Jenkins ke Pivotal Web Service2017-01-25T07:00:00+07:00https://software.endy.muhardin.com/java/deploy-jenkins-pivotal<p>Setelah <a href="http://software.endy.muhardin.com/java/jenkins-gitlab/">Jenkins berhasil melakukan proses build tiap kali ada push</a>, selanjutnya kita ingin meneruskan proses deployment apabila proses build berjalan sukses.</p>
<p>Deployment kali ini akan kita lakukan ke layanan cloud milik Pivotal, yaitu <a href="https://run.pivotal.io">Pivotal Web Service</a>. Saya lebih suka deploy ke layanan PaaS dimana environment sudah tersedia siap pakai dibandingkan dengan IaaS dimana kita harus setup sendiri Java, Maven, MySQL, dan aplikasi pendukung lainnya. Dengan demikian, kita bisa berfokus pada penulisan source code, tidak perlu pusing memikirkan infrastruktur. Ini terutama sangat memudahkan bagi tim/perusahaan dengan resource terbatas (baca: tidak mampu menggaji sysadmin ;p)</p>
<p>Pada saat artikel ini ditulis, harga Pivotal paling murah dibanding layanan PaaS lain seperti Heroku. Oleh karena itu, kita akan melakukan deployment ke Pivotal.</p>
<p>Ada beberapa langkah dalam menyambungkan Jenkins ke Pivotal :</p>
<ol>
<li>Memasukkan credentials (username/password) Pivotal ke Jenkins.</li>
<li>Menambahkan post-build action di Jenkins untuk mendeploy aplikasi apabila proses build berjalan sukses.</li>
<li>Membuat file konfigurasi Pivotal Cloud Foundry, yaitu <code class="language-plaintext highlighter-rouge">manifest.yml</code> dalam project kita.</li>
<li>Menambahkan konfigurator otomatis untuk koneksi database di Pivotal.</li>
<li>Lakukan <code class="language-plaintext highlighter-rouge">git push</code> untuk menjalankan Jenkins dan deployment.</li>
</ol>
<!--more-->
<p>Sebelum kita mulai mengkonfigurasi, pastikan dulu di Jenkins telah terinstal plugin Cloud Foundry.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-cloudfoundry/cloud-foundry-plugin.png" alt="Plugin Cloud Foundry" /></p>
<h2 id="pivotal-credentials">Pivotal Credentials</h2>
<p>Agar Jenkins bisa melakukan deployment, tentu dia butuh login dulu ke Pivotal. Untuk itu, masuk ke halaman Credentials, lalu masukkan username dan password Pivotal Web Services.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-cloudfoundry/pws-credentials.png" alt="PWS Credentials" /></p>
<h2 id="post-build-action">Post Build Action</h2>
<p>Berikutnya, masuk ke konfigurasi project, kemudian scroll ke bagian Post Build Action. Isikan konfigurasinya seperti pada screenshot berikut</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-cloudfoundry/post-build-action.png" alt="Konfigurasi Post Build" /></p>
<p>Pilih <code class="language-plaintext highlighter-rouge">Credentials</code> yang sudah kita masukkan pada tahap sebelumnya. Isi kolom <code class="language-plaintext highlighter-rouge">Organization</code> dan <code class="language-plaintext highlighter-rouge">Space</code> sesuai akun kita di Pivotal. Centang <code class="language-plaintext highlighter-rouge">reset app</code> supaya tiap deployment menghapus dulu deployment sebelumnya.</p>
<p>Aplikasi saya ini membutuhkan database. Untuk itu kita perlu membuatkan servicenya dulu. Masukkan nama instance database yang akan digunakan di aplikasi, yaitu <code class="language-plaintext highlighter-rouge">belajar-ci-db</code>. Pilih juga jenis servicenya, yaitu <code class="language-plaintext highlighter-rouge">cleardb</code> yang menyediakan database MySQL. Saya gunakan plan <code class="language-plaintext highlighter-rouge">Spark</code> yang gratisan. Centang juga <code class="language-plaintext highlighter-rouge">reset service</code> agar databasenya didrop dan dicreate ulang setiap kali deploy.</p>
<p>Kemudian terakhir, suruh Jenkins untuk membaca file konfigurasi <code class="language-plaintext highlighter-rouge">manifest.yml</code> yang ada dalam project folder.</p>
<h2 id="membuat-konfigurasi-deployment">Membuat Konfigurasi Deployment</h2>
<p>Konfigurasi deployment Pivotal Cloud Foundry biasanya diberi nama <code class="language-plaintext highlighter-rouge">manifest.yml</code>. Ini mirip dengan file <code class="language-plaintext highlighter-rouge">Procfile</code> di Heroku, yaitu menjelaskan tentang aplikasi yang akan dideploy. Berikut isi filenya</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">applications</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">belajar-ci</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">target/belajar-ci-0.0.1-SNAPSHOT.jar</span>
<span class="na">services</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">belajar-ci-db</span>
</code></pre></div></div>
<p>Artinya sebagai berikut:</p>
<ul>
<li>aplikasi kita akan diberi nama <code class="language-plaintext highlighter-rouge">belajar-ci</code></li>
<li>file yang akan dideploy adalah <code class="language-plaintext highlighter-rouge">target/belajar-ci-0.0.1-SNAPSHOT.jar</code></li>
<li>aplikasi ini akan menggunakan service <code class="language-plaintext highlighter-rouge">belajar-ci-db</code>. Service ini dibuatkan oleh Jenkins seperti kita konfigurasi pada langkah sebelumnya.</li>
</ul>
<h2 id="konfigurator-koneksi-database">Konfigurator Koneksi Database</h2>
<p>Database yang dibuatkan oleh Pivotal biasanya akan memiliki nama database, username, dan password yang acak. Setiap kali dibuat ulang, nilainya berbeda. Oleh karena itu, kita tidak bisa menulis nilainya dalam <code class="language-plaintext highlighter-rouge">application.properties</code> seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spring.datasource.url=jdbc:mysql://localhost/belajar
spring.datasource.username=belajar
spring.datasource.password=java
</code></pre></div></div>
<p>Untuk mengatasi masalah tersebut, para programmer Spring Cloud telah membuatkan library bantuan yang dapat mendeteksi nama variabel tersebut dan memasangnya di aplikasi kita. Cukup tambahkan saja librarynya di <code class="language-plaintext highlighter-rouge">pom.xml</code> sebagai berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-cloud-connectors<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<h2 id="test-deployment">Test Deployment</h2>
<p>Selesai sudah konfigurasinya. Sekarang kita bisa commit dan push. Source code akan masuk ke Gitlab, mentrigger Jenkins untuk build, kemudian melakukan deployment setelah build sukses.</p>
<p>Kita bisa monitor hasilnya di <code class="language-plaintext highlighter-rouge">Console Output</code> Jenkins.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-cloudfoundry/deployment-log.png" alt="Deployment Log" /></p>
<p>Setelah sukses, kita bisa coba browse ke <code class="language-plaintext highlighter-rouge">https://belajar-ci.cfapps.io</code> untuk melihat hasilnya</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-cloudfoundry/deploy-no-data.png" alt="Deploy No Data" /></p>
<p>Wah tidak ada datanya, jadi kita tidak bisa mengecek apakah databasenya terbentuk atau tidak. Coba kita masukkan data melalui perintah SQL.</p>
<h2 id="mengakses-database-pivotal">Mengakses Database Pivotal</h2>
<p>Agar bisa menjalankan SQL untuk insert, kita perlu mengakses database di Pivotal. Kita perlu tahu dulu parameter koneksinya, yaitu:</p>
<ul>
<li>host dan port servernya</li>
<li>nama database</li>
<li>username</li>
<li>password</li>
</ul>
<p>Caranya, kita harus membuat <code class="language-plaintext highlighter-rouge">service-key</code>, kemudian membaca isinya. Jalankan perintah berikut untuk membuat <code class="language-plaintext highlighter-rouge">service-key</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf create-service-key belajar-ci-db info-koneksi
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating service key info-koneksi for service instance belajar-ci-db as endy.muhardin@gmail.com...
OK
</code></pre></div></div>
<p>Selanjutnya, kita tampilkan isi <code class="language-plaintext highlighter-rouge">service-key</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf service-key belajar-ci-db info-koneksi
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Getting key info-koneksi for service instance belajar-ci-db as endy.muhardin@gmail.com...
{
"hostname": "us-cdbr-iron-east-04.cleardb.net",
"jdbcUrl": "jdbc:mysql://us-cdbr-iron-east-04.cleardb.net/ad_7778cd39cfcc524?user=b2d044382b2df8\u0026password=c98433d3",
"name": "ad_7778cd39cfcc524",
"password": "c98433d3",
"port": "3306",
"uri": "mysql://b2d044382b2df8:c98433d3@us-cdbr-iron-east-04.cleardb.net:3306/ad_7778cd39cfcc524?reconnect=true",
"username": "b2d044382b2df8"
}
</code></pre></div></div>
<p>Nah, sekarang kita bisa connect ke database</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql -u b2d044382b2df8 -h us-cdbr-iron-east-04.cleardb.net -p ad_7778cd39cfcc524
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 221029017
Server version: 5.5.46-log MySQL Community Server (GPL)
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
</code></pre></div></div>
<p>Jalankan perintah SQL untuk mengisi data</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">product</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">code</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">price</span><span class="p">)</span>
<span class="k">values</span> <span class="p">(</span><span class="s1">'p001'</span><span class="p">,</span> <span class="s1">'P-001'</span><span class="p">,</span> <span class="s1">'Product 001'</span><span class="p">,</span> <span class="mi">101001</span><span class="p">.</span><span class="mi">01</span><span class="p">);</span>
</code></pre></div></div>
<p>Refresh browser untuk melihat apakah datanya bisa dibaca</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-cloudfoundry/deploy-with-data.png" alt="Deploy With Data" /></p>
<p>Voila, ternyata aplikasi kita sudah berjalan dengan baik.</p>
<p>Agar di kemudian hari kita bisa langsung melihat data, ada baiknya kita tulis perintah insert tadi ke migration script FlywayDb.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah rangkaian serial setup Continuous Delivery dengan Gitlab, Jenkins, dan Pivotal Cloud Foundry. Dengan otomasi workflow ini, para programmer bisa berkonsentrasi menulis source code dan tidak dipusingkan dengan proses deployment setiap waktu.</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="http://www.haydonryan.com/using-jenkins-to-build-a-hello-world-spring-boot-app-and-push-to-cloud-foundry/">http://www.haydonryan.com/using-jenkins-to-build-a-hello-world-spring-boot-app-and-push-to-cloud-foundry/</a></li>
<li><a href="https://docs.run.pivotal.io/devguide/deploy-apps/ssh-services.html">https://docs.run.pivotal.io/devguide/deploy-apps/ssh-services.html</a></li>
</ul>
Mengakses Repository Gitlab dari Jenkins2017-01-23T07:00:00+07:00https://software.endy.muhardin.com/java/jenkins-gitlab<p>Pada <a href="http://software.endy.muhardin.com/java/instalasi-jenkins-ssl/">artikel sebelumnya</a>, kita telah menyiapkan server Jenkins untuk melakukan build pada project kita. Di artikel ini, kita akan mengkonfigurasikan project kita yang ada di Gitlab supaya bisa terhubung dengan Jenkins. Setelah selesai konfigurasi, kita akan bisa mendapatkan workflow seperti ini:</p>
<ol>
<li>Programmer mengedit source code di laptopnya. Kemudian mengetes di laptopnya apakah sudah berjalan dengan baik.</li>
<li>Setelah oke, programmer commit dan push perubahan yang dia lakukan ke server Gitlab.</li>
<li>Gitlab akan mengontak Jenkins, memberitahukan bahwa ada perubahan source code terbaru.</li>
<li>Jenkins akan mengambil source code yang terbaru dari Gitlab.</li>
<li>Jenkins menjalankan proses build.</li>
<li>Jenkins memberitahukan hasilnya kembali ke server Gitlab.</li>
<li>Gitlab memberitahukan hasilnya pada programmer.</li>
</ol>
<p>Langkah-langkah setup:</p>
<ol>
<li>Buat SSH Keypair</li>
<li>Setup SSH Key di Jenkins</li>
<li>Buat project di Gitlab</li>
<li>Daftarkan deploy key di Gitlab</li>
<li>Generate API token di Gitlab</li>
<li>Buat project di Jenkins</li>
<li>Daftarkan trigger Jenkins di Gitlab</li>
</ol>
<!--more-->
<h2 id="membuat-ssh-keypair">Membuat SSH Keypair</h2>
<p>Agar Jenkins dapat mengakses project kita di dalam Gitlab, ada beberapa mekanisme yang bisa digunakan, antara lain:</p>
<ul>
<li>username dan password</li>
<li>SSH Keypair</li>
</ul>
<p>Saya agak kurang suka menggunakan username dan password untuk komunikasi antar aplikasi. Oleh karena itu, mari kita buat SSH keypair saja.</p>
<p>Pembuatan ssh keypair ini bisa dilakukan di laptop kita. Berikut perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen -f jenkins
</code></pre></div></div>
<p>Perintah di atas akan menghasilkan output sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in jenkins.
Your public key has been saved in jenkins.pub.
The key fingerprint is:
SHA256:ZRmQbVSBt1gf574sWi6GD27lihabdoHhImW6b0Kz8+4 endymuhardin@Endys-MacBook-Air.local
The key's randomart image is:
+---[RSA 2048]----+
| .=ooo. |
| . +oo . .|
| .++ o + |
| o . o. . . .|
| + . S . |
| = . + . . .|
| . = . +.= .. .|
| = . =o+.+o. o |
| OEo.oo+oo.. |
+----[SHA256]-----+
</code></pre></div></div>
<p>Private key tidak perlu dipasangi password supaya setupnya nanti tidak sulit. Setelah perintah dijalankan, kita akan mendapatkan dua file, private key dan public key. Public key adalah file yang berakhiran <code class="language-plaintext highlighter-rouge">.pub</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -l | grep jenkins
-rw------- 1 endymuhardin staff 1679 Jan 23 12:43 jenkins
-rw-r--r-- 1 endymuhardin staff 418 Jan 23 12:43 jenkins.pub
</code></pre></div></div>
<p>SSH key ini kita masukkan ke Jenkins. Masuk ke menu Credentials > System > Global Credentials</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/global-credentials.png" alt="Global Credentials" /></p>
<p>Selanjutnya kita klik Add Credentials. Karena private key kita generate di laptop, maka kita isikan langsung ke dalam input box. File private key bisa dibuka dengan text editor biasa.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/input-private-key.png" alt="Input Private Key" /></p>
<p>Klik simpan</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/hasil-private-key.png" alt="Hasil Private Key" /></p>
<h2 id="membuat-project-di-gitlab">Membuat Project di Gitlab</h2>
<p>Berikutnya, kita pindah ke Gitlab untuk menyiapkan project yang akan dibuild. Projectnya tidak akan saya buat baru. Kita akan gunakan <a href="http://github.com/endymuhardin/belajar-ci">project di artikel sebelumnya yang ada di Github</a>.</p>
<p>Login ke Gitlab, kemudian kita buat New Project. Kita set menjadi project private agar tidak bisa diakses oleh umum.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/gitlab-new-project.png" alt="Gitlab New Project" /></p>
<p>Setelah project kita buat di Gitlab, kita akan isi dengan source code dari repo Github. Lakukan perintah berikut di laptop, dalam folder projectnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd belajar-ci
git remote add gitlab git@gitlab.artivisi.com:endymuhardin/belajar-ci.git
git push gitlab master
</code></pre></div></div>
<p>Berikutnya, kita akan menambahkan public key tadi supaya Jenkins bisa mengakses project <code class="language-plaintext highlighter-rouge">belajar-ci</code> ini. Masuk ke menu Settings > Deploy Key</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/menu-deploy-key.png" alt="Menu Deploy Key" /></p>
<p>Masukkan isi public key di file <code class="language-plaintext highlighter-rouge">jenkins.pub</code> seperti ini</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/input-deploy-key.png" alt="Input Deploy Key" /></p>
<p>Deploy key tersebut hanya mengijinkan Jenkins untuk mengambil source code saja. Kita ingin dia juga bisa mengambil informasi tentang project, seperti nama project, nama committers, dan lainnya. Untuk itu, kita perlu memiliki <code class="language-plaintext highlighter-rouge">access_token</code> dari Gitlab.</p>
<p>Masuk ke menu <code class="language-plaintext highlighter-rouge">Profile Settings</code> yang ada di menu user kita.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/menu-profile-settings.png" alt="Menu Profile Settings" /></p>
<p>Kemudian isi input fieldnya.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/generate-access-token.png" alt="Generate Access Token" /></p>
<p>Kita akan dibuatkan Access Token. Copy isinya untuk kita pasang di Jenkins.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/hasil-access-token.png" alt="Hasil Access Token" /></p>
<p>Di dalam Jenkins, masuk ke menu Global Credentials. Kemudian tambahkan Gitlab API Token.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/gitlab-token.png" alt="Gitlab Token di Jenkins" /></p>
<p>Selanjutnya, masih di Jenkins, masuk ke menu Configure System, kemudian scroll ke bagian Gitlab. Isikan url Gitlab kita, dan pilih credential access token yang sudah kita pasang sebelumnya.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/jenkins-gitlab-api.png" alt="Mengakses Gitlab API di Jenkins" /></p>
<h2 id="konfigurasi-project-di-jenkins">Konfigurasi Project di Jenkins</h2>
<p>Kita pindah dulu ke Jenkins untuk mendaftarkan project kita tersebut. Sebelum masuk ke Project, periksa dulu di Manage Jenkins > Global Tool Configuration, pastikan Maven sudah terkonfigurasi. Bila masih kosong seperti ini</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/maven-kosong.png" alt="Konfigurasi Maven" /></p>
<p>Klik Add Maven dan isikan lokasi instalasinya.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/input-maven.png" alt="Input Maven" /></p>
<p>Barulah kita bisa lanjut ke Project. Masuk ke New Project dan pilih Maven Project.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/new-maven-project.png" alt="Project Baru Maven" /></p>
<p>Di menu General, kita set integrasi Gitlab yang sudah kita konfigurasi sebelumnya.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/gitlab-connection.png" alt="Integrasi Gitlab" /></p>
<p>Selanjutnya, kita pasang url untuk ke repository Gitlab di bagian Source Code Management.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/konfigurasi-git-url.png" alt="URL Gitlab" /></p>
<p>Di bagian trigger, kita suruh Jenkins untuk melakukan build pada saat menerima perintah dari Gitlab, yaitu melalui HTTP API. Pada bagian ini kita bisa mendapatkan build URL, seperti dilihat pada gambar berikut</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/build-trigger-url.png" alt="URL Trigger" /></p>
<p>Build URL ini kita pasang di webhook pada project kita di Gitlab.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/menu-webhook.png" alt="Menu Webhook" /></p>
<p>Masukkan URL yang kita copas dari Jenkins tadi</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/input-webhook.png" alt="Input Webhook" /></p>
<p>Kadangkala, kita perlu membuat database dulu untuk aplikasi kita. Kita bisa jalankan perintah untuk membuat database di bagian <code class="language-plaintext highlighter-rouge">Pre Step</code></p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/drop-create-db.png" alt="Drop Create Database" /></p>
<p>Terakhir, kita tambahkan Post Build Action</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/post-build-action.png" alt="Post Build Action" /></p>
<p>Kemudian kita save.</p>
<h2 id="test-webhook">Test Webhook</h2>
<p>Di laman konfigurasi Webhook ada tombol test. Kita bisa gunakan itu untuk mengetes integrasi antara Gitlab dan Jenkins.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/webhook-tester.png" alt="Webhook Tester" /></p>
<p>Kita bisa klik tombolnya, dan kita lihat proses build segera berjalan di Jenkins</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/webhook-success.png" alt="Webhook Success" /></p>
<p>Selanjutnya, kita bisa monitor proses build di Jenkins yang harusnya sudah berjalan.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/proses-build.png" alt="Proses Build" /></p>
<p>Bila proses build berhasil, kita akan mendapatkan notifikasi dari Gitlab melalui email.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-gitlab/notifikasi-email.png" alt="Notifikasi Email" /></p>
<p>Pada saat kita menerima email, artinya prosesnya sudah berjalan otomatis. Begitu programmer commit dan push ke repository Git, kode programnya langsung dibuild dan dijalankan automated testnya. Hasilnya, sukses atau gagal, akan dikomunikasikan melalui email.</p>
<h2 id="reset-jenkins-build">Reset Jenkins Build</h2>
<p>Pada waktu mengkonfigurasi build, seringkali kita mengalami kesalahan konfigurasi sehingga buildnya gagal. Akibatnya, project kita akan memiliki history build yang kurang baik, disebabkan karena adanya build awal yang gagal. Untuk itu, biasanya setelah konfigurasi kita benar, kita ingin menghapus build gagal tersebut dan mulai dari awal. Untuk melakukan reset ini, masuk ke menu Manage Jenkins > Script Console.</p>
<p>Kita bisa menghapus semua build untuk project tertentu dan memulai build number dari 1 lagi dengan menggunakan script berikut</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">def</span> <span class="n">jobName</span> <span class="o">=</span> <span class="s2">"Nama project yang ingin direset"</span>
<span class="kt">def</span> <span class="n">job</span> <span class="o">=</span> <span class="n">Jenkins</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">getItem</span><span class="o">(</span><span class="n">jobName</span><span class="o">)</span>
<span class="n">job</span><span class="o">.</span><span class="na">getBuilds</span><span class="o">().</span><span class="na">each</span> <span class="o">{</span> <span class="n">it</span><span class="o">.</span><span class="na">delete</span><span class="o">()</span> <span class="o">}</span>
<span class="n">job</span><span class="o">.</span><span class="na">nextBuildNumber</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">job</span><span class="o">.</span><span class="na">save</span><span class="o">()</span>
</code></pre></div></div>
<p>Setelah itu, klik <code class="language-plaintext highlighter-rouge">Run</code> untuk menjalankan script. Project kita akan bersih kembali.</p>
<p>Bila kita membuat aplikasi microservices, biasanya kita akan punya banyak aplikasi dan project. Kalau kita gunakan script di atas, kita harus ganti nama project berkali-kali. Lumayan merepotkan. Solusinya, kita bisa gunakan script ini untuk <strong>menghapus history build di semua project</strong>. Jangan dijalankan bila ada project lain yang tidak ingin direset.</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="o">(</span><span class="n">item</span> <span class="k">in</span> <span class="n">Jenkins</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">items</span><span class="o">)</span> <span class="o">{</span>
<span class="n">item</span><span class="o">.</span><span class="na">builds</span><span class="o">.</span><span class="na">each</span><span class="o">()</span> <span class="o">{</span> <span class="n">build</span> <span class="o">-></span>
<span class="n">build</span><span class="o">.</span><span class="na">delete</span><span class="o">()</span>
<span class="o">}</span>
<span class="n">item</span><span class="o">.</span><span class="na">updateNextBuildNumber</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="penutup">Penutup</h2>
<p>Kita telah berhasil menghubungkan antara Gitlab dan Jenkins. Untuk selanjutnya, kita akan teruskan agar build yang sukses di Jenkins dapat dideploy secara otomatis di testing server. Dengan demikian, begitu programmer commit dan push ke repository Git, bila tidak terjadi error, akan segera bisa diakses di testing server.</p>
<p>Stay tuned untuk <a href="http://software.endy.muhardin.com/java/deploy-jenkins-pivotal/">bagian terakhir, yaitu deployment</a>.</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="https://medium.com/@teeks99/continuous-integration-with-jenkins-and-gitlab-fa770c62e88a#.xgl4n8cbg">https://medium.com/@teeks99/continuous-integration-with-jenkins-and-gitlab-fa770c62e88a#.xgl4n8cbg</a></li>
<li><a href="http://stackoverflow.com/a/24991433">http://stackoverflow.com/a/24991433</a></li>
</ul>
Instalasi Jenkins dengan SSL2017-01-22T07:00:00+07:00https://software.endy.muhardin.com/java/instalasi-jenkins-ssl<p>Hampir setahun yang lalu, saya menulis serangkaian artikel yang terdiri dari 4 artikel mengenai <a href="http://software.endy.muhardin.com/java/project-bootstrap-01/">cara setup project Java</a>, <a href="http://software.endy.muhardin.com/java/project-bootstrap-02/">konfigurasi continuous integration</a>, <a href="http://software.endy.muhardin.com/java/project-bootstrap-03/">deployment ke Heroku dan Openshift</a>, dan <a href="http://software.endy.muhardin.com/java/project-bootstrap-04/">continuous delivery</a> supaya tiap kali programmer commit, aplikasi langsung terdeploy ke server testing. Akan tetapi, artikel tersebut hanya cocok buat project open source yang source codenya tersedia bebas di Github, sehingga kita bisa menggunakan layanan gratis seperti Travis dan Coveralls. Bila kita ingin menggunakannya untuk project yang tidak publik (misalnya project yang dibayar oleh client, ataupun produk yang tidak kita rilis source codenya), kita harus membayar cukup mahal untuk menggunakan layanan Github, Travis, dan Coveralls tersebut.</p>
<p>Untuk itu, sekarang saya akan menulis lagi panduan untuk membuat workflow yang serupa, tapi untuk project privat. Konfigurasinya adalah sebagai berikut:</p>
<ul id="markdown-toc">
<li><a href="#instalasi-vps" id="markdown-toc-instalasi-vps">Instalasi VPS</a></li>
<li><a href="#setup-ssl" id="markdown-toc-setup-ssl">Setup SSL</a></li>
<li><a href="#memasang-ssl-di-jenkins" id="markdown-toc-memasang-ssl-di-jenkins">Memasang SSL di Jenkins</a></li>
<li><a href="#konfigurasi-port-forwarding" id="markdown-toc-konfigurasi-port-forwarding">Konfigurasi Port Forwarding</a></li>
<li><a href="#penutup" id="markdown-toc-penutup">Penutup</a></li>
<li><a href="#referensi" id="markdown-toc-referensi">Referensi</a></li>
</ul>
<!--more-->
<p>Pada bagian pertama ini (atau kelima kalau dihitung dari seri terdahulu), kita akan membahas dulu cara instalasi Jenkins. Adapun cara instalasi Gitlab tidak saya bahas karena amat sangat terlalu mudah. Silahkan ikuti <a href="https://about.gitlab.com/downloads/#ubuntu1604">panduannya</a>.</p>
<h2 id="instalasi-vps">Instalasi VPS</h2>
<p>Untuk menghosting Jenkins, kita akan gunakan <a href="https://m.do.co/c/910ad80271f7">Digital Ocean</a>.</p>
<p>Berikut langkah instalasinya. Saya menggunakan <a href="https://github.com/digitalocean/doctl">aplikasi command line resmi dari DigitalOcean</a></p>
<ul>
<li>
<p>siapkan VPS di DigitalOcean. Saya gunakan yang berukuran 2GB karena berdasarkan pengalaman, yang 512MB dan 1GB sering error kehabisan memori</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> doctl compute droplet create vps-jenkins --size=2gb --image ubuntu-16-04-x64 --region nyc1
</code></pre></div> </div>
<p>hasilnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image Status Tags
44271929 vps-jenkins 2048 2 40 nyc1 Ubuntu 16.04.2 x64 new
</code></pre></div> </div>
</li>
<li>
<p>Lihat IP Address VPS yang baru kita buat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> doctl compute droplet list
</code></pre></div> </div>
<p>hasilnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image Status Tags
44271929 vps-jenkins 67.205.183.31 2048 2 40 nyc1 Ubuntu 16.04.2 x64 active
</code></pre></div> </div>
</li>
<li>
<p>Login ssh ke dalam VPS</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ssh root@67.205.183.31
</code></pre></div> </div>
<p>Outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Attempting SSH: root@216.58.221.69
SShing with options: -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -i /Users/endymuhardin/.ssh/id_rsa -p 22 root@216.58.221.69
Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-59-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
0 packages can be updated.
0 updates are security updates.
</code></pre></div> </div>
</li>
<li>
<p>Perbaiki Perl warning. Secara default, hasil instalasi Ubuntu standar dari DO akan menghasilkan warning berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
</code></pre></div> </div>
<p>Untuk mengatasinya, jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> echo 'LC_ALL="en_US.UTF-8"' >> /etc/environment
</code></pre></div> </div>
<p>Setelah itu logout dan login lagi agar perubahan tersebut dijalankan.</p>
</li>
<li>
<p>Update dan upgrade dulu supaya up-to-date</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> apt-get update && apt-get upgrade -y
</code></pre></div> </div>
</li>
<li>
<p>Instalasi Java, Maven, dan Jenkins</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> apt-get install openjdk-8-jdk-headless maven jenkins -y
</code></pre></div> </div>
</li>
</ul>
<p>Seharusnya Jenkins sudah bisa diakses di port 8080. Dia akan meminta kita mengeset password. Tapi tidak perlu dijalankan dulu.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-ssl/jenkins-starter.png" alt="Jenkins Starter Page" /></p>
<h2 id="setup-ssl">Setup SSL</h2>
<p>Jaman sekarang, SSL sudah mudah dan murah (baca: gratis). Internet Security Research Group telah menyediakan layanan <a href="https://letsencrypt.org/">Let’s Encrypt</a> yang memberikan sertifikat SSL gratis untuk semua orang. Cara kerjanya bisa dibaca <a href="https://letsencrypt.org/how-it-works/">di sini</a>. Dia juga menyediakan aplikasi untuk memudahkan kita mendapatkan dan memperpanjang sertifikat SSL secara otomatis.</p>
<p>Kita akan meminta sertifikat SSL untuk domain jenkins.artivisi.com. Tentunya agar domain ini bisa dikenali, kita harus menambahkan <code class="language-plaintext highlighter-rouge">A</code> record di DNS Server kita untuk mengarahkan domain <code class="language-plaintext highlighter-rouge">jenkins.artivisi.com</code> ke IP server, yaitu <code class="language-plaintext highlighter-rouge">216.58.221.69</code>. Cara pendaftaran DNS ini tidak saya bahas, karena metodenya sangat berbeda tergantung penyedia domain yang digunakan. Silahkan hubungi orang yang mengurus domain Anda untuk mengetahui caranya.</p>
<p>Berikut langkah-langkah pemakaian Let’s Encrypt.</p>
<ul>
<li>
<p>Instalasi Let’s Encrypt untuk membuat sertifikat SSL</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> apt-get install letsencrypt -y
</code></pre></div> </div>
</li>
</ul>
<p>Pada saat menginstal package <code class="language-plaintext highlighter-rouge">letsencrypt</code> tersebut, sebenarnya kita menginstal aplikasi bernama <code class="language-plaintext highlighter-rouge">certbot</code> yang berguna untuk mengotomasi proses pembuatan sertifikat SSL. Aplikasi <code class="language-plaintext highlighter-rouge">certbot</code> ini memiliki beberapa plugin untuk memudahkan konfigurasi webserver yang sering dipakai orang, diantaranya:</p>
<ul>
<li>apache</li>
<li>nginx</li>
<li>webroot : untuk membuat sertifikat di server yang memiliki beberapa virtualhost</li>
<li>standalone : bila kita ingin menyuruh <code class="language-plaintext highlighter-rouge">certbot</code> melakukan verifikasi dengan menjalankan webservernya sendiri</li>
<li>manual : bila kita ingin membuat sertifikat SSL untuk mesin lain</li>
</ul>
<p>Kita akan menggunakan mode <code class="language-plaintext highlighter-rouge">standalone</code> karena Jenkins menggunakan application server Jetty yang tidak disupport oleh <code class="language-plaintext highlighter-rouge">certbot</code>.</p>
<p>Berikut perintah yang dijalankan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>letsencrypt certonly --standalone-supported-challenges tls-sni-01 -d jenkins.artivisi.com --email endy.muhardin@gmail.com --agree-tos
</code></pre></div></div>
<p>Outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IMPORTANT NOTES:
- If you lose your account credentials, you can recover through
e-mails sent to endy.muhardin@gmail.com.
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/jenkins.artivisi.com/fullchain.pem. Your cert
will expire on 2017-04-22. To obtain a new version of the
certificate in the future, simply run Let's Encrypt again.
- Your account credentials have been saved in your Let's Encrypt
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Let's
Encrypt so making regular backups of this folder is ideal.
- If you like Let's Encrypt, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
</code></pre></div></div>
<p>Sertifikat SSL dari Letsencrypt hanya berlaku selama 90 hari. Agar tidak repot memperpanjang setiap 3 bulan, kita gunakan fitur <code class="language-plaintext highlighter-rouge">autorenew</code>. Coba test dulu perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>letsencrypt renew --dry-run
</code></pre></div></div>
<p>Outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Processing /etc/letsencrypt/renewal/jenkins.artivisi.com.conf
2017-01-22 11:08:11,907:WARNING:letsencrypt.client:Registering without email!
** DRY RUN: simulating 'letsencrypt renew' close to cert expiry
** (The test certificates below have not been saved.)
Congratulations, all renewals succeeded. The following certs have been renewed:
/etc/letsencrypt/live/jenkins.artivisi.com/fullchain.pem (success)
** DRY RUN: simulating 'letsencrypt renew' close to cert expiry
** (The test certificates above have not been saved.)
IMPORTANT NOTES:
- Your account credentials have been saved in your Let's Encrypt
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Let's
Encrypt so making regular backups of this folder is ideal.
</code></pre></div></div>
<p>Proses renewal sudah bisa dijalankan dengan baik. Kita bisa mendaftarkan perintah ini ke <code class="language-plaintext highlighter-rouge">crontab</code> agar berjalan dua kali sehari. Jangan khawatir kalau frekuensinya terlalu sering, karena perintah <code class="language-plaintext highlighter-rouge">renew</code> ini boleh dijalankan walaupun sertifikat kita belum kadaluarsa. Bila kita jalankan outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>letsencrypt renew
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Processing /etc/letsencrypt/renewal/jenkins.artivisi.com.conf
The following certs are not due for renewal yet:
/etc/letsencrypt/live/jenkins.artivisi.com/fullchain.pem (skipped)
No renewals were attempted.
</code></pre></div></div>
<p>Karena proses renew ini akan menggunakan port 443, maka kita harus mematikan Jenkins dulu sebelum scriptnya berjalan. Setelah selesai renew, kita jalankan lagi Jenkins. Bila digabungkan maka perintahnya sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service jenkins stop && letsencrypt renew && service jenkins start
</code></pre></div></div>
<p>Setelah semuanya berjalan dengan baik, kita jalankan perintahnya dengan <code class="language-plaintext highlighter-rouge">cron</code> agar berjalan otomatis. Kita jadwalkan dua kali sehari dengan menit acak, sesuai <a href="https://certbot.eff.org/#ubuntuxenial-other">rekomendasi dari Let’s Encrypt</a>.</p>
<blockquote>
<p>If you’re setting up a cron or systemd job, we recommend running it twice per day (it won’t do anything until your certificates are due for renewal or revoked, but running it regularly would give your site a chance of staying online in case a Let’s Encrypt-initiated revocation happened for some reason). Please select a random minute within the hour for your renewal tasks.</p>
</blockquote>
<p>Berikut adalah isi dari file crontabnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SHELL=/bin/bash
0 */12 * * * sleep $((RANDOM*3600/32768)) && /etc/jenkins/ssl/renew-jenkins-ssl.sh
</code></pre></div></div>
<p>Script <code class="language-plaintext highlighter-rouge">renew-jenkins-ssl.sh</code> berisi langkah-langkah konversi dari sertifikat yang dihasilkan Let’s Encrypt menjadi format <code class="language-plaintext highlighter-rouge">JKS</code> yang biasa digunakan application server Java. Lebih detailnya sudah pernah dibahas pada <a href="http://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl/">artikel saya terdahulu</a>.</p>
<p>Berikut isi dari script tersebut</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
service jenkins stop
letsencrypt renew
<span class="nb">rm</span> /etc/jenkins/ssl/jenkins.jks
openssl pkcs12 <span class="nt">-export</span> <span class="se">\</span>
<span class="nt">-inkey</span> /etc/letsencrypt/live/jenkins.artivisi.com/privkey.pem <span class="se">\</span>
<span class="nt">-in</span> /etc/letsencrypt/live/jenkins.artivisi.com/cert.pem <span class="se">\</span>
<span class="nt">-certfile</span> /etc/letsencrypt/live/jenkins.artivisi.com/chain.pem <span class="se">\</span>
<span class="nt">-name</span> <span class="s2">"jenkins.artivisi.com"</span> <span class="se">\</span>
<span class="nt">-passout</span> <span class="s2">"pass:rahasia"</span> <span class="se">\</span>
<span class="nt">-out</span> /etc/jenkins/ssl/jenkins.p12
keytool <span class="nt">-importkeystore</span> <span class="se">\</span>
<span class="nt">-srcstoretype</span> PKCS12 <span class="se">\</span>
<span class="nt">-srckeystore</span> /etc/jenkins/ssl/jenkins.p12 <span class="se">\</span>
<span class="nt">-srcstorepass</span> <span class="s2">"rahasia"</span> <span class="se">\</span>
<span class="nt">-deststorepass</span> <span class="s1">'rahasia'</span> <span class="se">\</span>
<span class="nt">-destkeystore</span> /etc/jenkins/ssl/jenkins.jks
<span class="nb">rm</span> /etc/jenkins/ssl/jenkins.p12
service jenkins start
</code></pre></div></div>
<p>Yang harus diperhatikan pada saat membuat script di atas adalah : <strong>password pada saat membuat file p12 harus sama dengan password file JKS</strong>. Bila tidak sama, maka Jenkins akan mengeluarkan pesan error</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java.security.UnrecoverableKeyException: Cannot recover key
</code></pre></div></div>
<h2 id="memasang-ssl-di-jenkins">Memasang SSL di Jenkins</h2>
<p>Seperti bisa dibaca pada script di atas, dia akan membuat file <code class="language-plaintext highlighter-rouge">/etc/jenkins/ssl/jenkins.jks</code> dengan password <code class="language-plaintext highlighter-rouge">rahasia</code>. Ini akan kita pasang di konfigurasi Jenkins. Untuk Ubuntu, file konfigurasinya ada di <code class="language-plaintext highlighter-rouge">/etc/default/jenkins</code></p>
<p>Berikut adalah beberapa perubahan yang kita lakukan.</p>
<ul>
<li>Matikan port HTTP, dan nyalakan port HTTPS</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP_PORT=-1
HTTPS_PORT=8443
</code></pre></div></div>
<ul>
<li>Lokasi keystore dan passwordnya</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>KEY_STORE=/etc/jenkins/ssl/jenkins.jks
KEY_STORE_PASS=rahasia
</code></pre></div></div>
<ul>
<li>
<p>Opsi untuk menjalankan Jenkins. Tadinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> JENKINS_ARGS="--webroot=/var/cache/$NAME/war --httpPort=$HTTP_PORT"
</code></pre></div> </div>
<p>menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> JENKINS_ARGS="--webroot=/var/cache/$NAME/war --httpPort=$HTTP_PORT --httpsPort=$HTTPS_PORT --httpsKeyStore=$KEY_STORE --httpsKeyStorePassword=$KEY_STORE_PASS"
</code></pre></div> </div>
</li>
</ul>
<p>Setelah selesai mengedit, kita bisa restart Jenkins dengan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service jenkins restart
</code></pre></div></div>
<p>Harusnya Jenkins sudah bisa diakses di alamat <code class="language-plaintext highlighter-rouge">https://jenkins.artivisi.com:8443</code>.</p>
<h2 id="konfigurasi-port-forwarding">Konfigurasi Port Forwarding</h2>
<p>Kita ingin Jenkins bisa diakses tanpa menyebutkan port, yaitu di <code class="language-plaintext highlighter-rouge">https://jenkins.artivisi.com</code>. Untuk itu, kita lakukan redirect menggunakan <code class="language-plaintext highlighter-rouge">iptables</code>. Kita install dulu paket <code class="language-plaintext highlighter-rouge">iptables-persistent</code> agar setting firewall kita tidak hilang pada saat reboot.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install iptables-persistent -y
</code></pre></div></div>
<p>Kita akan ditanyakan apakah akan menyimpan setting saat ini. Jawab saja <code class="language-plaintext highlighter-rouge">No</code> karena kita belum mengkonfigurasi firewall.</p>
<p>Sebelum mengkonfigurasi firewall dengan <code class="language-plaintext highlighter-rouge">iptables</code>, terlebih dulu kita aktifkan settingnya di kernel. Jalankan perintah berikut di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo 1 > /proc/sys/net/ipv4/ip_forward
echo 1 > /proc/sys/net/ipv4/conf/eth0/route_localnet
</code></pre></div></div>
<p>Agar tidak hilang pada waktu reboot, tambahkan baris berikut di <code class="language-plaintext highlighter-rouge">/etc/sysctl.conf</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>net.ipv4.ip_forward=1
net.ipv4.conf.eth0.route_localnet = 1
</code></pre></div></div>
<p>Setelah itu, barulah kita pasang rule firewall. Jalankan perintah berikut di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8443
</code></pre></div></div>
<p>Untuk memahami perintah ini, silahkan baca artikel terdahulu tentang <a href="http://software.endy.muhardin.com/linux/network-address-translation/">Network Address Translation</a></p>
<p>Agar rule firewall ini tersimpan dan dijalankan setiap servernya reboot, maka kita gunakan aplikasi <code class="language-plaintext highlighter-rouge">iptables-persistent</code> yang sudah kita install tadi. Jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dpkg-reconfigure iptables-persistent
</code></pre></div></div>
<p>Kita akan disajikan pertanyaan apakah ingin menyimpan setting firewall yang aktif saat ini, mirip seperti pada waktu baru menginstall <code class="language-plaintext highlighter-rouge">iptables-persistent</code> tadi.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-ssl/save-firewall.png" alt="Konfirmasi simpan setting firewall" /></p>
<p>Jawab <code class="language-plaintext highlighter-rouge">Yes</code> untuk kedua pertanyaan. Hasilnya bisa dipastikan dengan melihat isi <code class="language-plaintext highlighter-rouge">/etc/iptables/rules.v4</code>. Kira-kira seperti ini isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Generated by iptables-save v1.6.0 on Mon Jan 23 04:50:46 2017
*nat
:PREROUTING ACCEPT [33:1840]
:INPUT ACCEPT [86:5016]
:OUTPUT ACCEPT [1:83]
:POSTROUTING ACCEPT [1:83]
-A PREROUTING -i eth0 -p tcp -m tcp --dport 443 -j REDIRECT --to-ports 8443
COMMIT
# Completed on Mon Jan 23 04:50:46 2017
# Generated by iptables-save v1.6.0 on Mon Jan 23 04:50:46 2017
*filter
:INPUT ACCEPT [78:8933]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [64:15339]
COMMIT
# Completed on Mon Jan 23 04:50:46 2017
</code></pre></div></div>
<p>Setelah itu, kita perlu mengkonfigurasi Jenkins URL, karena kita memalsukan portnya, aslinya jalan di 8443, tapi diakses di 443. Berikut Konfigurasinya</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-ssl/jenkins-url.png" alt="Konfigurasi Jenkins URL" /></p>
<p>Bila kita lupa, nanti Jenkins akan komplain bahwa reverse proxy belum dikonfigurasi dengan benar. Seperti ini pesan errornya</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2017/jenkins-ssl/reverse-proxy-broken.png" alt="Reverse Proxy Broken" /></p>
<h2 id="penutup">Penutup</h2>
<p>Selesai sudah konfigurasi Jenkins kita lengkap dengan SSL dan custom domain. Selanjutnya, kita bisa mulai <a href="http://software.endy.muhardin.com/java/jenkins-gitlab/">menambahkan project untuk dibuild secara otomatis dan periodik</a>. Stay tuned.</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins+on+Ubuntu">Tutorial Resmi Cara Instalasi Jenkins di Ubuntu</a></li>
<li><a href="http://sam.gleske.net/blog/engineering/2016/05/04/jenkins-with-ssl.html">Cara Konfigurasi SSL di Jenkins</a></li>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-using-iptables-on-ubuntu-14-04">Cara Setting Iptables di Ubuntu</a></li>
<li><a href="http://unix.stackexchange.com/a/125841">Cara menggunakan <code class="language-plaintext highlighter-rouge">iptables-persistent</code></a></li>
</ul>
Setup Firebase Cloud Messaging2016-12-15T07:00:00+07:00https://software.endy.muhardin.com/java/setup-firebase-cloud-messaging<p>Setup Firebase Cloud Messaging</p>
<p>Firebase Cloud Messaging (FCM) adalah solusi server push untuk aplikasi Android. Server push maksudnya adalah fitur yang kita gunakan apabila aplikasi server kita ingin mentrigger aplikasi mobile.</p>
<p>Google menyediakan fasilitas FCM yang bisa digunakan untuk aplikasi Android maupun iOS. Contoh skema penggunaannya dapat dilihat pada gambar berikut</p>
<p><img src="https://lh3.googleusercontent.com/N4768qRMt0y9aA-21zJmXRUPc3YbF0SbAPLnOR0t0W6Xw293GxOTN9htFiyFRm6MjEGLVZqG-gKM5hXarme3nvU2MGPrtlERS2TNqARYCt1aBzm70KBtwp3DCpw5ZDqoRQcQoa_EsC-5RKGlVW4_hfUPxA71iFpQ_yFCTejep13-B64KOkO-ELbwQHSSOPa7viCy4u3OjxrBPWM1Uk1vWDd0HN310uQSGlpZDLK1_cnRPkSquHzz39pg1y1H59cwCzw5kV_Ob4G_XEiBL689kLbJIvnmXpMAddxX-eKJ8uHuRdDwxZTx79-RIDzgaHWXCwoWrYE7ARebw-n81djjjzzb5yjlpgEtLZuYJzVE22Kqvox-w7vT-DsQzDScGer4UR0PqoAeuwaByNaFydake90XyOS9Rg0jrD3qg_UX8XltBAjFBRd5I8jVyLL0iuzxGzzH5ETC5rYzI1XdGYFI0XXnVNz9VQPIbUCmyCBF-_BzNNBc0Vp4fbPKOCczaMZowjTIzS4WF5bhFjdTHHO1paBNmJh0yRdWJIhVSg9wqGItNKN7UbpAPuRbU6aasgbSJRrsuGaJ542PUeJdgTDC8JvAErp4NWrR8rznIgGpcSjR4tL6-4esarw9ZwkcQrJ-wApAEVAhprIjNVy5wHNMDBirgJPx57Q0NycTi08vpQ=w1361-h538-no" alt="[Skema Update Tagihan](https://lh3.googleusercontent.com/N4768qRMt0y9aA-21zJmXRUPc3YbF0SbAPLnOR0t0W6Xw293GxOTN9htFiyFRm6MjEGLVZqG-gKM5hXarme3nvU2MGPrtlERS2TNqARYCt1aBzm70KBtwp3DCpw5ZDqoRQcQoa_EsC-5RKGlVW4_hfUPxA71iFpQ_yFCTejep13-B64KOkO-ELbwQHSSOPa7viCy4u3OjxrBPWM1Uk1vWDd0HN310uQSGlpZDLK1_cnRPkSquHzz39pg1y1H59cwCzw5kV_Ob4G_XEiBL689kLbJIvnmXpMAddxX-eKJ8uHuRdDwxZTx79-RIDzgaHWXCwoWrYE7ARebw-n81djjjzzb5yjlpgEtLZuYJzVE22Kqvox-w7vT-DsQzDScGer4UR0PqoAeuwaByNaFydake90XyOS9Rg0jrD3qg_UX8XltBAjFBRd5I8jVyLL0iuzxGzzH5ETC5rYzI1XdGYFI0XXnVNz9VQPIbUCmyCBF-_BzNNBc0Vp4fbPKOCczaMZowjTIzS4WF5bhFjdTHHO1paBNmJh0yRdWJIhVSg9wqGItNKN7UbpAPuRbU6aasgbSJRrsuGaJ542PUeJdgTDC8JvAErp4NWrR8rznIgGpcSjR4tL6-4esarw9ZwkcQrJ-wApAEVAhprIjNVy5wHNMDBirgJPx57Q0NycTi08vpQ=w1361-h538-no)" /></p>
<p>Untuk bisa menggunakan FCM, ada beberapa langkah yang harus dilakukan:</p>
<ol>
<li>Login dengan Google account di browser</li>
<li>Masuk ke <a href="https://console.firebase.google.com">Firebase Console</a></li>
<li>Membuat project</li>
<li>Menyambungkan project android kita ke project di Firebase Console</li>
<li>Membuat kode program di aplikasi Android untuk mendapatkan FCM token. FCM Token ini adalah identifier untuk perangkat dimana aplikasi kita berjalan.</li>
<li>Membuat kode program di aplikasi Android untuk menerima dan menangani message yang datang.</li>
<li>Mencatat Server Key (yang nantinya akan digunakan untuk mengirim message)</li>
<li>Membuat kode program di aplikasi server untuk mengirim message</li>
</ol>
<!--more-->
<h2 id="setup-fcm-di-aplikasi-android">Setup FCM di Aplikasi Android</h2>
<p>Bila kita menggunakan Android Studio Versi 2.2 ke atas, langkah 1 - 4 sudah disediakan alat bantunya. Kita bisa akses di menu Tools > Firebase</p>
<p><img src="https://lh3.googleusercontent.com/yUesvalbWIJcihQ3mLRxJBg20EhlWYxsZ_cVDibocaWJAPGEJzh70WmsCEnVj_nfCC8gZTW_w5Kjwq6Ou96lvThH-DhsQKTbtQkIQLuUkNHByNZigZFAjKYKu3sz4J2JSu0_sA6racdGe3SknNPyP5ppJXG4usPvSCrK9MCrjy4Mny3RIeayoNuWcBrq5uw6FD7YBK6rZrg4zX8CrwIss4BhO5vzMddsyeTkt2HUDE8QyUc2JfvIuyAkKPNEOB3hvpbv8PJXjKmKHUe0BqUm12dpDUUBYh1aViyMSFs56yn6wTnan8m47UiptK7UfSKV2Z5mUTGR54Ke_TFJYaTlm11DcJWmAn0nzN2P1YQ7ugL6yybzqdIbwQTIKg94UqAVTfrp6sJhMH5KQaDDNyMSEr_QpMbglTeH6wYXm7JS5au_WO6LtpN_ohnLQWGa4e_k_ozU3XBsaC5EfXf6sZoKjTDjTVj9D66Tf8SlMrGueOMgwzAnCBgNjYjd-7smLS0IMgb2ujboEKIxABBQmNBJMdW8nhP5c2W6ogDXppFDjhI8UfY2jtdY6hC77UNe9SyCKCV2NMQewUdYKoMyxKjTuvU6UjmU39jwATltehIWGqjsP4t_e8xQul_E2-LqxvueDu1-AEwck7sqCFnV2i0nm4Ayzf9kpcUdyH2AQaTkQQ=w1235-h694-no" alt="[Menu Tools Firebase](https://lh3.googleusercontent.com/yUesvalbWIJcihQ3mLRxJBg20EhlWYxsZ_cVDibocaWJAPGEJzh70WmsCEnVj_nfCC8gZTW_w5Kjwq6Ou96lvThH-DhsQKTbtQkIQLuUkNHByNZigZFAjKYKu3sz4J2JSu0_sA6racdGe3SknNPyP5ppJXG4usPvSCrK9MCrjy4Mny3RIeayoNuWcBrq5uw6FD7YBK6rZrg4zX8CrwIss4BhO5vzMddsyeTkt2HUDE8QyUc2JfvIuyAkKPNEOB3hvpbv8PJXjKmKHUe0BqUm12dpDUUBYh1aViyMSFs56yn6wTnan8m47UiptK7UfSKV2Z5mUTGR54Ke_TFJYaTlm11DcJWmAn0nzN2P1YQ7ugL6yybzqdIbwQTIKg94UqAVTfrp6sJhMH5KQaDDNyMSEr_QpMbglTeH6wYXm7JS5au_WO6LtpN_ohnLQWGa4e_k_ozU3XBsaC5EfXf6sZoKjTDjTVj9D66Tf8SlMrGueOMgwzAnCBgNjYjd-7smLS0IMgb2ujboEKIxABBQmNBJMdW8nhP5c2W6ogDXppFDjhI8UfY2jtdY6hC77UNe9SyCKCV2NMQewUdYKoMyxKjTuvU6UjmU39jwATltehIWGqjsP4t_e8xQul_E2-LqxvueDu1-AEwck7sqCFnV2i0nm4Ayzf9kpcUdyH2AQaTkQQ=w1235-h694-no)" /></p>
<p>Begitu diklik, maka akan muncul Firebase Assistant. Selanjutnya kita buka menu Cloud Messaging.</p>
<p><img src="https://lh3.googleusercontent.com/XKlOb6-mR0y0ZKUswKF0lq2fhge3_I9JbrOIKIhNW7Vj-H6HtEbQo0bvUUdBLah20XiHAXpY1olFvlFF_DNWNewvKp2L3_GFmoz5_WWKApJQZ1orx4nx5gK4z_v3jwIAWGEmwPT2fJ84StaAdaA7BxgEDdIpGzuXsh_IqzZN38vIgCgZnQBrfU1xz5oKsNjxt0NtsUl4qrgkMgEAwfKNHuIGmpLNhcw17FMCUJL9cJeYaUXzj03gQKpd3a9iaNzmruPOOJE7srTSvMrq4Ovl8e-VgqZ9DE8Pj2dNaFGkaJncOIaJI6-3MNeyQeNWbpILYDbpdYBZxBomYasTIVvyNX9ZgMdYhZdTrt15ooGuZc3NU1-xE-Naf6OVChYryYa26hltqfbJuMM6PcRRQ5cqDH1ZuzXEK-XAbPtpnV_ziDB3pDX5By-TGT4iqIYG7GW4nnkrlVlhXs0oLpOMPuUprpRMyAscU1nKuD9jN9coGLycyYYSFZ1uNmKD8WUACq3cS_dIje4SF30rFwQzgsaWWaoMsSPPMYKWOu3kEM0CMfgcXfSERilptU-9Zrnq6-_fDdyTbs9LdcxTnJLGbHvLnBW2a6coSDe9B9NaB0iDMbFSqQFnEzOZx9z_EcYJhZjMIUR8dS5vdzS3UPYTh4g5chzvzmfKK2KdWQjtmOPP-w=w1235-h694-no" alt="[Cloud Messaging](https://lh3.googleusercontent.com/XKlOb6-mR0y0ZKUswKF0lq2fhge3_I9JbrOIKIhNW7Vj-H6HtEbQo0bvUUdBLah20XiHAXpY1olFvlFF_DNWNewvKp2L3_GFmoz5_WWKApJQZ1orx4nx5gK4z_v3jwIAWGEmwPT2fJ84StaAdaA7BxgEDdIpGzuXsh_IqzZN38vIgCgZnQBrfU1xz5oKsNjxt0NtsUl4qrgkMgEAwfKNHuIGmpLNhcw17FMCUJL9cJeYaUXzj03gQKpd3a9iaNzmruPOOJE7srTSvMrq4Ovl8e-VgqZ9DE8Pj2dNaFGkaJncOIaJI6-3MNeyQeNWbpILYDbpdYBZxBomYasTIVvyNX9ZgMdYhZdTrt15ooGuZc3NU1-xE-Naf6OVChYryYa26hltqfbJuMM6PcRRQ5cqDH1ZuzXEK-XAbPtpnV_ziDB3pDX5By-TGT4iqIYG7GW4nnkrlVlhXs0oLpOMPuUprpRMyAscU1nKuD9jN9coGLycyYYSFZ1uNmKD8WUACq3cS_dIje4SF30rFwQzgsaWWaoMsSPPMYKWOu3kEM0CMfgcXfSERilptU-9Zrnq6-_fDdyTbs9LdcxTnJLGbHvLnBW2a6coSDe9B9NaB0iDMbFSqQFnEzOZx9z_EcYJhZjMIUR8dS5vdzS3UPYTh4g5chzvzmfKK2KdWQjtmOPP-w=w1235-h694-no)" /></p>
<p>Kita akan mendapati langkah-langkah setupnya.</p>
<p><img src="https://lh3.googleusercontent.com/ISZuQJSHxMmXOOcDnTdrz-RYODzt1SpsQ-8aT7r4bUXpuE-sn0yV51nQFzktr6R10sftZgtMtR9KBRPk_GsU1vN9DHtsdyTL2LiK-C94ZpSEv1EtsxzXm47LYHQ5WnQEbR_kHagFPZFskzjwSINY3K8oP817JRRP3DPFNvG9QGFBgjmAoEGW64rgU9U6nBnJwJGEDNMk56FWDTz7xSb8V0mxtSs5h6omvbHSCOAqk4SF1g7-0RZYkKQlJ9_RLA6t-uq5uEylJRuoo8rSAVD4-sntfRhVhCAqckCL8MvKpA2VeAZPGMbJ0iLZwNWspHQbAzF-7yba5P8hKHOFNpzgJjfuR9Tjp4rQgvs4uVk5McDoClyXqNHJWJulx5u0RJQlxxGk6uRrBHlh22mrfuSlS-GSc3TqXMQ3Es4X7dwd40KtR8HAJExIE-9OBMY29i6WQCbjV90vF-vFN4YN6IvKIJ_4hHc_uWRbm_90IpmSq7aPjKekKCdCjIQ50W1Y3n0zBhAOBcbQGcYYAQiA7NFQ_Ej9y-VL-Tdv4IEPeK80k48_BLtz8xwNUXsAHcWOb3CsGlu0gvGtGuiiE-kuVwq573lxyD_ODQvv0lmtgQyLO8B6qQO9xCGpDBQkVlU_4coxmqzBRQgeTmGzTBNS5vSSHsMHIiGgb8D0OfBbevOMNg=w1235-h694-no" alt="[Langkah-langkah setup](https://lh3.googleusercontent.com/ISZuQJSHxMmXOOcDnTdrz-RYODzt1SpsQ-8aT7r4bUXpuE-sn0yV51nQFzktr6R10sftZgtMtR9KBRPk_GsU1vN9DHtsdyTL2LiK-C94ZpSEv1EtsxzXm47LYHQ5WnQEbR_kHagFPZFskzjwSINY3K8oP817JRRP3DPFNvG9QGFBgjmAoEGW64rgU9U6nBnJwJGEDNMk56FWDTz7xSb8V0mxtSs5h6omvbHSCOAqk4SF1g7-0RZYkKQlJ9_RLA6t-uq5uEylJRuoo8rSAVD4-sntfRhVhCAqckCL8MvKpA2VeAZPGMbJ0iLZwNWspHQbAzF-7yba5P8hKHOFNpzgJjfuR9Tjp4rQgvs4uVk5McDoClyXqNHJWJulx5u0RJQlxxGk6uRrBHlh22mrfuSlS-GSc3TqXMQ3Es4X7dwd40KtR8HAJExIE-9OBMY29i6WQCbjV90vF-vFN4YN6IvKIJ_4hHc_uWRbm_90IpmSq7aPjKekKCdCjIQ50W1Y3n0zBhAOBcbQGcYYAQiA7NFQ_Ej9y-VL-Tdv4IEPeK80k48_BLtz8xwNUXsAHcWOb3CsGlu0gvGtGuiiE-kuVwq573lxyD_ODQvv0lmtgQyLO8B6qQO9xCGpDBQkVlU_4coxmqzBRQgeTmGzTBNS5vSSHsMHIiGgb8D0OfBbevOMNg=w1235-h694-no)" /></p>
<p>Mulai dari langkah pertama, klik Connect your app to firebase. Browser akan dibukakan untuk kita dan kita disuruh login dengan Google Account. Apabila kita punya beberapa Google account, maka kita akan disuruh memilih akun mana yang ingin digunakan.</p>
<p><img src="https://lh3.googleusercontent.com/_9KN62cj4OX8E6MbNma7_38RCEpPfPw3f9fa2e-F0I9e6PsVljuVLxIJ3dOU501IYNb8GtPTxHkhH4r0ey8_CeWZ8XyM7sl8XbAL3AN7AycJLeKmjJ1wRtP1MD6rmwGXXI_tTyPH90OUddFIujh-t7ePaoaMzZ5ABiDNCRF0kX2qb5JevWzKTk45hmYVhz-5m21HBmUsQHQ70Tj81uImCmDfdZxm2UX4C_BvGJe9NNE91VgOX6eCqWhx6mVQDY6XDky7AAz5qjU7tzLraTYowYR3_74qcql4_O1D9ZOXRaxlov4vI01l69Mp2ZVAMMQfWE-ckhPLkmHRwv8FAAXrJlGH7G-WFv8_cF8UIbiwTqDBSHIUZ0gah82FVRHajoLT0POfhptWqrr9fm9zcH6eZLNh2xDl0228qlAcrI2T4FyJdZGek9g-j-ppsSyActOFlSYBjDLN3EV7VA2iV4mBvUoNAQgJCl-z04NiuvwypToDrB9KI5LvJaN5aZviCYHBelnu3QsoWfZRfpwhzes6DszI_3ytkAY4ZK5TIeL5yXX0eGTkQjMWC8KhDU1iT5-Sy1pZI4C6ooFScZD_anoZ8339JldMKjejvZI-b9RWtYdJL3vR14PQ6M4l85p-8kSStDDu5QJNmbglExomItjJFNCZIeDb9jSwszbAEUE8RA=w518-h416-no" alt="[Pilih akun google](https://lh3.googleusercontent.com/_9KN62cj4OX8E6MbNma7_38RCEpPfPw3f9fa2e-F0I9e6PsVljuVLxIJ3dOU501IYNb8GtPTxHkhH4r0ey8_CeWZ8XyM7sl8XbAL3AN7AycJLeKmjJ1wRtP1MD6rmwGXXI_tTyPH90OUddFIujh-t7ePaoaMzZ5ABiDNCRF0kX2qb5JevWzKTk45hmYVhz-5m21HBmUsQHQ70Tj81uImCmDfdZxm2UX4C_BvGJe9NNE91VgOX6eCqWhx6mVQDY6XDky7AAz5qjU7tzLraTYowYR3_74qcql4_O1D9ZOXRaxlov4vI01l69Mp2ZVAMMQfWE-ckhPLkmHRwv8FAAXrJlGH7G-WFv8_cF8UIbiwTqDBSHIUZ0gah82FVRHajoLT0POfhptWqrr9fm9zcH6eZLNh2xDl0228qlAcrI2T4FyJdZGek9g-j-ppsSyActOFlSYBjDLN3EV7VA2iV4mBvUoNAQgJCl-z04NiuvwypToDrB9KI5LvJaN5aZviCYHBelnu3QsoWfZRfpwhzes6DszI_3ytkAY4ZK5TIeL5yXX0eGTkQjMWC8KhDU1iT5-Sy1pZI4C6ooFScZD_anoZ8339JldMKjejvZI-b9RWtYdJL3vR14PQ6M4l85p-8kSStDDu5QJNmbglExomItjJFNCZIeDb9jSwszbAEUE8RA=w518-h416-no)" /></p>
<p>Setelah kita pilih, maka akan ada konfirmasi ijin akses buat Android Studio. Klik saja Accept.</p>
<p><img src="https://lh3.googleusercontent.com/h_RqmqbOLX-8Ar8lVRg71iJJ-1AC2HNVObLonu8Tbhl_2khalRSY9jdcFCu1ytewRV-Tw0dfSL1fj49Y7JCxC8HXDMrlR4NFvwwZEVlhUhpXPlX-6YmTzmZULtg82NxLKZ36PmYqK2918bpGkfCgPeBGUOYXd_bZf3hNmSDIuYubjcKqLYQJ_w58MWP97rgJRcBbyJfY6zx49ieYJxo3bBsMQaDTZl3ugcpZyx-al3O99an-_zJ-c0KJTRMuxYiscF4StJEEAK1-vdCO2qOp2mDIBWJO5H-jcUdnHYrD5-eYJH88AxluWAnU7I2ei2ncrAg0gkCQHe7kC-L4khH6-6CTUJn_h8HoakhcMzxleQYiz8QSO4GTffsh5CfQQoC1cuYTLaaV2RI-7iF3sk4HydQYt6FGO1Yzae3R2kjQRUNR4aUBYAZgseko-XeVHX-1fny85D00Gd2MpHxSqdTdtakfu1ScVcqvT8MmOGs0UlgZ1u27MambOHXRS6F3IiqAJ9Urbr4YWAs2YZlkvXXBXdjF7UxTHOtacwb7WeKhlXS9XAg8tvrf61OyMDt1wZSo-7k0CEKylhwv8Rvv3l2y__z8CMI-1d2jvNXEO32c9YG7Y7GKQFhKOXceCotNSKliyK-RmJ4g4Lo2j3ALrmKn1EqB2rjG0lmfx-y1J5UVyw=w517-h659-no" alt="[Konfirmasi Ijin Akses](https://lh3.googleusercontent.com/h_RqmqbOLX-8Ar8lVRg71iJJ-1AC2HNVObLonu8Tbhl_2khalRSY9jdcFCu1ytewRV-Tw0dfSL1fj49Y7JCxC8HXDMrlR4NFvwwZEVlhUhpXPlX-6YmTzmZULtg82NxLKZ36PmYqK2918bpGkfCgPeBGUOYXd_bZf3hNmSDIuYubjcKqLYQJ_w58MWP97rgJRcBbyJfY6zx49ieYJxo3bBsMQaDTZl3ugcpZyx-al3O99an-_zJ-c0KJTRMuxYiscF4StJEEAK1-vdCO2qOp2mDIBWJO5H-jcUdnHYrD5-eYJH88AxluWAnU7I2ei2ncrAg0gkCQHe7kC-L4khH6-6CTUJn_h8HoakhcMzxleQYiz8QSO4GTffsh5CfQQoC1cuYTLaaV2RI-7iF3sk4HydQYt6FGO1Yzae3R2kjQRUNR4aUBYAZgseko-XeVHX-1fny85D00Gd2MpHxSqdTdtakfu1ScVcqvT8MmOGs0UlgZ1u27MambOHXRS6F3IiqAJ9Urbr4YWAs2YZlkvXXBXdjF7UxTHOtacwb7WeKhlXS9XAg8tvrf61OyMDt1wZSo-7k0CEKylhwv8Rvv3l2y__z8CMI-1d2jvNXEO32c9YG7Y7GKQFhKOXceCotNSKliyK-RmJ4g4Lo2j3ALrmKn1EqB2rjG0lmfx-y1J5UVyw=w517-h659-no)" /></p>
<p>Kita akan mendapatkan pesan sukses.</p>
<p><img src="https://lh3.googleusercontent.com/-NrACPupyRz_Le1T9alCPe61RmYdVnKhNDG_GWcGkW8f4qQlF1iy3cPRA2gglTSTqaOJg7uXDVzCcZdo9uAauSKvUEY1RCWEoJxvuBke_aSAEliftQmn8m32IMRv_D3g0-8S5bK4AqdKb-lk_nJ5tcA2VkSR3vTl-CEJMONX0r0xw2tvjmxWTY0i_A-BdpfQp5-73Ud_aunZ1qtMNI73taAcFmfBX3YYqYICpdlgzA3vGzuj5iXVPLh09ASrjqAPX9uDfQxnAixfsSZlYU2bZieo45DjeW_csNlMJIwred0ZY3vjhhT_Iudx4G0DAQMw3wN1rl3F4zo-XIi3vRMM8SjazqeQocwO78UKvhNCzyHkPkkXE-97GwO9cLrxUXcFlq0Zp5UAgUX9OY3kHtPNDX9dRdHZ0G_tUevNpGNu4CZE_svyiahUXU_gNUz3iLTVIZyglZumgHYYCnO9xLI8Fc0JTgiFJm5u-QwAqSk8T4OlAN-WreVPpSQO7LehZUWE2PhATdaGt8F-L0-aGtmzMlQSpEdjE9DIcnthyrOAsLQR_lZA1oma2M2jLre7vfCrgh1hJUrlOQkY3brqtXEYGN13eyS1jBRORaHV0A8_K2OpoEIGDcygOh3KTjNku8Au2U0NOG6ieW9QkZTTTg_bJRemDZWXfBzhek9rcWwP_g=w582-h424-no" alt="[Sukses otentikasi Android Studio](https://lh3.googleusercontent.com/-NrACPupyRz_Le1T9alCPe61RmYdVnKhNDG_GWcGkW8f4qQlF1iy3cPRA2gglTSTqaOJg7uXDVzCcZdo9uAauSKvUEY1RCWEoJxvuBke_aSAEliftQmn8m32IMRv_D3g0-8S5bK4AqdKb-lk_nJ5tcA2VkSR3vTl-CEJMONX0r0xw2tvjmxWTY0i_A-BdpfQp5-73Ud_aunZ1qtMNI73taAcFmfBX3YYqYICpdlgzA3vGzuj5iXVPLh09ASrjqAPX9uDfQxnAixfsSZlYU2bZieo45DjeW_csNlMJIwred0ZY3vjhhT_Iudx4G0DAQMw3wN1rl3F4zo-XIi3vRMM8SjazqeQocwO78UKvhNCzyHkPkkXE-97GwO9cLrxUXcFlq0Zp5UAgUX9OY3kHtPNDX9dRdHZ0G_tUevNpGNu4CZE_svyiahUXU_gNUz3iLTVIZyglZumgHYYCnO9xLI8Fc0JTgiFJm5u-QwAqSk8T4OlAN-WreVPpSQO7LehZUWE2PhATdaGt8F-L0-aGtmzMlQSpEdjE9DIcnthyrOAsLQR_lZA1oma2M2jLre7vfCrgh1hJUrlOQkY3brqtXEYGN13eyS1jBRORaHV0A8_K2OpoEIGDcygOh3KTjNku8Au2U0NOG6ieW9QkZTTTg_bJRemDZWXfBzhek9rcWwP_g=w582-h424-no)" /></p>
<p>Berikutnya, kita akan disajikan pilihan apakah akan membuat project Firebase yang baru, atau menggunakan project yang sudah ada.</p>
<p><img src="https://lh3.googleusercontent.com/oarRaDebeCqbMWQwFAwXnSk-XtGcTuUdkR43GNbN93ZHmKWz2G4f6zbykop632mJaVD7Fp9E5d5govfso_UX7hipxgo-GYMoXrllVdp1PrxHU2zvX-PWXoT4rx_AvS6sOaQUIocxfA4qAkExapQQ7epXtk7aqS5nQzEf2S3IZeoE52DJ-rnY3hObpm0aoq9-S7c-BKRb67FiUxT8ShGFw822MawDPZ4jWArvqvbcgR1yVO00d9e7osEJ8a5NF9e0KA0bSMkwau3rp4sUjBTUNXTN3-wCaV4IFpq7zRzsPCc2m0RxmwhKsQNFTVs7lqAnsW9T3qio0sJZR1KJLTh_VJia6I6OzVLXnPKGWrTmJjn0phGRg7ytmd3BVV1jhj4hhUnrevTDrEMAAvottb5YMXCoRgbTrsK6w-Tq3-VoaVlp2ULSlPME_NJv6vOzpm6sQ8fcS1xSxsXnMI7B4ryp6FQhJluokXaNgP6zorJsqQdQeCgkAVmyKFIbvqIoZs0QKJKVxHvIf15bYnv_D9ro-iLdtfIhE4QX4Ctc_xow7KS2orMRmu041Tw5PvkZ2vwwZXmbjmFPWEG-OvHImAKMIN80TjSg5qFvrXEvnnpJrL0Bfq66MQ__sNYcr-JY0agI-_ZxUwYHDzkRtSr4TEXW5Lq1BQqBrwyvjAchmqNNEg=w1235-h694-no" alt="[Membuat project Firebase](https://lh3.googleusercontent.com/oarRaDebeCqbMWQwFAwXnSk-XtGcTuUdkR43GNbN93ZHmKWz2G4f6zbykop632mJaVD7Fp9E5d5govfso_UX7hipxgo-GYMoXrllVdp1PrxHU2zvX-PWXoT4rx_AvS6sOaQUIocxfA4qAkExapQQ7epXtk7aqS5nQzEf2S3IZeoE52DJ-rnY3hObpm0aoq9-S7c-BKRb67FiUxT8ShGFw822MawDPZ4jWArvqvbcgR1yVO00d9e7osEJ8a5NF9e0KA0bSMkwau3rp4sUjBTUNXTN3-wCaV4IFpq7zRzsPCc2m0RxmwhKsQNFTVs7lqAnsW9T3qio0sJZR1KJLTh_VJia6I6OzVLXnPKGWrTmJjn0phGRg7ytmd3BVV1jhj4hhUnrevTDrEMAAvottb5YMXCoRgbTrsK6w-Tq3-VoaVlp2ULSlPME_NJv6vOzpm6sQ8fcS1xSxsXnMI7B4ryp6FQhJluokXaNgP6zorJsqQdQeCgkAVmyKFIbvqIoZs0QKJKVxHvIf15bYnv_D9ro-iLdtfIhE4QX4Ctc_xow7KS2orMRmu041Tw5PvkZ2vwwZXmbjmFPWEG-OvHImAKMIN80TjSg5qFvrXEvnnpJrL0Bfq66MQ__sNYcr-JY0agI-_ZxUwYHDzkRtSr4TEXW5Lq1BQqBrwyvjAchmqNNEg=w1235-h694-no)" /></p>
<p>Dalam hal ini saya memilih membuat project baru. Nama project sudah diusulkan oleh Android Studio, yaitu disamakan dengan nama project android kita. Begitu kita klik OK, maka Android Studio akan berusaha membuat project di Firebase. Aktivitasnya bisa kita pantau di Event Log</p>
<p><img src="https://lh3.googleusercontent.com/wPWxkeaCdrk-f6bh_anysiEEnYYFHsTb8345MuXOjkWt-lUOElugxP8HEH6T3CWh-L-h3lMUErvUGOCQVB13Ftr4K64J3xHKDSPqbkiWZRJvOEjpkwDD8rIqWZJwmplYAr3Oa26tfB5iH4EM3ABDx4wKSs1jp-kAAEc0AKwRcZyvNsVzDzB8yMg7LjinDPpc2fDsmUtjgIE_a4r3-H8wPzjdgOEsiO72sHIv7uknFxT_MhYUaGBzstZWsRG6Atujl7xV-BsKKTkZaJvDcKq8dFasyobv2rxA-ebzMxuBTeMA69X83a7bNajtardtkD7IizJN_vcrs2rsFRXx5Tm5t32XqmEr1VcSHH7Npd8qjCHeDxXbKP_ZpGidZrjp6qnxksTvlRGLBz3pxgCtvkuop4l8n3swF4ILpSuiSZNc7xBFE3umNPN72smCrfAcoCqElVX-uTqevRgcYf9cEz61zRroC3ZHB5aKOImREu0DZHncf4n3BfP5XrcXQWATR2gmYDAqLUMMcOk1_ZwAYMdkWrrQ4TLTBgwK5qwC2ZaGiwerb1Zx9nqmIFkfZwjh-uL-tm1XE5FqSsuZTOEkUIA4XG0wrXE7U2kUWf7P7Tye-_P-y_iP20NolK0BxJX7f1ddDRAGEO3guZd3sGbxDI0nMRqfQPoXKIrFUcQ8aMOaGA=w651-h461-no" alt="[Event Log Create Project](https://lh3.googleusercontent.com/wPWxkeaCdrk-f6bh_anysiEEnYYFHsTb8345MuXOjkWt-lUOElugxP8HEH6T3CWh-L-h3lMUErvUGOCQVB13Ftr4K64J3xHKDSPqbkiWZRJvOEjpkwDD8rIqWZJwmplYAr3Oa26tfB5iH4EM3ABDx4wKSs1jp-kAAEc0AKwRcZyvNsVzDzB8yMg7LjinDPpc2fDsmUtjgIE_a4r3-H8wPzjdgOEsiO72sHIv7uknFxT_MhYUaGBzstZWsRG6Atujl7xV-BsKKTkZaJvDcKq8dFasyobv2rxA-ebzMxuBTeMA69X83a7bNajtardtkD7IizJN_vcrs2rsFRXx5Tm5t32XqmEr1VcSHH7Npd8qjCHeDxXbKP_ZpGidZrjp6qnxksTvlRGLBz3pxgCtvkuop4l8n3swF4ILpSuiSZNc7xBFE3umNPN72smCrfAcoCqElVX-uTqevRgcYf9cEz61zRroC3ZHB5aKOImREu0DZHncf4n3BfP5XrcXQWATR2gmYDAqLUMMcOk1_ZwAYMdkWrrQ4TLTBgwK5qwC2ZaGiwerb1Zx9nqmIFkfZwjh-uL-tm1XE5FqSsuZTOEkUIA4XG0wrXE7U2kUWf7P7Tye-_P-y_iP20NolK0BxJX7f1ddDRAGEO3guZd3sGbxDI0nMRqfQPoXKIrFUcQ8aMOaGA=w651-h461-no)" /></p>
<p>Proses ini akan menghasilkan file <code class="language-plaintext highlighter-rouge">google-services.json</code> dalam folder project Android kita.</p>
<p><img src="https://lh3.googleusercontent.com/4r2BUbCkJQVkUngzrSnHUY_31WigM_D2jk5CYWM9Ni_VwUAZ1DzyYFs_Fevg1fOx9n2j5-i-Qtqm3Mg5J-wO-r9jng4eqoYm45-0sT3gRKtVA1K-DgSTG_r2N0oxC9BOuoV_KOQcmCn98LRjPH3IMFMjptrWRrHE5zS06mOht3EwiwZicNxTMXZD3rIShxCLF2MK-RK0gSImkLMM8Lh2XaxPcT-1IZPXpqz9pt77G5pJt-0T4jou0tuAouPDHB-l9YryC1iJJFaEXFKSzblw2ATQ22mLDmk0JNVirvZQulkTDgWsgImckaMdZoLKdN3ZKyAmb6W5m6tqXmiJglwA8R-gFCLijchw-Ygv8Xh3a6B-bIK-dPxEs1Fl2_BH-uvmkDpU9yGr8dzP2sYcGoHjz6zbCJ72pxtW3ORpSQrwPDuVXu_W4oFIBZoyLtVAXyB0v_1tngZeYNDyf4pJR5YsaTyTYRf3pxJqvmBxT2pr3x0FjgrcH7Ls1ez7ciyp-jgWbx4gTYwYOHY5mssyJqKJskHSk8POcLC91qi9TYrInmWm9hh26cFN93u7DoTZe474Ba5DNyLi0tjbnK3fwMY1hMe4_2VROnQiZIJ1MClcyJyTLoYVFKWYlTYUoyo_cWbh3rWogZPlxh7VREb14j6xPVEtaC6u-1iixfp9PFwUqQ=w698-h180-no" alt="[File JSON Google Services](https://lh3.googleusercontent.com/4r2BUbCkJQVkUngzrSnHUY_31WigM_D2jk5CYWM9Ni_VwUAZ1DzyYFs_Fevg1fOx9n2j5-i-Qtqm3Mg5J-wO-r9jng4eqoYm45-0sT3gRKtVA1K-DgSTG_r2N0oxC9BOuoV_KOQcmCn98LRjPH3IMFMjptrWRrHE5zS06mOht3EwiwZicNxTMXZD3rIShxCLF2MK-RK0gSImkLMM8Lh2XaxPcT-1IZPXpqz9pt77G5pJt-0T4jou0tuAouPDHB-l9YryC1iJJFaEXFKSzblw2ATQ22mLDmk0JNVirvZQulkTDgWsgImckaMdZoLKdN3ZKyAmb6W5m6tqXmiJglwA8R-gFCLijchw-Ygv8Xh3a6B-bIK-dPxEs1Fl2_BH-uvmkDpU9yGr8dzP2sYcGoHjz6zbCJ72pxtW3ORpSQrwPDuVXu_W4oFIBZoyLtVAXyB0v_1tngZeYNDyf4pJR5YsaTyTYRf3pxJqvmBxT2pr3x0FjgrcH7Ls1ez7ciyp-jgWbx4gTYwYOHY5mssyJqKJskHSk8POcLC91qi9TYrInmWm9hh26cFN93u7DoTZe474Ba5DNyLi0tjbnK3fwMY1hMe4_2VROnQiZIJ1MClcyJyTLoYVFKWYlTYUoyo_cWbh3rWogZPlxh7VREb14j6xPVEtaC6u-1iixfp9PFwUqQ=w698-h180-no)" /></p>
<p>File ini berisi konfigurasi dan otentikasi agar aplikasi android kita bisa terhubung dengan Firebase. Setelah selesai, maka langkah pertama akan dinyatakan sukses, ditandai dengan icon berwarna hijau.</p>
<p><img src="https://lh3.googleusercontent.com/Vy09kI0rxkgdBkSuwfNTLAmLKCMAMZkVQHnYTHibVX5lNcORU0p1R8-o9N67MBquGp1R6WUVVoXNx3LsWlhKw75zNp-RbU_swZathacukUNJalBkixN_tT9wYcRt-CT8J8xgrpc0SSSO4bNA5t8P9mHeXXfRlQCrwLIhPwnjPV-zRB-cyfWtf4mRLJ32FKEYafelQkUfj92_8uZeFHV8DTaUmRM5LsjOrng_dJZ5-cBALLP1SLpGxv39uVXqn_vWJt6nr3iNWcgaun0weovcuINWi8GNU1oQklfzaEPaE1iZt2P5WAVmwcouv7KUjPohVMG2RYmYv1itmm2FfTICoTyregIVsQffkfHYE8I7LjJl9JSwUbFzpYWNuD13KKhlJc-1p7GLnynV70Y_bFxcs6_h3-MRPw7lpeaIL2fyXimx7Se68CMnk04XG3gtgL0EqGdixS9srsYGZSLn2wWZwaXsFmf1qhPYYlM5X5kr6759KsWj1WAcOLdM_KBeOSGDlc9cFVENR3AgDsA6VG85rXMA9QrIOt97N4eSJHKz-hua6ofG8c0pvihIAPIiBwoPyxu5Eq_A__Y9KM9uBXviuGjs9Fg3ImnxmVMDZIZPPeXJ4sqVbROY2QJS5Poh0yyBL13Q187eYtjwdlBYD5sprlLdHa_ARNEdf7wlFhkhlg=w435-h652-no" alt="[Sukses connect app](https://lh3.googleusercontent.com/Vy09kI0rxkgdBkSuwfNTLAmLKCMAMZkVQHnYTHibVX5lNcORU0p1R8-o9N67MBquGp1R6WUVVoXNx3LsWlhKw75zNp-RbU_swZathacukUNJalBkixN_tT9wYcRt-CT8J8xgrpc0SSSO4bNA5t8P9mHeXXfRlQCrwLIhPwnjPV-zRB-cyfWtf4mRLJ32FKEYafelQkUfj92_8uZeFHV8DTaUmRM5LsjOrng_dJZ5-cBALLP1SLpGxv39uVXqn_vWJt6nr3iNWcgaun0weovcuINWi8GNU1oQklfzaEPaE1iZt2P5WAVmwcouv7KUjPohVMG2RYmYv1itmm2FfTICoTyregIVsQffkfHYE8I7LjJl9JSwUbFzpYWNuD13KKhlJc-1p7GLnynV70Y_bFxcs6_h3-MRPw7lpeaIL2fyXimx7Se68CMnk04XG3gtgL0EqGdixS9srsYGZSLn2wWZwaXsFmf1qhPYYlM5X5kr6759KsWj1WAcOLdM_KBeOSGDlc9cFVENR3AgDsA6VG85rXMA9QrIOt97N4eSJHKz-hua6ofG8c0pvihIAPIiBwoPyxu5Eq_A__Y9KM9uBXviuGjs9Fg3ImnxmVMDZIZPPeXJ4sqVbROY2QJS5Poh0yyBL13Q187eYtjwdlBYD5sprlLdHa_ARNEdf7wlFhkhlg=w435-h652-no)" /></p>
<p>Berikutnya, langkah kedua, kita akan menambahkan library yang dibutuhkan oleh Google services ke dalam project Android kita.</p>
<p><img src="https://lh3.googleusercontent.com/iTjKPs3YlQ2Lvm4RNQcUao8iVSX0Ut8r1FrRzkivdbW4sG-0596M_stmpQyJts6z8cGFxeYrOmVkreREP4v_8l3GhgsKTokxi_wGXgf5DlQ8rQISydLDkSyN-oG2xiwPcCZebqIqL2b5jFl3pCvjlIkek56_ggKeux78sG2Ok8QrLzkwbSdX9gXQuY1Xvh9y74a70bQpP1562sy3mlW3LES5-d4z4N6UFNJ0_U04xKtDi-GNHEGfUGmoY4Y0iTm8tH8sRb6CPx0rcsX3OXXM7i8pX75uSDp_iUZD1oc0K3iY7iE_eJc4nZVBBHctcO_AB0fv7-8LtMw-ZSIQXGD00ceWXZZ2FDo2YE30cAT1jxWW-FhOFqy2pVfUy8MfrhRauKfbcUAOG9sVNxYjo-fYFvotm62mFYuqMkA12ArXHoMVvKdJmh5hVIH6GhUkTmsASoCbF-JeTQjo1oEGOXI6YU8ymqnmkyd2dcxEBYtvGz9IvgjNvNej31xW9eTJ7XCPFHJP9tg-3QoE5hKIM6V-vAl-PRKozPEH8P-oMHHexjHQGBwqjDSSOyvNz9Jr0PSsz7iz8JqujbFx4EaR7F6ortNt8l1dDNWm_VSwKtrWreMKsC75kjD9evnMgRsH1xznDZFGL3aCbBz4QqwQ9IXaC7jxQ_5zbexmYmMoQlZnYw=w1235-h694-no" alt="[Menambahkan dependensi](https://lh3.googleusercontent.com/iTjKPs3YlQ2Lvm4RNQcUao8iVSX0Ut8r1FrRzkivdbW4sG-0596M_stmpQyJts6z8cGFxeYrOmVkreREP4v_8l3GhgsKTokxi_wGXgf5DlQ8rQISydLDkSyN-oG2xiwPcCZebqIqL2b5jFl3pCvjlIkek56_ggKeux78sG2Ok8QrLzkwbSdX9gXQuY1Xvh9y74a70bQpP1562sy3mlW3LES5-d4z4N6UFNJ0_U04xKtDi-GNHEGfUGmoY4Y0iTm8tH8sRb6CPx0rcsX3OXXM7i8pX75uSDp_iUZD1oc0K3iY7iE_eJc4nZVBBHctcO_AB0fv7-8LtMw-ZSIQXGD00ceWXZZ2FDo2YE30cAT1jxWW-FhOFqy2pVfUy8MfrhRauKfbcUAOG9sVNxYjo-fYFvotm62mFYuqMkA12ArXHoMVvKdJmh5hVIH6GhUkTmsASoCbF-JeTQjo1oEGOXI6YU8ymqnmkyd2dcxEBYtvGz9IvgjNvNej31xW9eTJ7XCPFHJP9tg-3QoE5hKIM6V-vAl-PRKozPEH8P-oMHHexjHQGBwqjDSSOyvNz9Jr0PSsz7iz8JqujbFx4EaR7F6ortNt8l1dDNWm_VSwKtrWreMKsC75kjD9evnMgRsH1xznDZFGL3aCbBz4QqwQ9IXaC7jxQ_5zbexmYmMoQlZnYw=w1235-h694-no)" /></p>
<p>Setelah kita OK, maka file <code class="language-plaintext highlighter-rouge">build.gradle</code> kita akan diedit oleh Android Studio.</p>
<p><img src="https://lh3.googleusercontent.com/KPzm_G06jNCblKxJiC_MlCXBvL5unG7ssDJ4O3P8ox9ZMklUPD2r8CXMPZsQwWUdQviiiCzZCiqCbtinEMvRdBFoJej8EZkV1jQIpHHmKgPCtCn5mXqPyVtq59WT94d95UxgIZk2loBDBYba9qaWa7qy1fgSrk50vznS10hyljuZBiVDzmACiaV2uDP-gjjxeu6ORh3D7YL5dskLkCEPZ1clgJv0nwtzsv6uUsj78CVAWUZsZ4SbVCBtegPFf5dfTVqQ5-b77dFM8GKEG43pmGRTW6B6uqzPT2VvVtpk3huHATt43lvO1c9zYwqll9km1RSrdg0uZvnVWPSCyHzG_0xjgr0MAAEARdtKiHhrmzZw_fJs7_KhbEvQHQlk0HszUCY6uyWf8hSZLYLmenPFn9EQse7prkaMGcNBqABglJV5WtUpzQF_6Vy3zDizoCtR5PLCezB29zXX6tS-1Em5oBMEH5THsOCtgxf5zc8vGka_JuG4YOnCoLxAqWuI1NxebLPZsamfpEYXUFlPGuVtUZAm1A5QsmnZBPyvb-FcUe7pUYgJUE7LUIHJwEVyzGIF1MzVM5KWmKkcShTz0Q1yEah5wlgpIRiTjFZ2cJdQrTbhgpw8HSZia0ILJzoYERdQgJ6Fa1oxzCC3C-LfDf6ZMX3ZDph6riwjBhakQ6o0sA=w688-h297-no" alt="[Perubahan file gradle](https://lh3.googleusercontent.com/KPzm_G06jNCblKxJiC_MlCXBvL5unG7ssDJ4O3P8ox9ZMklUPD2r8CXMPZsQwWUdQviiiCzZCiqCbtinEMvRdBFoJej8EZkV1jQIpHHmKgPCtCn5mXqPyVtq59WT94d95UxgIZk2loBDBYba9qaWa7qy1fgSrk50vznS10hyljuZBiVDzmACiaV2uDP-gjjxeu6ORh3D7YL5dskLkCEPZ1clgJv0nwtzsv6uUsj78CVAWUZsZ4SbVCBtegPFf5dfTVqQ5-b77dFM8GKEG43pmGRTW6B6uqzPT2VvVtpk3huHATt43lvO1c9zYwqll9km1RSrdg0uZvnVWPSCyHzG_0xjgr0MAAEARdtKiHhrmzZw_fJs7_KhbEvQHQlk0HszUCY6uyWf8hSZLYLmenPFn9EQse7prkaMGcNBqABglJV5WtUpzQF_6Vy3zDizoCtR5PLCezB29zXX6tS-1Em5oBMEH5THsOCtgxf5zc8vGka_JuG4YOnCoLxAqWuI1NxebLPZsamfpEYXUFlPGuVtUZAm1A5QsmnZBPyvb-FcUe7pUYgJUE7LUIHJwEVyzGIF1MzVM5KWmKkcShTz0Q1yEah5wlgpIRiTjFZ2cJdQrTbhgpw8HSZia0ILJzoYERdQgJ6Fa1oxzCC3C-LfDf6ZMX3ZDph6riwjBhakQ6o0sA=w688-h297-no)" /></p>
<p>Langkah kedua selesai, ditandai dengan icon hijau di nomer dua.</p>
<p><img src="https://lh3.googleusercontent.com/aL2gX3wmAucA1kaS2SsjksMEBx03rBqVa9xe8QEBf0pFZ4AVeYjud36UPFNkuyvEfcQxBpry1vTBSDbaMGiAutmPagbsAMW1Zu1YsCEUy7zxg-L36i0Guv_liAaLwq4BnF08WxI7XQsCJEhgCrHRVIVJB48jyetNetYVi9y-lSXICaOG3ww8G8DIhxsmijKo5cMehGbnSQUWcJ-MuzmZCbnQYvKxz_P96yc0kPzEOnf0e3uqLvWpAqhJF0TuE4j_LVV22BOGD8paL4mvXTUCofJIlYUFCxEAOz_OmUWzb4ts6N5ks1YFMq-KZHCcDi_tyCHHRKUoyg8zfe1jWcVGV7ykihYV_NvhvdMluGB3SS5_z2Pgbs1LIgqF7CyU1i0xkW0QFa846aiEBdwbkOvEzfkV_PA65IFsTLMhuP25xI2kDfqIBerl1rjOfmYkLBK0xTM2AS-vtVU2QnXwy3TrzMC0rkTMrrbEoY-GiAxufExIzx82JgWSW0eEZTvD6CvF6TKn-4CMF5TmZkHbM8I5XHmUsi2uzv6sO0GpSFTUT463FKahYa-StILltaU5UaXJBBLrhOTfxET4KdXyh3m5_arvYwbNL0bgeCVuVm1g9HlCSeDwWGGGaKDx3Rx1z88JaiZrlC7dKjCR4GI3bV-cbHODwrdw1oeEixU5OfneHw=w1235-h694-no" alt="[Sukses setup project](https://lh3.googleusercontent.com/aL2gX3wmAucA1kaS2SsjksMEBx03rBqVa9xe8QEBf0pFZ4AVeYjud36UPFNkuyvEfcQxBpry1vTBSDbaMGiAutmPagbsAMW1Zu1YsCEUy7zxg-L36i0Guv_liAaLwq4BnF08WxI7XQsCJEhgCrHRVIVJB48jyetNetYVi9y-lSXICaOG3ww8G8DIhxsmijKo5cMehGbnSQUWcJ-MuzmZCbnQYvKxz_P96yc0kPzEOnf0e3uqLvWpAqhJF0TuE4j_LVV22BOGD8paL4mvXTUCofJIlYUFCxEAOz_OmUWzb4ts6N5ks1YFMq-KZHCcDi_tyCHHRKUoyg8zfe1jWcVGV7ykihYV_NvhvdMluGB3SS5_z2Pgbs1LIgqF7CyU1i0xkW0QFa846aiEBdwbkOvEzfkV_PA65IFsTLMhuP25xI2kDfqIBerl1rjOfmYkLBK0xTM2AS-vtVU2QnXwy3TrzMC0rkTMrrbEoY-GiAxufExIzx82JgWSW0eEZTvD6CvF6TKn-4CMF5TmZkHbM8I5XHmUsi2uzv6sO0GpSFTUT463FKahYa-StILltaU5UaXJBBLrhOTfxET4KdXyh3m5_arvYwbNL0bgeCVuVm1g9HlCSeDwWGGGaKDx3Rx1z88JaiZrlC7dKjCR4GI3bV-cbHODwrdw1oeEixU5OfneHw=w1235-h694-no)" /></p>
<p>Selanjutnya, kita perlu membuat kode program untuk menangani proses registrasi. Cara membuatnya sudah dijelaskan oleh Firebase Assistant. Tinggal kita ikut saja.</p>
<p><img src="https://lh3.googleusercontent.com/qyc9k1vOtuwuUfBm3KfU3KtppUSUhix2NDPRpqsHpwFkLvCeMP1topTaIzZyA5tNZz6UdXm4nObncEww_qVfFgQSffGHnyCCKkPCfCqIAe_MqBdxZ00yawMcFkBbKHFavSc-caoRs-jdpYnAFdlD5sPUhTdEPZbVhMqKx9tr28lJwo69wzB0Qsebdw6Ssulk4FSG4KjFlN4zcejkS5oLOzk71Izaef0zdjhi4StsYBVJa1WBIb-gEhxZJfPXq-7HCtamX8uPgfXA34_BxHbSWCsekzm4HAKcZMMQxJfMKDBDe3PtzFFbMaAJKq-yxv_zOR90oPeIVSZRAdwLqfxx8bYLPn0bGsJA62XupF61YbUFX8jFd_wFmLs_8B2NIpCxVUZLU6Dbt__BNJ_CziH_SzF_DFhX1oCgp_bjCdVPz-1LaswHXPPtlP1SZDrffTZU6KehFRQAGcaOSoGsO6pNWZI8xcSBauUG4tvsR3eVTRf_q0Tuu5BTVnLeggS4KzCS9c5oi779tlK9GGXj0bf741YjjyVcFPDV3Ivkhp9oK8F1j8GpTXdZRKM_gwvVSTypLynAIf_lN6aOEVJfw6meX-uVlDHmMHAqDa0V8WIdgTxRXsauriOFQFH3eq6ELi3tulYf2x2S_N9evq9-O_ORSOcQmYwKsqxZNlhS-iMhiA=w1235-h694-no" alt="[Service Class Registrasi Token](https://lh3.googleusercontent.com/qyc9k1vOtuwuUfBm3KfU3KtppUSUhix2NDPRpqsHpwFkLvCeMP1topTaIzZyA5tNZz6UdXm4nObncEww_qVfFgQSffGHnyCCKkPCfCqIAe_MqBdxZ00yawMcFkBbKHFavSc-caoRs-jdpYnAFdlD5sPUhTdEPZbVhMqKx9tr28lJwo69wzB0Qsebdw6Ssulk4FSG4KjFlN4zcejkS5oLOzk71Izaef0zdjhi4StsYBVJa1WBIb-gEhxZJfPXq-7HCtamX8uPgfXA34_BxHbSWCsekzm4HAKcZMMQxJfMKDBDe3PtzFFbMaAJKq-yxv_zOR90oPeIVSZRAdwLqfxx8bYLPn0bGsJA62XupF61YbUFX8jFd_wFmLs_8B2NIpCxVUZLU6Dbt__BNJ_CziH_SzF_DFhX1oCgp_bjCdVPz-1LaswHXPPtlP1SZDrffTZU6KehFRQAGcaOSoGsO6pNWZI8xcSBauUG4tvsR3eVTRf_q0Tuu5BTVnLeggS4KzCS9c5oi779tlK9GGXj0bf741YjjyVcFPDV3Ivkhp9oK8F1j8GpTXdZRKM_gwvVSTypLynAIf_lN6aOEVJfw6meX-uVlDHmMHAqDa0V8WIdgTxRXsauriOFQFH3eq6ELi3tulYf2x2S_N9evq9-O_ORSOcQmYwKsqxZNlhS-iMhiA=w1235-h694-no)" /></p>
<p>Class di atas akan melakukan registrasi aplikasi kita ke FCM dan mendapatkan token. Token ini harus kita daftarkan ke aplikasi server kita, agar kita bisa mengirim data ke user tertentu. Nantinya kita kirim token berpasangan dengan user id, agar bisa diidentifikasi device mana milik user mana.</p>
<p>Class tadi juga harus didaftarkan ke <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code></p>
<p><img src="https://lh3.googleusercontent.com/oceBbxDZ6pjMbYb7FLUF3WlT59nvyJ_5HsKxw5Q7Wv8ew4gpcCCu3QYL0IRoS96zZ7NZ8TKEoRPezGLCiHV-yMtlWqXDOHcoA9kk3Ceobrom0L83WAzXWSp6Z5yepos9pW4xHCj8CVvnJee5V1V7u2bIwxJ0ImfvDr6SSJCIsWbudrJTqgJcW-gOBUm90n2LGeXv6xynJJo20Yo_oaHyWFWVDWpBE5wfyX8ktAOOT_Mr6VG4CgXBZ5J_88tuTQT4aak1Duqjy0wtctz5wLXofN5aQlSTVXLXcw_Kp9afyEwvbQ4F6rzHYhvMIwssJJBhcSoAIyhrK6IWEKGzYDDLtvXx-jJKH-S14gbd61AZJMlSqRuJy6LfoppnRjwPIBmJ27j9AZqSaJpgAd0YJgfbGCma59nH6_t2vU-z8n1iymzmgSvBr6JZf9fbz_FbK4l_zT7eTzSsRPML8JA5v5ICrv1sPN4tJWxPLrI0ejrqAO4zj1FcvJzjv4lLVWoiaD9qMnQfqbDQmWeUHJvVtWd193_biaivw1Uwzebwi25G_YKcou7zp7QX1_do_M-6W32vGyxQHnT_0gbUY9LkNqLl6BKWu5wcI-n7o8rWyRgvjF-ygQj3wyweAFFbUKLBWHnAE_iBG4QFiiCdKWI_wYMHUKXHEJcu9gGL_fNU-TsTZw=w1235-h694-no" alt="[RegistrasiService di Manifest](https://lh3.googleusercontent.com/oceBbxDZ6pjMbYb7FLUF3WlT59nvyJ_5HsKxw5Q7Wv8ew4gpcCCu3QYL0IRoS96zZ7NZ8TKEoRPezGLCiHV-yMtlWqXDOHcoA9kk3Ceobrom0L83WAzXWSp6Z5yepos9pW4xHCj8CVvnJee5V1V7u2bIwxJ0ImfvDr6SSJCIsWbudrJTqgJcW-gOBUm90n2LGeXv6xynJJo20Yo_oaHyWFWVDWpBE5wfyX8ktAOOT_Mr6VG4CgXBZ5J_88tuTQT4aak1Duqjy0wtctz5wLXofN5aQlSTVXLXcw_Kp9afyEwvbQ4F6rzHYhvMIwssJJBhcSoAIyhrK6IWEKGzYDDLtvXx-jJKH-S14gbd61AZJMlSqRuJy6LfoppnRjwPIBmJ27j9AZqSaJpgAd0YJgfbGCma59nH6_t2vU-z8n1iymzmgSvBr6JZf9fbz_FbK4l_zT7eTzSsRPML8JA5v5ICrv1sPN4tJWxPLrI0ejrqAO4zj1FcvJzjv4lLVWoiaD9qMnQfqbDQmWeUHJvVtWd193_biaivw1Uwzebwi25G_YKcou7zp7QX1_do_M-6W32vGyxQHnT_0gbUY9LkNqLl6BKWu5wcI-n7o8rWyRgvjF-ygQj3wyweAFFbUKLBWHnAE_iBG4QFiiCdKWI_wYMHUKXHEJcu9gGL_fNU-TsTZw=w1235-h694-no)" /></p>
<p>Langkah terakhir di sisi Android adalah membuat service untuk menerima FCM message. Cara membuatnya juga sudah ditunjukkan oleh Firebase Assistant. Tinggal kita ikuti saja.</p>
<p><img src="https://lh3.googleusercontent.com/cGc1QA_KUsIufiebCNPk36wKwb1LoBINkOAhmxd93Q5sTD-6NYGu2kEDi2vMT0_FJ8cvON14RO9niBylEvJqPonjXfn7GE5ww8IARot-CzVPzvlYCHWWiR1yGzZuZYj1EGLWZGccZJv5yNvqwlwXq04Y-pzpUR0RrhKkF4XBqznVmKD7FITrMqMPNzOfv9jvW5MAjQgDSP-l4blunCcBYskfzeK_jxiepciLtYM7y_pfW-cXq7LXa8zo6JQvOOgst0flLrqomPgX-UXsKr9Gr1PKUFRqP15xizFx7M75mYzvI6SSlQqseHao2YKu8vdsCc6fv4335aqvGYxCxzcsDo5mdPupDnivMTcRY-cxzbea7A92k-wIXnANrxwCbMdXEus33d1MosHhdOpkt3S5cqbFbSVPX_SVENVpf10-qY6jUX5fcH8B2lOWOGFfinwZS4i3a5aNK-JFxDf35lBT6qw7MbBZq_LPMWnD6w2EqNDTE-QIDGLRTVKu6UpJT15mhUXMIhG8sIZ4zW9vW0bOWCudcxMpRAqEGed73JnyVAYa0ommGTwFviGtXdE5gCks063KPQ7L0JtWU6DN9NKlTimoWoajAPxELa1CZ7mQoMvaoO4J_7nZuML7xnEca9KC9GreXgFQ2kY5sdOwTyry_5yOycLM1wabvzG-ykZwZA=w1235-h694-no" alt="[Service Terima Message](https://lh3.googleusercontent.com/cGc1QA_KUsIufiebCNPk36wKwb1LoBINkOAhmxd93Q5sTD-6NYGu2kEDi2vMT0_FJ8cvON14RO9niBylEvJqPonjXfn7GE5ww8IARot-CzVPzvlYCHWWiR1yGzZuZYj1EGLWZGccZJv5yNvqwlwXq04Y-pzpUR0RrhKkF4XBqznVmKD7FITrMqMPNzOfv9jvW5MAjQgDSP-l4blunCcBYskfzeK_jxiepciLtYM7y_pfW-cXq7LXa8zo6JQvOOgst0flLrqomPgX-UXsKr9Gr1PKUFRqP15xizFx7M75mYzvI6SSlQqseHao2YKu8vdsCc6fv4335aqvGYxCxzcsDo5mdPupDnivMTcRY-cxzbea7A92k-wIXnANrxwCbMdXEus33d1MosHhdOpkt3S5cqbFbSVPX_SVENVpf10-qY6jUX5fcH8B2lOWOGFfinwZS4i3a5aNK-JFxDf35lBT6qw7MbBZq_LPMWnD6w2EqNDTE-QIDGLRTVKu6UpJT15mhUXMIhG8sIZ4zW9vW0bOWCudcxMpRAqEGed73JnyVAYa0ommGTwFviGtXdE5gCks063KPQ7L0JtWU6DN9NKlTimoWoajAPxELa1CZ7mQoMvaoO4J_7nZuML7xnEca9KC9GreXgFQ2kY5sdOwTyry_5yOycLM1wabvzG-ykZwZA=w1235-h694-no)" /></p>
<p>Sama seperti sebelumnya, service inipun harus kita daftarkan ke <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code>.</p>
<p><img src="https://lh3.googleusercontent.com/91nA60smokYVxgnMh-m_Jm2t1WfWa088HQAOnE-mThAe9PymwR2N6HxQI1sJz2_qqLuy_wTd5I-8xzRhKlnkZ1rHaoSgtLw__Bc_x3iTSBbhBbJGu9PKthmulXGqfznYiOw-w4xzaxxCMJ_wMZM4kSRD2RWYXkyu15ntwBKS8WVqmAJApFKu1NiwfFBLZ-WpheAmZxCP2V_qmM-pxfPTySCThJfYVAK3Bk2-pveorfeBg0YgHRl0tadJmatE2y1AynC2jcnzL1OcU7VrRBSfTMkSSKUyutDWJkEWfbHET89mCsVZNW2E-7rgppcRmgVH2ug0UR_0UNC6EyWGw2kM1FEKzYyjLkM04oC40I_Ofqr9pMmREImurDcdi5lTUvS7k-Mne4VqKGL-y5bMnDmgWiRhYGJfG1OphGIT6z39CZv9mcpFp1k941z5P_C1IwYBJ6CxHhBoQyq3Y4clwwOaJUBVXLWOIekw8t_vCMbfNRZlYOcH_5zqicXN2nHa6sBSqh_gfDBSGUTJhRBvda1N_iuVEIeMbn9kAdf0vaCI9ZlinWnOIDlK5dQblOPc5lgYtGFwQLIOFeUWSsbr-eaHRcA35zQGq5uGGba3ZVeEAJZ3fv3Q7PejvQBPY9fmUHVfF1wkpJYer7DIfjvfDIDER0N5u7MzcHpfNSrBlMf65w=w1235-h694-no" alt="[Manifest Terima Message](https://lh3.googleusercontent.com/91nA60smokYVxgnMh-m_Jm2t1WfWa088HQAOnE-mThAe9PymwR2N6HxQI1sJz2_qqLuy_wTd5I-8xzRhKlnkZ1rHaoSgtLw__Bc_x3iTSBbhBbJGu9PKthmulXGqfznYiOw-w4xzaxxCMJ_wMZM4kSRD2RWYXkyu15ntwBKS8WVqmAJApFKu1NiwfFBLZ-WpheAmZxCP2V_qmM-pxfPTySCThJfYVAK3Bk2-pveorfeBg0YgHRl0tadJmatE2y1AynC2jcnzL1OcU7VrRBSfTMkSSKUyutDWJkEWfbHET89mCsVZNW2E-7rgppcRmgVH2ug0UR_0UNC6EyWGw2kM1FEKzYyjLkM04oC40I_Ofqr9pMmREImurDcdi5lTUvS7k-Mne4VqKGL-y5bMnDmgWiRhYGJfG1OphGIT6z39CZv9mcpFp1k941z5P_C1IwYBJ6CxHhBoQyq3Y4clwwOaJUBVXLWOIekw8t_vCMbfNRZlYOcH_5zqicXN2nHa6sBSqh_gfDBSGUTJhRBvda1N_iuVEIeMbn9kAdf0vaCI9ZlinWnOIDlK5dQblOPc5lgYtGFwQLIOFeUWSsbr-eaHRcA35zQGq5uGGba3ZVeEAJZ3fv3Q7PejvQBPY9fmUHVfF1wkpJYer7DIfjvfDIDER0N5u7MzcHpfNSrBlMf65w=w1235-h694-no)" /></p>
<h2 id="setup-di-aplikasi-server">Setup di Aplikasi Server</h2>
<p>Untuk bisa mengirim message, kita harus memiliki API Key server. Masuk ke Firebase Console.</p>
<p><img src="https://lh3.googleusercontent.com/IJo3Pt4uBKKUCQS5E_uQBEqU7LQ5Z3QTryBAtM1ZU522fhHR_H1XkkaygUedN4UXo6S2xeWITd10Wz_OX0oS8TXn7xTZvIammlM8Az3PdSbIDms6Vg0j_9mNtzTPHNhz_RNbNRmpVHy22aeRNMO6HEfmaf2UKUhS4wrI-lXqhVbX2evF_OgzN2XMBBQQvfarQwtklbtMiubuJuL1Szjm22boKzMagXmR_YwYeSh6h-4Weu7jkElV3VcPyUF-o06BOU99jW8EF5jbm9CHd2tWRay0HY5q1_A2V5a-x-_TGNVmy1pjf7dB23v80a2BHWT5NoUnwDVOOyQAA8sOqzGKsE5Dxuc-Bxb_ITZlvxVRfTsFVbw7rbA_MJ9ZTtq3FlbdJfyjtRwnwjZbfXjVfkaVPjpkcw0PEhKEM5gANmpO0zdmIQ_2WKWBMTyDiW15SQo8NH_JTDg8EYFo6o8RQrj33OloCPis_qjeMfZmzcoPNe-gbACFH-029hzPUBXrmDAjsOJAzeBM_QKaBxfAuMnbdKBIykgLmivVZSI4tmXceQD238nJYxBM01d6FZZOOzOlZ-WxUPtrk56ojxjDW7EI9HVCugUJIaRM5N7rmsEzry8xaVQnLeS8UY7EGQS05yDQm5QGi3Y0WzCZDLC6TWD9n0Mvq_KfSKHmyy_MPJhdYg=w1235-h694-no" alt="[Firebase Console](https://lh3.googleusercontent.com/IJo3Pt4uBKKUCQS5E_uQBEqU7LQ5Z3QTryBAtM1ZU522fhHR_H1XkkaygUedN4UXo6S2xeWITd10Wz_OX0oS8TXn7xTZvIammlM8Az3PdSbIDms6Vg0j_9mNtzTPHNhz_RNbNRmpVHy22aeRNMO6HEfmaf2UKUhS4wrI-lXqhVbX2evF_OgzN2XMBBQQvfarQwtklbtMiubuJuL1Szjm22boKzMagXmR_YwYeSh6h-4Weu7jkElV3VcPyUF-o06BOU99jW8EF5jbm9CHd2tWRay0HY5q1_A2V5a-x-_TGNVmy1pjf7dB23v80a2BHWT5NoUnwDVOOyQAA8sOqzGKsE5Dxuc-Bxb_ITZlvxVRfTsFVbw7rbA_MJ9ZTtq3FlbdJfyjtRwnwjZbfXjVfkaVPjpkcw0PEhKEM5gANmpO0zdmIQ_2WKWBMTyDiW15SQo8NH_JTDg8EYFo6o8RQrj33OloCPis_qjeMfZmzcoPNe-gbACFH-029hzPUBXrmDAjsOJAzeBM_QKaBxfAuMnbdKBIykgLmivVZSI4tmXceQD238nJYxBM01d6FZZOOzOlZ-WxUPtrk56ojxjDW7EI9HVCugUJIaRM5N7rmsEzry8xaVQnLeS8UY7EGQS05yDQm5QGi3Y0WzCZDLC6TWD9n0Mvq_KfSKHmyy_MPJhdYg=w1235-h694-no)" /></p>
<p>Kita akan melihat bahwa project kita sudah terbentuk. Klik project tersebut.</p>
<p><img src="https://lh3.googleusercontent.com/VlRFWy5BGdWJme0hq-R7gVZEj_24_2Pi7t_Cfwq5jpiRxwJiVUVVxtt0840S32hLkiwgUAQziUdovrZS6oB_4um2fxmg7gkzmk5xVCU1_MH2xjBgzQKjUPX3KvH741P4lBQSviT0O1-tV_8QG2dr-5HFhROjsmEZ0izbDJR8sB7WOmqa4f8eQ_kZpNyX0UNvXsW5qPuDZXBbhJOA6AaZ1yhLTRKkxwsrxfyyfL7Jbnecdd_ZJWvbNhoZNzfQEkEclWxzb4bOoNNwPRL3MxZ5ztDmo_aNjKkVPFa-4XAKbocfSvRpV0sJVpqCYK5tdi8XFlMHRPLubIj78-sba7vOSSKM8FOMOjzfTDzcALXRugqHjWd_4V5Z1L7cuSsoyN114sAZPLlNvFK4E6bg907cPN7B2MNyveeKb55CPw1j-XoBnnbqRKxTtor9XYl9CPDivMfg9XLXtZMjQJCIdttoALbXTCTXBWfPvCkRxDau1mGDm9SRuwLGIDy-qzZNXC8XVGGu8Hu_7YT_YQOCRLBSpQz0CsjTr_z_ZvmuuVYJpxseFcijnXyGpCpp6rUaXfunaZtt1Ay5P8XOgaBfiEBuzMTwc9UyteQPGnzbXBrw8L3IKUjumLmpGW_Qen0jSZ1vdQeOTzTqxGR0PXReUlgf4xmFIs2y4VN2mjo5Mcwsrw=w1235-h694-no" alt="[Detail Project](https://lh3.googleusercontent.com/VlRFWy5BGdWJme0hq-R7gVZEj_24_2Pi7t_Cfwq5jpiRxwJiVUVVxtt0840S32hLkiwgUAQziUdovrZS6oB_4um2fxmg7gkzmk5xVCU1_MH2xjBgzQKjUPX3KvH741P4lBQSviT0O1-tV_8QG2dr-5HFhROjsmEZ0izbDJR8sB7WOmqa4f8eQ_kZpNyX0UNvXsW5qPuDZXBbhJOA6AaZ1yhLTRKkxwsrxfyyfL7Jbnecdd_ZJWvbNhoZNzfQEkEclWxzb4bOoNNwPRL3MxZ5ztDmo_aNjKkVPFa-4XAKbocfSvRpV0sJVpqCYK5tdi8XFlMHRPLubIj78-sba7vOSSKM8FOMOjzfTDzcALXRugqHjWd_4V5Z1L7cuSsoyN114sAZPLlNvFK4E6bg907cPN7B2MNyveeKb55CPw1j-XoBnnbqRKxTtor9XYl9CPDivMfg9XLXtZMjQJCIdttoALbXTCTXBWfPvCkRxDau1mGDm9SRuwLGIDy-qzZNXC8XVGGu8Hu_7YT_YQOCRLBSpQz0CsjTr_z_ZvmuuVYJpxseFcijnXyGpCpp6rUaXfunaZtt1Ay5P8XOgaBfiEBuzMTwc9UyteQPGnzbXBrw8L3IKUjumLmpGW_Qen0jSZ1vdQeOTzTqxGR0PXReUlgf4xmFIs2y4VN2mjo5Mcwsrw=w1235-h694-no)" /></p>
<p>Untuk melihat API key, klik tombol roda gigi, kemudian klik Project Settings.</p>
<p><img src="https://lh3.googleusercontent.com/HD7FnccBrGbzyaGjhDvv8rBqdwCZ22vydEB6UhWcJcos_1gPdUhLp1pdVUEFHj7fDMp98Fk-PoIz8Uf5kyhXK3etaNex0Gk6i9iLOdLI5eJ7U1Tbl2i2kLxC7hcCUU1avLbMWmmTCp0KIHenEbmoXR7qVCbxZUJQ4cjjPy2dWfFlWRR-c-scE6LE85-3l77EFRVm378O2Xw3OWbOtax62LHIPT3Bx8hltwtz2enL2BihkholYGgFy7s2ZKV-a6I4RZEXGI3ugGwMTHXPyYG8QvEo9NUty4hn0Zo8M_SxXnaZvOkphlCgao4_933Mp4xB4Hth7YV-o5gP2BjxdrPPECvnZKrEVm97_cqkEOdmeYgZIsx9p7TzjFfUXKesQWmhUa2VtlGhKGUJ_enDywUe-Xkb9H70DorJgairuoe398DsdLL7zGUqzXSBzggNKUtkU1Lt-OnxWoYTHc4g_3ddfSyxb9tCcFggGozq3mGgNUjZKYEB9AZu6jpBEL_o6lb4lfEp2woVefuYTHkZwWtceEY3SGp5IoujlMZrbU08LWkbWGGmGOBCyF6bIr06DOwtanROK8SsqQnWTqKqU_tUOCYcUBJnoeVb3eAFQdHjBDcPOT7O6OjOLI0to1cUNURAR_1OMEKLLgI0JTG_6ZTONaT5erqR6kV_uRuWsutz1g=w1235-h694-no" alt="[Tombol Project Settings](https://lh3.googleusercontent.com/HD7FnccBrGbzyaGjhDvv8rBqdwCZ22vydEB6UhWcJcos_1gPdUhLp1pdVUEFHj7fDMp98Fk-PoIz8Uf5kyhXK3etaNex0Gk6i9iLOdLI5eJ7U1Tbl2i2kLxC7hcCUU1avLbMWmmTCp0KIHenEbmoXR7qVCbxZUJQ4cjjPy2dWfFlWRR-c-scE6LE85-3l77EFRVm378O2Xw3OWbOtax62LHIPT3Bx8hltwtz2enL2BihkholYGgFy7s2ZKV-a6I4RZEXGI3ugGwMTHXPyYG8QvEo9NUty4hn0Zo8M_SxXnaZvOkphlCgao4_933Mp4xB4Hth7YV-o5gP2BjxdrPPECvnZKrEVm97_cqkEOdmeYgZIsx9p7TzjFfUXKesQWmhUa2VtlGhKGUJ_enDywUe-Xkb9H70DorJgairuoe398DsdLL7zGUqzXSBzggNKUtkU1Lt-OnxWoYTHc4g_3ddfSyxb9tCcFggGozq3mGgNUjZKYEB9AZu6jpBEL_o6lb4lfEp2woVefuYTHkZwWtceEY3SGp5IoujlMZrbU08LWkbWGGmGOBCyF6bIr06DOwtanROK8SsqQnWTqKqU_tUOCYcUBJnoeVb3eAFQdHjBDcPOT7O6OjOLI0to1cUNURAR_1OMEKLLgI0JTG_6ZTONaT5erqR6kV_uRuWsutz1g=w1235-h694-no)" /></p>
<p>Kita akan mendapati layar setting project. Klik tab Cloud Messaging</p>
<p><img src="https://lh3.googleusercontent.com/gKkZqHnRFXo2nQ88yUCSwQkAnMIuV9RasRu7vSyg76zPpqFnyGmV5VCeZq2f-dISRRo7_qnXPDEKgCoqTbXOowOYdfcn34lNg930QOo-MV2tD7eEdAfFJ0BNDunAyHibffGPuw=w1235-h694-no" alt="[Layar Project Setting](https://lh3.googleusercontent.com/gKkZqHnRFXo2nQ88yUCSwQkAnMIuV9RasRu7vSyg76zPpqFnyGmV5VCeZq2f-dISRRo7_qnXPDEKgCoqTbXOowOYdfcn34lNg930QOo-MV2tD7eEdAfFJ0BNDunAyHibffGPuw=w1235-h694-no)" /></p>
<p>Nah, kita bisa mengcopy server key yang tampil di layar.</p>
<p><img src="https://lh3.googleusercontent.com/HnMitW-wJurD6KPtrzjMw4S44OPLB0k6aUBJZLy6IzVHXwCmXJ3vCYjB6uEtgROjjRNMRTaJiWO8e9rpzLwuFtaAB8P3MImPCE36k1bcFt2oMUSCMjmrgWQ6k5Y-cvJHg1fVYsH2M3_oSPVQPkzhE0YP3pK4XHDNwW0f_mv4evjoeAsyH5CTddDjndX-g0Jm57g8qnsi9cyvpfoI3vm8zLa5EpcOdja2U7GH4x2R7Z5HXZER1eD-lX4oAnArcyQ5uklgzsxndey-8IXwJpgfI9yxPiGes-0ZZDrkl0V23ny_5fhH2lY6VNRp2YjO_9HqwZZ4PKjSxyKoG45kdz4YgMi9ZSKWsiCKAhPPfgi2I-X_v2mK4nDKYuxHnPa0wrIPy0Ob0-yn8sHQ5juTceHVuA0l106lYQhNlMj7EsHrntJ6pRl1OmKZDVtBNUao0FYMpSwPtaNLPonTLgAJ8rm6uRCZJIeZCbK3l6dRzr8cV7BYOEuhIqsYiA7grysVJ1SUYYu2m_2AvlZ-AFVuSlwsD7UPW5Iu3Osdq_pTb-FMRORp0U0IzAC1WVzzWD9Xl5RCdy5-PIfmtvqyGebASNzElM-5pFfvYonsXylf4qq70pU9rgavVfdtRfDea9ukJLQ5I9GpW7ukJcH4KnxttC8ls7DuUNj5tW3XHzRLW9xLFg=w1235-h694-no" alt="[Server API Key](https://lh3.googleusercontent.com/HnMitW-wJurD6KPtrzjMw4S44OPLB0k6aUBJZLy6IzVHXwCmXJ3vCYjB6uEtgROjjRNMRTaJiWO8e9rpzLwuFtaAB8P3MImPCE36k1bcFt2oMUSCMjmrgWQ6k5Y-cvJHg1fVYsH2M3_oSPVQPkzhE0YP3pK4XHDNwW0f_mv4evjoeAsyH5CTddDjndX-g0Jm57g8qnsi9cyvpfoI3vm8zLa5EpcOdja2U7GH4x2R7Z5HXZER1eD-lX4oAnArcyQ5uklgzsxndey-8IXwJpgfI9yxPiGes-0ZZDrkl0V23ny_5fhH2lY6VNRp2YjO_9HqwZZ4PKjSxyKoG45kdz4YgMi9ZSKWsiCKAhPPfgi2I-X_v2mK4nDKYuxHnPa0wrIPy0Ob0-yn8sHQ5juTceHVuA0l106lYQhNlMj7EsHrntJ6pRl1OmKZDVtBNUao0FYMpSwPtaNLPonTLgAJ8rm6uRCZJIeZCbK3l6dRzr8cV7BYOEuhIqsYiA7grysVJ1SUYYu2m_2AvlZ-AFVuSlwsD7UPW5Iu3Osdq_pTb-FMRORp0U0IzAC1WVzzWD9Xl5RCdy5-PIfmtvqyGebASNzElM-5pFfvYonsXylf4qq70pU9rgavVfdtRfDea9ukJLQ5I9GpW7ukJcH4KnxttC8ls7DuUNj5tW3XHzRLW9xLFg=w1235-h694-no)" /></p>
<h2 id="mengirim-message">Mengirim Message</h2>
<p>Untuk mengirim message, kita bisa gunakan aplikasi yang bisa mengirim HTTP Request. Saya biasanya menggunakan <a href="https://chrome.google.com/webstore/detail/rest-console/cokgbflfommojglbmbpenpphppikmonn">Rest Console</a>.</p>
<p>Pertama, kita isikan target URLnya.</p>
<p><img src="https://lh3.googleusercontent.com/GJha2lhG947GZqhvxOi4WNs5RyEA7_BZcFkvK2fPTH4jaJGcseNt3-qYM-ieKs4c-f8aYzhiVuKf4-I-Q2ldyl5A3POjHtspct0Ct_7QntwOHwM3nPpQKQXv2QXb2FIpr3kz3KIuHcdN27f3neL9dNb4_D_0KQAFykYHXjS6N0Cc5pd-KAngQ-WOQgEOfcSjOUI_mj0rZmJTVzFSQYtjL2aj2cwbkxhUm3fSEvPpfgzHEfgo5fqqhLd7xefclDiXyDkI2VB8VPZCq2ak0HE_sRLf644Lm4NsbOmiXoHKLRsEmpJMfmW7Nst9SHE937gBwBioYDIVASeN41sQm-SjUp0rkmVIQpEVunb4TJT9MiN-_4NuMHfNVWX3tZpCSj5OH0anG6sUetDdjpsJuk3AgPCvHqza2HPvBHVF3IwdjPvT0exW2OLTXvB2DzyQrB5YXWNcnIkay5uQszceJ6fQ8P_VMXMkePkf8GlitV9E64UsJDE-jpzvi_zNmlBKFQ6GCOll92AlzuU0LBxcaD_zvrfxShrGnoU5FqhEWnXtqiHryZnuZ1Xsb_QbePDFKgxYFgIqMvxQOoo8yPCdwv3-BHpIg98puWQmnX7zihqHWMzoBtncCOnA6pT3gp0zQIALHC4h5YEoXSSr12eVkwye8bVpx3eZwDHyi2d-9EWOBA=w996-h398-no" alt="[Target URL](https://lh3.googleusercontent.com/GJha2lhG947GZqhvxOi4WNs5RyEA7_BZcFkvK2fPTH4jaJGcseNt3-qYM-ieKs4c-f8aYzhiVuKf4-I-Q2ldyl5A3POjHtspct0Ct_7QntwOHwM3nPpQKQXv2QXb2FIpr3kz3KIuHcdN27f3neL9dNb4_D_0KQAFykYHXjS6N0Cc5pd-KAngQ-WOQgEOfcSjOUI_mj0rZmJTVzFSQYtjL2aj2cwbkxhUm3fSEvPpfgzHEfgo5fqqhLd7xefclDiXyDkI2VB8VPZCq2ak0HE_sRLf644Lm4NsbOmiXoHKLRsEmpJMfmW7Nst9SHE937gBwBioYDIVASeN41sQm-SjUp0rkmVIQpEVunb4TJT9MiN-_4NuMHfNVWX3tZpCSj5OH0anG6sUetDdjpsJuk3AgPCvHqza2HPvBHVF3IwdjPvT0exW2OLTXvB2DzyQrB5YXWNcnIkay5uQszceJ6fQ8P_VMXMkePkf8GlitV9E64UsJDE-jpzvi_zNmlBKFQ6GCOll92AlzuU0LBxcaD_zvrfxShrGnoU5FqhEWnXtqiHryZnuZ1Xsb_QbePDFKgxYFgIqMvxQOoo8yPCdwv3-BHpIg98puWQmnX7zihqHWMzoBtncCOnA6pT3gp0zQIALHC4h5YEoXSSr12eVkwye8bVpx3eZwDHyi2d-9EWOBA=w996-h398-no)" /></p>
<p>Kemudian, kita isi body request</p>
<p><img src="https://lh3.googleusercontent.com/Ikyx_ricUYJ4jK_F-GVzG8TbjSM8SuT0xuBvCraiWDfb9EFku0zVTs7tZjD0uregRsC5n8DaDQB_uIMh9iQ6LwPuw32cQ8u5KFQbuvzpbrMD3WmrEfDmPSI0XqqMx7jf8n99kT3XGeeKRf3NUrppXwZ65T8NQ16py5CwxDS3dUBugyU7MLVgT0eSh5dhpWk5k-aRZjb9HMZxe6c1I5L6q5cfu4OiNJWXed_YgMD-jt0ZGmpr6-NB2U0DsYg4Hi94DVDuE8xgQSD3TAGOvL5IqIfMQn7IA0kTTJEzXdA4G6CcgSsi5CpSoY3_nrf8sKwobXqWr8LHSWW97TNf_pnSkBmaWyYmb4BdI9j4207fZXsntFCba3RnE_uuCjgQ-aQfTeFfExLimlve6nesgEUJqKm3lqbnOKDS57rTqZx9sxMZBvzQXApYApbgN3KDnFAUT5XW-TD7RwIXBBnYWS1QNNIzeoeaKelNeiW6DLw1-98uAluB1FV-w20WD87O-3RbRdOKi2X4S3Zc9UXLoZ4P7e4lnX7AA5_XqmYbzDFKwnVT2F8myiAdvcPeF5cO6AEnyx-xJJQJtD0YWxEYruKbwpa97l6VXzzQO9xsISOy2-4gg7rULbdMnv43AUFlb-MoNYI_RbwAwN3hyji6M46I1cD6lSvBHWc47MNOazlueA=w992-h582-no" alt="[Body Request](https://lh3.googleusercontent.com/Ikyx_ricUYJ4jK_F-GVzG8TbjSM8SuT0xuBvCraiWDfb9EFku0zVTs7tZjD0uregRsC5n8DaDQB_uIMh9iQ6LwPuw32cQ8u5KFQbuvzpbrMD3WmrEfDmPSI0XqqMx7jf8n99kT3XGeeKRf3NUrppXwZ65T8NQ16py5CwxDS3dUBugyU7MLVgT0eSh5dhpWk5k-aRZjb9HMZxe6c1I5L6q5cfu4OiNJWXed_YgMD-jt0ZGmpr6-NB2U0DsYg4Hi94DVDuE8xgQSD3TAGOvL5IqIfMQn7IA0kTTJEzXdA4G6CcgSsi5CpSoY3_nrf8sKwobXqWr8LHSWW97TNf_pnSkBmaWyYmb4BdI9j4207fZXsntFCba3RnE_uuCjgQ-aQfTeFfExLimlve6nesgEUJqKm3lqbnOKDS57rTqZx9sxMZBvzQXApYApbgN3KDnFAUT5XW-TD7RwIXBBnYWS1QNNIzeoeaKelNeiW6DLw1-98uAluB1FV-w20WD87O-3RbRdOKi2X4S3Zc9UXLoZ4P7e4lnX7AA5_XqmYbzDFKwnVT2F8myiAdvcPeF5cO6AEnyx-xJJQJtD0YWxEYruKbwpa97l6VXzzQO9xsISOy2-4gg7rULbdMnv43AUFlb-MoNYI_RbwAwN3hyji6M46I1cD6lSvBHWc47MNOazlueA=w992-h582-no)" /></p>
<p>Pastikan content type yang digunakan adalah <code class="language-plaintext highlighter-rouge">application/json</code>.</p>
<p>Body messagenya sebagai berikut</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> <span class="dl">"</span><span class="s2">data</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">nama</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">endy</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">no_peserta</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">123</span><span class="dl">"</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">to</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">ethAjHSkZac:APA91bHatrwLuNuGOWvRvaINJc3S2hIHczl2dU73kk2O9Dhf4CEJO99zINWgSENaBPy_6mm6L74TlF3IeqlZtd1HLy6Zf-RPxWuetwdQsPETw--Nq4o_nGOwjffhv42JegG8F8HvkVBu</span><span class="dl">"</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Isi field <code class="language-plaintext highlighter-rouge">to</code> adalah registration token yang didapat aplikasi android kita pada saat dijalankan. Untuk aplikasi saya, nilai token ini saya tampilkan di log</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onTokenRefresh</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Get updated InstanceID token.</span>
<span class="nc">String</span> <span class="n">refreshedToken</span> <span class="o">=</span> <span class="nc">FirebaseInstanceId</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">getToken</span><span class="o">();</span>
<span class="c1">// TODO: Implement this method to send any registration to your app's servers.</span>
<span class="n">sendRegistrationToServer</span><span class="o">(</span><span class="n">refreshedToken</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">sendRegistrationToServer</span><span class="o">(</span><span class="nc">String</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Log</span><span class="o">.</span><span class="na">d</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Refreshed token: "</span> <span class="o">+</span> <span class="n">token</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Sehingga token yang didapat bisa dilihat di Android Monitor.</p>
<p><img src="https://lh3.googleusercontent.com/NQxjGmJHpBiGqJIh6kjpj213zUFJ764Rg-6y71Sm-Xw1Ud6_RVIaoAwyKXGaVROCc4ZVXi62NnaDhfsy23re4ggAUuOb970lKl2njdnns39Sj2-C2RvTvNTzLG4tc3zXI9fRQPysBqSksp3aZ3REQ527UdECivpAjuRZ4VICpweAU7e9UyA5vtgzqGj6phDM_ihP27q12PHtsxYhbfY2pk-aSRCP2nbbp5GHK2ku5D8v2c-I7vvpqi1DpYkiH2XwHz6Y9WhvqpjTiciP1vSpVweTa-pn5vudNryFDQGtSC1qreRMHWQeuqr6G2rQMKZtCiGlpmb2ztbBjqMArrl2Lb-ba4h3rKDDSHitcn5EylB7YbhtyyXzO65fzX5pmFJC8OpxPrtUnsapFAOdXdqHg72zDA5_qtCxMp6t7HnFxfPLXNKN3H_EEcDwLToBaxxRoR2e5lQQKNVXGBDxseisvZse0-TPtGp3WygTXXbkL2EvmuqSWstC-Jo4WcG6xHIzA0mPfvL1z4avA9xkbq8jMsvlTfKr66EYzmOMe1-Ur6nALXgQk3YnUxIbqxVQv4IFf4Zv5Ia-xyR6x8y-0DM4szQeagFA5CkNmYX_tEsot5WyaWw4zwjgSr7GVJGD9aU9ysjh4sb_uExpoRDTeWHnJwqKUK0t_BEV1Bn5p0POXA=w1322-h165-no" alt="[Android Monitor Token](https://lh3.googleusercontent.com/NQxjGmJHpBiGqJIh6kjpj213zUFJ764Rg-6y71Sm-Xw1Ud6_RVIaoAwyKXGaVROCc4ZVXi62NnaDhfsy23re4ggAUuOb970lKl2njdnns39Sj2-C2RvTvNTzLG4tc3zXI9fRQPysBqSksp3aZ3REQ527UdECivpAjuRZ4VICpweAU7e9UyA5vtgzqGj6phDM_ihP27q12PHtsxYhbfY2pk-aSRCP2nbbp5GHK2ku5D8v2c-I7vvpqi1DpYkiH2XwHz6Y9WhvqpjTiciP1vSpVweTa-pn5vudNryFDQGtSC1qreRMHWQeuqr6G2rQMKZtCiGlpmb2ztbBjqMArrl2Lb-ba4h3rKDDSHitcn5EylB7YbhtyyXzO65fzX5pmFJC8OpxPrtUnsapFAOdXdqHg72zDA5_qtCxMp6t7HnFxfPLXNKN3H_EEcDwLToBaxxRoR2e5lQQKNVXGBDxseisvZse0-TPtGp3WygTXXbkL2EvmuqSWstC-Jo4WcG6xHIzA0mPfvL1z4avA9xkbq8jMsvlTfKr66EYzmOMe1-Ur6nALXgQk3YnUxIbqxVQv4IFf4Zv5Ia-xyR6x8y-0DM4szQeagFA5CkNmYX_tEsot5WyaWw4zwjgSr7GVJGD9aU9ysjh4sb_uExpoRDTeWHnJwqKUK0t_BEV1Bn5p0POXA=w1322-h165-no)" /></p>
<p>Kemudian, kita isikan <code class="language-plaintext highlighter-rouge">Authorization</code> dengan key yang didapat dari Firebase Console tadi.</p>
<p><img src="https://lh3.googleusercontent.com/m304L-YXodhPZ9dTI8101C6DV4geHp9y1FkQCnyWHVbva5W3vWDSKENxpgxjjHCv8GsFy5Yqt4uFzR0j7f9YCk5Sl43FN94Iyl_5hdo2iev023VsbMH82asuUigFawH-PYH0fZKqIvvOXFUaU4Md2mxp_72A9kmfolMsyACuQvM7KMIl-A4E4djDDhlVxCZFl2xQYZ5TpKHhdKTlCE_DDzSt5GeQXJg1M6LjNi4dJKDt_Qew_XsP4jwfElokwZe8P_8S2kHjAGD8h8UyplKYDs-uVXaxzCmFS0z4I_EAhazjHeMkh3CIkWXlH5r9YANTs3h-LWnS0PCurrLdjfHpEH7gefKI_IRWdqwqh6_MK_P_UHhfe_5A4Yns320rSD5WqzpSgQKeuS60kJUFM6n5M3EIBCqTj-WqhBnN0QVJh1Ru2vbvc3CpB7qOznU6Q7wtH0GZyMmQdk1mxSt_Gmrto_J1SLwBhauDBMmwsAhnKcaO7XeZvbVD5-Nzf_rWuwrIj1PsviXZYjoYadIk6k6MTf8oenY_13P5R3COvdoKaYlPnv7aikK2ZgOWr4mlc52sEx1VBazwLtT2hNchlQspLWFdDu3TK44PtXm67-WQArv14Y107WonFmrzUcT7-5A1H8Tc5ITTeYldN4BTNlLn2OxRMjAt2Snv8Q1X51e4OQ=w998-h205-no" alt="[Authorization Header](https://lh3.googleusercontent.com/m304L-YXodhPZ9dTI8101C6DV4geHp9y1FkQCnyWHVbva5W3vWDSKENxpgxjjHCv8GsFy5Yqt4uFzR0j7f9YCk5Sl43FN94Iyl_5hdo2iev023VsbMH82asuUigFawH-PYH0fZKqIvvOXFUaU4Md2mxp_72A9kmfolMsyACuQvM7KMIl-A4E4djDDhlVxCZFl2xQYZ5TpKHhdKTlCE_DDzSt5GeQXJg1M6LjNi4dJKDt_Qew_XsP4jwfElokwZe8P_8S2kHjAGD8h8UyplKYDs-uVXaxzCmFS0z4I_EAhazjHeMkh3CIkWXlH5r9YANTs3h-LWnS0PCurrLdjfHpEH7gefKI_IRWdqwqh6_MK_P_UHhfe_5A4Yns320rSD5WqzpSgQKeuS60kJUFM6n5M3EIBCqTj-WqhBnN0QVJh1Ru2vbvc3CpB7qOznU6Q7wtH0GZyMmQdk1mxSt_Gmrto_J1SLwBhauDBMmwsAhnKcaO7XeZvbVD5-Nzf_rWuwrIj1PsviXZYjoYadIk6k6MTf8oenY_13P5R3COvdoKaYlPnv7aikK2ZgOWr4mlc52sEx1VBazwLtT2hNchlQspLWFdDu3TK44PtXm67-WQArv14Y107WonFmrzUcT7-5A1H8Tc5ITTeYldN4BTNlLn2OxRMjAt2Snv8Q1X51e4OQ=w998-h205-no)" /></p>
<p>Jangan lupa untuk memberikan prefix <code class="language-plaintext highlighter-rouge">key:</code> sebelum nilai keynya.</p>
<p>Lakukan HTTP Post, dan FCM akan memberikan response seperti ini</p>
<p><img src="https://lh3.googleusercontent.com/fhRDTbJ2d-8uskvtztz6YF7vEZkstDZHtpBhkmURSxveof89_r_DQz7VBEMH0a0H86NZjElYt1NInsXVR27fDoy8IZ1-pmRl1Ao7r47ILOfzetBbDTo4ulQ5vUb3fwHemjZt_JhEnSexNMARlekdk5CX2aOXzuJRFjADjNCGhr7dDIjWrF9v11fqkLVmnZBPl-b5yt-tNlWFr_5PO12dvhuSFyjp7AbxduKzo_smLQYkc_j1BdiZrlMoh3qVumrKipOlGobHUQlCeiWw41T6nOx8Aiqw6aTAzHz6NInRqg-iKQ0SsstEQc3IYCUug-n9r2QMhJkjKvgCY3mACqgafxgyezV66DymOHJuPpBnZ1ubDtsQgviYtpwsrPKxnkw8L4GGH3sQmxuydtyUtsFFbLvTpOYh3fGZ5n9KVpWbQkz8z0SONQVgkMl0_Y-BURUi1VrNZkLJsVrWiepDCrvA6HhokG7GHXEQu4G_SoSi7IbOo6VMHiBwQlZ94h9oM5XBcjRuTdFCjXS0slADU36Bl_xa0HOBFMa2GMiKGuq_5KdgXtj2o6qbfVwfTBtSeHbgyytEgvCJ03BFLEAxdvtAeh6RUpvIqRF9-8mGXKcTgs1a4NqohHO-goBpexSsAsDHuv5y8e2aLcVHqYqYDCaf_WKNl83dUaEu2pZ_tieyrw=w1001-h511-no" alt="[Response](https://lh3.googleusercontent.com/fhRDTbJ2d-8uskvtztz6YF7vEZkstDZHtpBhkmURSxveof89_r_DQz7VBEMH0a0H86NZjElYt1NInsXVR27fDoy8IZ1-pmRl1Ao7r47ILOfzetBbDTo4ulQ5vUb3fwHemjZt_JhEnSexNMARlekdk5CX2aOXzuJRFjADjNCGhr7dDIjWrF9v11fqkLVmnZBPl-b5yt-tNlWFr_5PO12dvhuSFyjp7AbxduKzo_smLQYkc_j1BdiZrlMoh3qVumrKipOlGobHUQlCeiWw41T6nOx8Aiqw6aTAzHz6NInRqg-iKQ0SsstEQc3IYCUug-n9r2QMhJkjKvgCY3mACqgafxgyezV66DymOHJuPpBnZ1ubDtsQgviYtpwsrPKxnkw8L4GGH3sQmxuydtyUtsFFbLvTpOYh3fGZ5n9KVpWbQkz8z0SONQVgkMl0_Y-BURUi1VrNZkLJsVrWiepDCrvA6HhokG7GHXEQu4G_SoSi7IbOo6VMHiBwQlZ94h9oM5XBcjRuTdFCjXS0slADU36Bl_xa0HOBFMa2GMiKGuq_5KdgXtj2o6qbfVwfTBtSeHbgyytEgvCJ03BFLEAxdvtAeh6RUpvIqRF9-8mGXKcTgs1a4NqohHO-goBpexSsAsDHuv5y8e2aLcVHqYqYDCaf_WKNl83dUaEu2pZ_tieyrw=w1001-h511-no)" /></p>
<p>Selanjutnya, kita bisa lihat messagenya diterima aplikasi Android kita dengan melihat ke Android Monitor.</p>
<p><img src="https://lh3.googleusercontent.com/qt8GeUeceJbkna2_fYMATnYE4pxc90y8Y6l0wn7-TgiL6hrHDynrcIu92a8o6_hvETHF2ZhW3ODUj0neU3anM8ufGuyhavPfPZxIwHf25yKZtvCz9pM2Wiwrv8J12lr1grwSP0hVaPTkF86ciVAoBOeWqTL1zKMzoDsnvzledQ0Y4KWqAAz1SUfmHFaxeTLc9_54E_Hk0OBfABTjEsrC6yG09EKymxaP3sDvy5AipKqhZaunvP7dC1mQRWDQVY0fJ0g6s5NA-qLPSReuaAi7rawgultE3LvpS1c1V7Y5YQDrfeJCnpRXNkbCztz0ey4kSv_Myje8q6vogOy0DKmRRmf6CcMQdvbzHC-Wob9p2RkkUNf0Lug1VFytr83MUqQ-pDuVoThci-4hhL6V04f_KLH6NB8oNgkTsSd48KvxVJQuJGFFXab5L35U3juEOf8id4SzQaxOU_S550g-nBtO5HEurl2OCVLmOsOmmh31ziUC0ox-hYs82TXiou-oJ3R-0kz1-OtBK41K71YsFWZ-3Ik9D4Kh8IlUvytSoSUyTfqY-VJTbBUzzDmWdr7q89yVDMkQPgjuv0pu-gW82M9MkxXfO0QfTfwwunHCtqmzenuhOIe_TkMS7OPdfXDZeuLZS4fdunBd-cY-Ykr3shNIZZaF-1j-SoUXbqQh9ZMyZw=w994-h204-no" alt="[Android Monitor Message](https://lh3.googleusercontent.com/qt8GeUeceJbkna2_fYMATnYE4pxc90y8Y6l0wn7-TgiL6hrHDynrcIu92a8o6_hvETHF2ZhW3ODUj0neU3anM8ufGuyhavPfPZxIwHf25yKZtvCz9pM2Wiwrv8J12lr1grwSP0hVaPTkF86ciVAoBOeWqTL1zKMzoDsnvzledQ0Y4KWqAAz1SUfmHFaxeTLc9_54E_Hk0OBfABTjEsrC6yG09EKymxaP3sDvy5AipKqhZaunvP7dC1mQRWDQVY0fJ0g6s5NA-qLPSReuaAi7rawgultE3LvpS1c1V7Y5YQDrfeJCnpRXNkbCztz0ey4kSv_Myje8q6vogOy0DKmRRmf6CcMQdvbzHC-Wob9p2RkkUNf0Lug1VFytr83MUqQ-pDuVoThci-4hhL6V04f_KLH6NB8oNgkTsSd48KvxVJQuJGFFXab5L35U3juEOf8id4SzQaxOU_S550g-nBtO5HEurl2OCVLmOsOmmh31ziUC0ox-hYs82TXiou-oJ3R-0kz1-OtBK41K71YsFWZ-3Ik9D4Kh8IlUvytSoSUyTfqY-VJTbBUzzDmWdr7q89yVDMkQPgjuv0pu-gW82M9MkxXfO0QfTfwwunHCtqmzenuhOIe_TkMS7OPdfXDZeuLZS4fdunBd-cY-Ykr3shNIZZaF-1j-SoUXbqQh9ZMyZw=w994-h204-no)" /></p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah cara setup Firebase Cloud Messaging agar bisa digunakan di aplikasi Android. Selamat mencoba, semoga bermanfaat.</p>
Membuat Datasource PostgreSQL di Wildfly2016-12-05T07:00:00+07:00https://software.endy.muhardin.com/java/membuat-datasource-postgresql-di-wildfly<p>Pada kesempatan ini, kita akan melakukan konfigurasi datasource (koneksi database) ke PostgreSQL dalam application server Wildfly 10.1.0.0.Final.</p>
<!--more-->
<p>Secara garis besar, ada dua hal yang harus dilakukan:</p>
<ol>
<li>Memasang driver JDBC PostgreSQL sebagai modul</li>
<li>Membuat datasource atau koneksi database yang bisa digunakan oleh aplikasi.</li>
</ol>
<p>Pendaftaran driver dilakukan satu kali saja per versi database. Jadi bila kita punya 5 aplikasi yang menggunakan versi database yang sama, kita cukup mendaftarkan driver satu kali saja.</p>
<p>Tapi pembuatan datasource dilakukan per koneksi database. Bila kita punya 5 database yang digunakan 5 aplikasi berbeda, maka kita harus membuat 5 datasource juga.</p>
<h2 id="pendaftaran-jdbc-driver">Pendaftaran JDBC Driver</h2>
<p>Untuk mendaftarkan JDBC Driver, terlebih dulu kita download JDBC drivernya. Driver PostgreSQL yang saya gunakan bisa <a href="https://jdbc.postgresql.org/download/postgresql-9.4.1212.jar">diunduh di sini</a>. Pastikan Anda mengunduh versi terbaru. Hasil unduhan saya letakkan di folder <code class="language-plaintext highlighter-rouge">/tmp</code>.</p>
<p>Selanjutnya, kita gunakan aplikasi <code class="language-plaintext highlighter-rouge">jboss-cli</code> yang ada di dalam folder instalasi Wildfly.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /folder/instalasi/Wildfly
cd bin
./jboss-cli.sh
</code></pre></div></div>
<p>Setelah dijalankan, kita akan mendapatkan prompt</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /]
</code></pre></div></div>
<p>Ketik <code class="language-plaintext highlighter-rouge">connect</code> kemudian Enter. Setelah itu lakukan pendaftaran modul baru dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module add --name=org.postgres --resources=/tmp/postgresql-9.4.1212.jar --dependencies=javax.api,javax.transaction.api
</code></pre></div></div>
<p>Sesuaikan lokasi <code class="language-plaintext highlighter-rouge">resources</code> dengan lokasi file <code class="language-plaintext highlighter-rouge">postgresql.jar</code> yang diunduh.</p>
<p>Kemudian, jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/subsystem=datasources/jdbc-driver=postgres:add(driver-name="postgres",driver-module-name="org.postgres",driver-class-name=org.postgresql.Driver)
</code></pre></div></div>
<p>Bila semua berjalan lancar, outputnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{"outcome" => "success"}
</code></pre></div></div>
<p>Ketik <code class="language-plaintext highlighter-rouge">\q</code> untuk keluar dari jboss-cli.</p>
<h2 id="pembuatan-datasource">Pembuatan Datasource</h2>
<p>Untuk membuat datasource, edit file <code class="language-plaintext highlighter-rouge">/folder/instalasi/wildfly/standalone/configuration/standalone-full.xml</code>. Tambahkan baris berikut dalam tag <code class="language-plaintext highlighter-rouge">server > profile > subsystem > datasources</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><datasource</span> <span class="na">jndi-name=</span><span class="s">"java:jboss/datasources/KontakDS"</span> <span class="na">pool-name=</span><span class="s">"KontakDS"</span> <span class="na">enabled=</span><span class="s">"true"</span> <span class="na">use-java-context=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><connection-url></span>jdbc:postgresql://localhost:5432/kontakdb<span class="nt"></connection-url></span>
<span class="nt"><driver></span>postgres<span class="nt"></driver></span>
<span class="nt"><pool></span>
<span class="nt"><min-pool-size></span>5<span class="nt"></min-pool-size></span>
<span class="nt"><initial-pool-size></span>5<span class="nt"></initial-pool-size></span>
<span class="nt"><max-pool-size></span>100<span class="nt"></max-pool-size></span>
<span class="nt"><prefill></span>true<span class="nt"></prefill></span>
<span class="nt"></pool></span>
<span class="nt"><security></span>
<span class="nt"><user-name></span>kontakapp<span class="nt"></user-name></span>
<span class="nt"><password></span>1234<span class="nt"></password></span>
<span class="nt"></security></span>
<span class="nt"><validation></span>
<span class="nt"><valid-connection-checker</span> <span class="na">class-name=</span><span class="s">"org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker"</span><span class="nt">/></span>
<span class="nt"><exception-sorter</span> <span class="na">class-name=</span><span class="s">"org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter"</span><span class="nt">/></span>
<span class="nt"></validation></span>
<span class="nt"></datasource></span>
</code></pre></div></div>
<p>Konfigurasi di atas akan membuat datasource dengan nama <code class="language-plaintext highlighter-rouge">KontakDS</code>. Jangan lupa sesuaikan nama database yang ada di <code class="language-plaintext highlighter-rouge">connection-url</code>, <code class="language-plaintext highlighter-rouge">user-name</code> dan <code class="language-plaintext highlighter-rouge">password</code>.</p>
<p>Selanjutnya datasource ini bisa digunakan dalam file <code class="language-plaintext highlighter-rouge">src/main/resources/META-INF/persistence.xml</code> sebagai berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><persistence</span> <span class="na">version=</span><span class="s">"2.1"</span> <span class="na">xmlns=</span><span class="s">"http://xmlns.jcp.org/xml/ns/persistence"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="na">xsi:schemaLocation=</span><span class="s">"http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"</span><span class="nt">></span>
<span class="nt"><persistence-unit</span> <span class="na">name=</span><span class="s">"KontakPU"</span> <span class="na">transaction-type=</span><span class="s">"JTA"</span><span class="nt">></span>
<span class="nt"><jta-data-source></span>java:/jboss/datasources/KontakDS<span class="nt"></jta-data-source></span>
<span class="nt"><exclude-unlisted-classes></span>false<span class="nt"></exclude-unlisted-classes></span>
<span class="nt"><properties></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"javax.persistence.schema-generation.database.action"</span> <span class="na">value=</span><span class="s">"create"</span><span class="nt">/></span>
<span class="nt"></properties></span>
<span class="nt"></persistence-unit></span>
<span class="nt"></persistence></span>
</code></pre></div></div>
<p>Demikianlah cara konfigurasi database PostgreSQL di Wildfly agar bisa digunakan dalam aplikasi Java EE. Semoga bermanfaat.</p>
Menggunakan Pivotal Web Service2016-11-27T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/menggunakan-pivotal-web-service<p>Pada workshop yang diadakan Pivotal kemarin, para peserta dipandu untuk membuat dan menjalankan aplikasi di <a href="http://run.pivotal.io/">Pivotal Web Services</a>. Ini adalah layanan cloud PaaS yang disediakan oleh Pivotal untuk menjalankan aplikasi yang kita buat.</p>
<p>Secara garis besar, cara deploymentnya mirip dengan Heroku dan Openshift, seperti yang pernah saya bahas <a href="http://software.endy.muhardin.com/java/project-bootstrap-03/">di artikel terdahulu</a>. Perbedaan yang paling mendasar, kalau di Heroku dan Openshift kita mendeploy source code (untuk kemudian dilakukan build di cloud), maka di Pivotal Web Services ini (kita sebut PWS aja ya biar gak capek ngetiknya) kita mendeploy JAR atau WAR.</p>
<p>PWS ini berjalan menggunakan software Pivotal Cloud Foundry (PCF). Aplikasi PCF ini open source dan tersedia untuk diunduh. Jadi, kita bisa memasangnya di server kita sendiri. Mirip dengan Openshift, ada aplikasinya, open source, bisa diinstal di server sendiri (on premise).</p>
<p>Pivotal juga menyediakan versi mini dari PCF, disebut dengan <a href="https://pivotal.io/pcf-dev">PCF Dev</a>, yang bisa diinstal dengan mudah di laptop kita sendiri. Soalnya kalau kita mau install PCF versi full, lumayan ribet juga, harus paham Linux, platform IaaS, dan juga setup jaringan untuk kebutuhan routing dan DNS. Walaupun demikian, siapkan koneksi internet yang mumpuni, karena setup PCF Dev ini akan mendownload bergiga-giga data.</p>
<p>Sedangkan bila ingin menginstal PCF versi full, bisa membaca <a href="http://docs.pivotal.io/pivotalcf/1-8/installing/pcf-docs.html">dokumentasi di websitenya</a></p>
<p>Rekan-rekan bisa mengikuti panduan berikut dengan cara mendaftar di PWS. Setelah mendaftar, kita akan diberikan akun gratis untuk periode tertentu. Yuk kita mulai …</p>
<!--more-->
<h2 id="daftar-di-runpivotalio">Daftar di run.pivotal.io</h2>
<p>Langkah pertama tentunya adalah membuat akun alias mendaftar ke PWS. Tentunya untuk pendaftaran tidak perlu saya jelaskan lagi :D
Jadi kita bisa lanjut ke langkah berikutnya.</p>
<h2 id="instalasi-aplikasi-command-line">Instalasi Aplikasi Command Line</h2>
<p>Pengoperasian Cloud Foundry bisa dilakukan lewat web ataupun lewat command line. Tetapi lebih fleksibel dan mudah diikuti adalah metode Command Line. Perintahnya bisa dicopas dari artikel ini. Tidak demikian kalau versi web. Saya bisa menyediakan screenshot, tapi untuk mengikutinya harus klik sana sini.</p>
<p>Instalasi aplikasi command line bisa dibaca sendiri di <a href="https://docs.run.pivotal.io/cf-cli/install-go-cli.html">websitenya Cloud Foundry</a>. Silahkan ikuti panduan yang sesuai dengan sistem operasi masing-masing.</p>
<h2 id="setup-aplikasi-command-line">Setup Aplikasi Command Line</h2>
<p>Karena Cloud Foundry bisa diinstal di local (dengan PWS Dev) ataupun di server kita sendiri (dengan PWS full), maka kita perlu memberitahu aplikasi command line mengenai server yang kita gunakan. Kemudian baru kita masukkan email dan password. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf login -a api.run.pivotal.io
API endpoint: api.run.pivotal.io
Email> endy.muhardin@gmail.com
Password>
Authenticating...
OK
Targeted org endy.muhardin.com
Targeted space development
API endpoint: https://api.run.pivotal.io (API version: 2.64.0)
User: endy.muhardin@gmail.com
Org: endy.muhardin.com
Space: development
</code></pre></div></div>
<p>Pada contoh di atas, saya arahkan ke server PWS, yaitu <code class="language-plaintext highlighter-rouge">api.run.pivotal.io</code>.</p>
<h2 id="aplikasi-yang-akan-dideploy">Aplikasi yang akan dideploy</h2>
<p>Untuk mempermudah pembaca mengikuti, Anda bisa menggunakan contoh aplikasi yang sudah saya buat selama praktek di workshop kemarin. Silahkan clone dari Github</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/endymuhardin/pivotal-workshop.git
</code></pre></div></div>
<p>Setelah diclone, masuk ke foldernya, kemudian build projectnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd pivotal-workshop
mvn clean package
</code></pre></div></div>
<h2 id="konfigurasi-cloud-foundry">Konfigurasi Cloud Foundry</h2>
<p>Supaya Cloud Foundry tahu cara menjalankan aplikasi kita, perlu ada file konfigurasi. File konfigurasinya diberi nama <code class="language-plaintext highlighter-rouge">manifest.yml</code> dan ditaruh di top level folder dalam project kita. Isinya sebagai berikut</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">applications</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">aplikasi-lab</span>
<span class="na">memory</span><span class="pi">:</span> <span class="s">512M</span>
<span class="na">instances</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">host</span><span class="pi">:</span> <span class="s">aplikasi-lab-${random-word}</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">target/aplikasi-lab-0.0.1-SNAPSHOT.jar</span>
</code></pre></div></div>
<h2 id="deployment">Deployment</h2>
<p>Setelah itu, kita bisa melakukan deployment dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf push
</code></pre></div></div>
<p>Berikut adalah outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Using manifest file /Users/endymuhardin/Downloads/aplikasi-lab/manifest.yml
Creating app aplikasi-lab in org endy.muhardin.com / space development as endy.muhardin@gmail.com...
OK
Creating route aplikasi-lab-weightiest-reen.cfapps.io...
OK
Binding aplikasi-lab-weightiest-reen.cfapps.io to aplikasi-lab...
OK
Uploading aplikasi-lab...
Uploading app files from: /var/folders/l4/82c0hrld15g_fgn110435k7m0000gn/T/unzipped-app849606044
Uploading 316.8K, 90 files
Done uploading
OK
Starting app aplikasi-lab in org endy.muhardin.com / space development as endy.muhardin@gmail.com...
Downloading binary_buildpack...
Downloading nodejs_buildpack...
Downloading staticfile_buildpack...
Downloading java_buildpack...
Downloading ruby_buildpack...
Downloaded ruby_buildpack
Downloading dotnet_core_buildpack...
Downloaded nodejs_buildpack
Downloading dotnet_core_buildpack_beta...
Downloaded java_buildpack
Downloading php_buildpack...
Downloaded dotnet_core_buildpack
Downloading go_buildpack...
Downloaded staticfile_buildpack
Downloading python_buildpack...
Downloaded php_buildpack
Downloaded go_buildpack
Downloaded python_buildpack
Downloaded binary_buildpack
Downloaded dotnet_core_buildpack_beta
Creating container
Successfully created container
Downloading app package...
Downloaded app package (12.6M)
Staging...
-----> Java Buildpack Version: v3.10 (offline) | https://github.com/cloudfoundry/java-buildpack.git#193d6b7
-----> Downloading Open Jdk JRE 1.8.0_111 from https://java-buildpack.cloudfoundry.org/openjdk/trusty/x86_64/openjdk-1.8.0_111.tar.gz (found in cache)
Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (1.0s)
-----> Downloading Open JDK Like Memory Calculator 2.0.2_RELEASE from https://java-buildpack.cloudfoundry.org/memory-calculator/trusty/x86_64/memory-calculator-2.0.2_RELEASE.tar.gz (found in cache)
Memory Settings: -Xss349K -XX:MetaspaceSize=104857K -Xms681574K -Xmx681574K -XX:MaxMetaspaceSize=104857K
-----> Downloading Spring Auto Reconfiguration 1.10.0_RELEASE from https://java-buildpack.cloudfoundry.org/auto-reconfiguration/auto-reconfiguration-1.10.0_RELEASE.jar (found in cache)
Exit status 0
Staging complete
Uploading droplet, build artifacts cache...
Uploading build artifacts cache...
Uploading droplet...
Uploaded build artifacts cache (108B)
Uploaded droplet (57.7M)
Uploading complete
Destroying container
Successfully destroyed container
0 of 1 instances running, 1 starting
1 of 1 instances running
App started
OK
App aplikasi-lab was started using this command `CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-2.0.2_RELEASE -memorySizes=metaspace:64m..,stack:228k.. -memoryWeights=heap:65,metaspace:10,native:15,stack:10 -memoryInitials=heap:100%,metaspace:100% -stackThreads=300 -totMemory=$MEMORY_LIMIT) && JAVA_OPTS="-Djava.io.tmpdir=$TMPDIR -XX:OnOutOfMemoryError=$PWD/.java-buildpack/open_jdk_jre/bin/killjava.sh $CALCULATED_MEMORY" && SERVER_PORT=$PORT eval exec $PWD/.java-buildpack/open_jdk_jre/bin/java $JAVA_OPTS -cp $PWD/. org.springframework.boot.loader.JarLauncher`
Showing health and status for app aplikasi-lab in org endy.muhardin.com / space development as endy.muhardin@gmail.com...
OK
requested state: started
instances: 1/1
usage: 512M x 1 instances
urls: aplikasi-lab-weightiest-reen.cfapps.io
last uploaded: Wed Nov 23 09:09:16 UTC 2016
stack: cflinuxfs2
buildpack: java-buildpack=v3.10-offline-https://github.com/cloudfoundry/java-buildpack.git#193d6b7 java-main open-jdk-like-jre=1.8.0_111 open-jdk-like-memory-calculator=2.0.2_RELEASE spring-auto-reconfiguration=1.10.0_RELEASE
state since cpu memory disk details
#0 running 2016-11-23 04:10:11 PM 76.8% 267.6M of 512M 137.1M of 1G
</code></pre></div></div>
<p>Aplikasi kita sudah terdeploy dan bisa diakses di <code class="language-plaintext highlighter-rouge">http://aplikasi-lab-weightiest-reen.cfapps.io</code>. Karena controller yang dibuat dimapping di url <code class="language-plaintext highlighter-rouge">/user/</code> maka kita bisa jalankan controllernya dengan mengakses url <code class="language-plaintext highlighter-rouge">http://aplikasi-lab-weightiest-reen.cfapps.io/user/</code></p>
<h2 id="setup-database">Setup Database</h2>
<p>Aplikasi yang kita buat menggunakan database. Untuk itu, kita harus menyiapkan databasenya dulu. Cloud Foundry memiliki marketplace, yaitu tempat untuk mendapatkan service tambahan seperti database, email, message broker, dan sebagainya. Kita bisa cari service database dengan perintah <code class="language-plaintext highlighter-rouge">cf marketplace</code>. Berikut adalah outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Getting services from marketplace in org endy.muhardin.com / space development as endy.muhardin@gmail.com...
OK
service plans description
3scale free_appdirect, basic_appdirect* API Management Platform
app-autoscaler standard Scales bound applications in response to load (beta)
blazemeter free-tier, basic1kmr*, pro5kmr* Performance Testing Platform
cedexisopenmix opx_global*, openmix-gslb-with-fusion-feeds* Openmix Global Cloud and Data Center Load Balancer
cedexisradar free-community-edition Free Website and Mobile App Performance Reports
cleardb spark, boost*, amp*, shock* Highly available MySQL for your Apps.
cloudamqp lemur, tiger*, bunny*, rabbit*, panda* Managed HA RabbitMQ servers in the cloud
cloudforge free, standard*, pro* Development Tools In The Cloud
elephantsql turtle, panda*, hippo*, elephant* PostgreSQL as a Service
flashreport trial, basic*, silver*, gold*, platinum* Generate PDF from your data
gluon free, indie*, business*, enterprise* Mobile Synchronization and Cloud Integration
ironworker production*, starter*, developer*, lite Job Scheduling and Processing
loadimpact lifree, li100*, li500*, li1000* Performance testing for DevOps
memcachedcloud 100mb*, 250mb*, 500mb*, 1gb*, 2-5gb*, 5gb*, 30mb Enterprise-Class Memcached for Developers
memcachier dev, 100*, 250*, 500*, 1000*, 2000*, 5000*, 7500*, 10000*, 20000*, 50000*, 100000* The easiest, most advanced memcache.
mlab sandbox Fully managed MongoDB-as-a-Service
newrelic standard Manage and monitor your apps
pubnub free Build Realtime Apps that Scale
rediscloud 100mb*, 250mb*, 500mb*, 1gb*, 2-5gb*, 5gb*, 10gb*, 50gb*, 30mb Enterprise-Class Redis for Developers
searchify small*, plus*, pro* Custom search you control
searchly small*, micro*, professional*, advanced*, starter, business*, enterprise* Search Made Simple. Powered-by Elasticsearch
sendgrid free, bronze*, silver* Email Delivery. Simplified.
ssl basic* Upload your SSL certificate for your app(s) on your custom domain
stamplay plus*, premium*, core, starter* API-first development platform
statica starter, spike*, micro*, medium*, large*, enterprise*, premium* Enterprise Static IP Addresses
temporize small*, medium*, large* Simple and flexible job scheduling for your application
* These service plans have an associated cost. Creating a service instance will incur this cost.
TIP: Use 'cf marketplace -s SERVICE' to view descriptions of individual plans of a given service.
</code></pre></div></div>
<p>Kita akan gunakan service ClearDB yang menyediakan layanan MySQL. Gunakan paket <code class="language-plaintext highlighter-rouge">spark</code> yang gratis.</p>
<p>Service ClearDB paket <code class="language-plaintext highlighter-rouge">spark</code> diinisialisasi dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf create-service cleardb spark aplikasi-lab-db
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating service instance aplikasi-lab-db in org endy.muhardin.com / space development as endy.muhardin@gmail.com...
OK
</code></pre></div></div>
<h2 id="bind-service-database-ke-aplikasi">Bind service database ke aplikasi</h2>
<p>Selanjutnya, kita sambungkan aplikasi kita ke database tersebut. Gunakan perintah <code class="language-plaintext highlighter-rouge">cf bs</code> untuk melakukan bind service.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf bs aplikasi-lab aplikasi-lab-db
Binding service aplikasi-lab-db to app aplikasi-lab in org endy.muhardin.com / space development as endy.muhardin@gmail.com...
OK
TIP: Use 'cf restage aplikasi-lab' to ensure your env variable changes take effect
</code></pre></div></div>
<p>Hasilnya bisa dilihat dengan perintah <code class="language-plaintext highlighter-rouge">cf env</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf env aplikasi-lab
Getting env variables for app aplikasi-lab in org endy.muhardin.com / space development as endy.muhardin@gmail.com...
OK
System-Provided:
{
"VCAP_SERVICES": {
"cleardb": [
{
"credentials": {
"hostname": "us-cdbr-iron-east-04.cleardb.net",
"jdbcUrl": "jdbc:mysql://us-cdbr-iron-east-04.cleardb.net/ad_64335bf0ac3ef8d?user=b7882f306fb1a2\u0026password=34035fad",
"name": "ad_64335bf0ac3ef8d",
"password": "34035fad",
"port": "3306",
"uri": "mysql://b7882f306fb1a2:34035fad@us-cdbr-iron-east-04.cleardb.net:3306/ad_64335bf0ac3ef8d?reconnect=true",
"username": "b7882f306fb1a2"
},
"label": "cleardb",
"name": "aplikasi-lab-db",
"plan": "spark",
"provider": null,
"syslog_drain_url": null,
"tags": [
"Data Stores",
"Cloud Databases",
"Web-based",
"Online Backup \u0026 Storage",
"Single Sign-On",
"Cloud Security and Monitoring",
"Certified Applications",
"Developer Tools",
"Data Store",
"Development and Test Tools",
"Buyable",
"mysql",
"relational"
],
"volume_mounts": []
}
]
}
}
{
"VCAP_APPLICATION": {
"application_id": "30ab9b1b-64dd-458d-a027-a82beadbf05a",
"application_name": "aplikasi-lab",
"application_uris": [
"aplikasi-lab-weightiest-reen.cfapps.io"
],
"application_version": "64cc26f2-c28d-4f07-b31a-13e0d42c8df0",
"cf_api": "https://api.run.pivotal.io",
"limits": {
"disk": 1024,
"fds": 16384,
"mem": 512
},
"name": "aplikasi-lab",
"space_id": "02048815-c0b1-47ed-82f4-04a3efdfbfe2",
"space_name": "development",
"uris": [
"aplikasi-lab-weightiest-reen.cfapps.io"
],
"users": null,
"version": "64cc26f2-c28d-4f07-b31a-13e0d42c8df0"
}
}
No user-defined env variables have been set
No running env variables have been set
No staging env variables have been set
</code></pre></div></div>
<h2 id="konfigurasi-koneksi-database-di-aplikasi">Konfigurasi Koneksi Database di Aplikasi</h2>
<p>Biasanya, kita mengedit file <code class="language-plaintext highlighter-rouge">application.properties</code> untuk mengatur JDBC URL, username, dan password database kita. Tapi kali ini tidak perlu. Spring Boot sudah menyediakan konfigurator otomatis untuk berbagai layanan cloud populer (Cloud Foundry, Openshift, Heroku, AWS, dan sebagainya). Kita cukup menambahkan dependensi paket <code class="language-plaintext highlighter-rouge">spring-cloud-connector-starter</code> saja di <code class="language-plaintext highlighter-rouge">pom.xml</code>.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-cloud-connectors<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Setelah ditambahkan dependensinya, lakukan build ulang dan push hasil buildnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn clean package && cf push
</code></pre></div></div>
<h2 id="cek-status-aplikasi-di-cf">Cek status aplikasi di cf</h2>
<p>Untuk mengecek kondisi aplikasi kita di Cloud Foundry, kita jalankan perintah <code class="language-plaintext highlighter-rouge">cf apps</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cf apps
Getting apps in org endy.muhardin.com / space development as endy.muhardin@gmail.com...
OK
name requested state instances memory disk urls
aplikasi-lab started 1/1 512M 1G aplikasi-lab-weightiest-reen.cfapps.io, aplikasi-lab-nonflying-demipique.cfapps.io, aplikasi-lab-noncogent-semiproductiveness.cfapps.io, aplikasi-lab-unsolar-canoe.cfapps.io
</code></pre></div></div>
<h2 id="deployment-ke-heroku">Deployment ke Heroku</h2>
<p>Untuk membuktikan kecanggihan Spring Cloud Connector, kita juga akan mendeploy aplikasi ini ke Heroku dan melihat sebanyak apa perubahan dan konfigurasi yang harus kita lakukan.</p>
<p>Dokumentasi lengkapnya bisa dibaca di <a href="https://spring.io/blog/2014/06/03/introducing-spring-cloud">blog tim Spring tentang Spring Cloud</a>.</p>
<p>Sebelum menjalankan perintah ini, pastikan di komputer kita sudah terinstal aplikasi command line Heroku. Caranya bisa dilihat <a href="http://software.endy.muhardin.com/aplikasi/membuat-blog-jekyll-heroku/">di artikel terdahulu</a>.</p>
<h3 id="persiapan-di-sisi-heroku">Persiapan di sisi Heroku</h3>
<p>Pertama, kita perlu membuat aplikasi dulu di Heroku. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku apps:create
</code></pre></div></div>
<p>Dan ini outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating app... done, ⬢ mysterious-spire-63516
https://mysterious-spire-63516.herokuapp.com/ | https://git.heroku.com/mysterious-spire-63516.git
</code></pre></div></div>
<p>Selanjutnya, kita beri nama aplikasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku config:set SPRING_CLOUD_APP_NAME=aplikasi-lab
</code></pre></div></div>
<p>Hasilnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Setting SPRING_CLOUD_APP_NAME and restarting ⬢ mysterious-spire-63516... done, v3
SPRING_CLOUD_APP_NAME: aplikasi-lab
</code></pre></div></div>
<p>Lalu, kita siapkan databasenya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku addons:create heroku-postgresql:hobby-dev
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating heroku-postgresql:hobby-dev on ⬢ mysterious-spire-63516... free
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-animated-92052 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
</code></pre></div></div>
<p>Persiapan di sisi heroku sudah selesai. Selanjutnya, kita bisa tampilkan log aplikasi kita di Heroku agar mudah memonitor kalau ada error.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku logs --tail
</code></pre></div></div>
<h3 id="konfigurasi-heroku">Konfigurasi Heroku</h3>
<p>Seperti yang sudah-sudah, Heroku membutuhkan konfigurasi dalam file yang diberi nama <code class="language-plaintext highlighter-rouge">Procfile</code>. Berikut isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>web: java -Dserver.port=$PORT -Dspring.profiles.active=cloud -jar target/*.jar
</code></pre></div></div>
<p>Ini mirip dengan petunjuk saya <a href="http://software.endy.muhardin.com/java/project-bootstrap-03/">di artikel terdahulu</a>. Bedanya, di sini kita gunakan nama profil <code class="language-plaintext highlighter-rouge">cloud</code>. Ini adalah nama khusus yang akan dikenali oleh <code class="language-plaintext highlighter-rouge">spring-cloud-connector</code>. Kita juga tidak perlu membuatkan konfigurasi database khusus seperti di artikel sebelumnya.</p>
<h3 id="deployment-ke-heroku-1">Deployment ke Heroku</h3>
<p>Deployment seperti biasa, cukup git push saja.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
git commit -m "konfigurasi Heroku"
git push heroku master
</code></pre></div></div>
<p>Hasilnya bisa dilihat dengan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku apps:info
</code></pre></div></div>
<p>Dan lihat outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=== mysterious-spire-63516
Addons: heroku-postgresql:hobby-dev
Dynos: web: 1
Git URL: https://git.heroku.com/mysterious-spire-63516.git
Owner: endy.muhardin@gmail.com
Region: us
Repo Size: 12 KB
Slug Size: 75 MB
Stack: cedar-14
Web URL: https://mysterious-spire-63516.herokuapp.com/
</code></pre></div></div>
<p>Seperti biasa, saya menginisialisasi database menggunakan <a href="https://flywaydb.org/">Flyway</a>, sehingga pada waktu deployment, aplikasi akan otomatis membuat tabel dan mengisi sampel data. Mari kita cek apakah tabel dan datanya sudah ada.</p>
<p>Gunakan perintah berikut untuk connect ke database Heroku.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku pg:psql
</code></pre></div></div>
<p>Outputnya begini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---> Connecting to DATABASE_URL
psql (9.5.5)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
mysterious-spire-63516::DATABASE=>
</code></pre></div></div>
<p>Setelah mendapat prompt, kita bisa cek tabel yang ada di database dengan perintah <code class="language-plaintext highlighter-rouge">\d</code> dan lihat datanya dengan perintah <code class="language-plaintext highlighter-rouge">select * from s_user</code>.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikian cara mendeploy aplikasi kita di server Cloud Foundry yang disediakan oleh Pivotal. Sebetulnya masih banyak fitur lain yang tersedia, seperti:</p>
<ul>
<li><a href="https://docs.run.pivotal.io/devguide/deploy-apps/blue-green.html">Blue Green Deployment</a></li>
<li><a href="https://docs.run.pivotal.io/devguide/deploy-apps/cf-scale.html">Horizontal dan Vertical Scale</a></li>
<li><a href="https://docs.run.pivotal.io/appsman-services/autoscaler/using-autoscaler.html">Autoscale</a></li>
<li>dan sebagainya</li>
</ul>
<p>Silahkan daftar dan coba-coba sendiri. Semoga bermanfaat</p>
Pivotal Microservice Workshop2016-11-26T07:00:00+07:00https://software.endy.muhardin.com/event/pivotal-microservice-workshop<p>Beberapa hari yang lalu, tepatnya 24-24 November 2016, saya dan tim ArtiVisi diundang oleh <a href="https://pivotal.io/">Pivotal</a> untuk menghadiri workshop yang berjudul “Migrating a Monolith Application to Microservices”.</p>
<p><img src="https://lh3.googleusercontent.com/h6hgA1F5t0bAfIe0ccXbAmRFewB4n61brbFx3XfOloXFdbfB7eXfySPUoc7-RgTLhDF-uXMGe2ph=w731-h694-no" alt="[Migrating Monolith Apps to Microservices](https://lh3.googleusercontent.com/h6hgA1F5t0bAfIe0ccXbAmRFewB4n61brbFx3XfOloXFdbfB7eXfySPUoc7-RgTLhDF-uXMGe2ph=w731-h694-no)" /></p>
<p>Berikut adalah catatan singkat saya tentang event ini.</p>
<!--more-->
<p>Acara ini dibawakan oleh instruktur Liu Dapeng, instruktur dari Pivotal. Pivotal ini adalah perusahaan yang menaungi proyek open source Spring Framework dan keluarganya.</p>
<p><img src="https://lh3.googleusercontent.com/_cqBBLx_tO7dnVXFIl8iTdff8X-wx200Y9GQgFQlA2eLHt2TW-4gwQOjVcasTIi_2rzsovGaXGSQ=s694-no" alt="[Liu Dapeng in Action](https://lh3.googleusercontent.com/_cqBBLx_tO7dnVXFIl8iTdff8X-wx200Y9GQgFQlA2eLHt2TW-4gwQOjVcasTIi_2rzsovGaXGSQ=s694-no)" /></p>
<p>Materi hari pertama membahas :</p>
<ul>
<li>Konsep Microservices</li>
<li>Membuat Microservices dengan Spring Boot</li>
<li>Layanan dan Fitur Cloud Foundry</li>
</ul>
<p>Sedangkan di hari kedua, materinya adalah:</p>
<ul>
<li>Spring Cloud</li>
<li>12 Factor</li>
<li>OAuth 2.0</li>
</ul>
<p>Yang paling menarik dari hari kedua tentunya adalah menu makan siang. Pivotal menjamu para peserta dengan berbagai jenis hewan dan berbagai cara memasak. Bisa ditumis, dipanggang, dan digulai.</p>
<p><img src="https://lh3.googleusercontent.com/RlTQa3rjEqUazQpqdynzbadZYtQwNz8EZILgrHk9BLZ3Pd-Nucvg8byVSYi5e37c8VvDpOp4tpM0=w926-h694-no" alt="[Menu Makan Siang](https://lh3.googleusercontent.com/RlTQa3rjEqUazQpqdynzbadZYtQwNz8EZILgrHk9BLZ3Pd-Nucvg8byVSYi5e37c8VvDpOp4tpM0=w926-h694-no)" /></p>
<p>Kembali ke materi workshop, selengkapnya bisa dilihat pada gambar berikut</p>
<p><img src="https://lh3.googleusercontent.com/9a_GPAuC3afNxwCtP9E7229_kzot3zj7b46pKhHQWx0G5cBz3ac8pQ5LKfxS2sRhRCRz380n362k=w621-h315-no" alt="[Outline Materi](https://lh3.googleusercontent.com/9a_GPAuC3afNxwCtP9E7229_kzot3zj7b46pKhHQWx0G5cBz3ac8pQ5LKfxS2sRhRCRz380n362k=w621-h315-no)" /></p>
<p>Setelah saya posting di Facebook mengenai acara ini, beberapa orang meminta diceritakan materinya. Beberapa materi sudah pernah saya buatkan videonya di Youtube, yaitu:</p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbwPnEqUpUkbTVZEnqfD8n3e">Kumpulan video pelajaran Spring Boot</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbwBK-VWbCHsr9kiDJ5Eo0_o">Kumpulan video pelajaran tentang OAuth 2.0</a></li>
</ul>
<p>Untuk materi penggunaan Cloud Foundry, saya membuat beberapa catatan yang saat ini masih dirapikan dan insya Allah akan diposting pada artikel berikutnya.</p>
<p>Adapun Spring Cloud lumayan panjang kalau diceritakan di blog. Kalau dijadikan training, kira-kira bisa 3-5 hari sendiri.</p>
<p>Mengenai 12 Factor, sebetulnya bisa dibaca <a href="https://12factor.net/">di websitenya</a>. Ini adalah 12 hal yang harus diperhatikan agar aplikasi kita bisa berjalan dengan baik di cloud.</p>
<p>Demikian sekilas cerita tentang workshop Pivotal. Acaranya sangat bermanfaat dan menambah wawasan. Instrukturnya membawakan materi dengan enerjik dan sangat menguasai materinya. Sebetulnya tiap materi ada latihannya, tapi waktunya tidak cukup kalau mau dicoba semua. Dan tidak lupa, makanannya enak-enak.</p>
<p>Terima kasih Pivotal sudah mengadakan acara ini dan mengundang kami. Mudah-mudahan ada acara seperti ini lagi di masa depan.</p>
<p><img src="https://lh3.googleusercontent.com/5dVYUMI5wSlJypUamN0o4Y-fLw3qFuL_ZrrVXVfolon4JxBBZxrkTa8scqioSdZ0jiBuqZORp6Tr=w521-h694-no" alt="[Banner Pivotal](https://lh3.googleusercontent.com/5dVYUMI5wSlJypUamN0o4Y-fLw3qFuL_ZrrVXVfolon4JxBBZxrkTa8scqioSdZ0jiBuqZORp6Tr=w521-h694-no)" /></p>
Membuat Blog dengan Jekyll dan Heroku2016-11-23T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/membuat-blog-jekyll-heroku<p>Pada artikel terdahulu, kita sudah membahas tentang <a href="http://software.endy.muhardin.com/aplikasi/membuat-blog-gratis-di-openshift/">cara membuat blog gratis di OpenShift</a>. Tapi sayang sekali, saat ini OpenShift tidak lagi menerima pendaftaran baru untuk platform versi 2. Sedangkan platformnya yang baru, yaitu versi 3, membatasi akses gratis hanya 30 hari. Setelah 30 hari, ijin pakai kita berakhir masa pakainya dan aplikasi kita akan dihapus.</p>
<p>Untungnya, masih ada alternatif lain, yaitu Heroku. Pada artikel ini, kita akan membahas cara pembuatan website atau blog gratis dengan Heroku.</p>
<!--more-->
<h2 id="instalasi-jekyll">Instalasi Jekyll</h2>
<p>Pada artikel terdahulu, kita sudah membahas cara instalasi Jekyll di Ubuntu. Karena itu, saya tidak akan mengulanginya lagi. Sebagai gantinya saya akan membahas cara instalasi Jekyll di OSX Sierra.</p>
<p>Ruby dan Gem sudah terinstal secara default di OSX, jadi kita bisa langsung menginstal Jekyll dengan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo gem install -n /usr/local/bin/ bundler jekyll
</code></pre></div></div>
<p>Opsi <code class="language-plaintext highlighter-rouge">-n /usr/local/bin/</code> penting buat OSX El Capitan ke atas, karena pada versi ini ada peningkatan sistem keamanan yang disebut dengan istilah <a href="https://support.apple.com/en-us/HT204899">System Integrity Protection</a>. Secara garis besar, SIP ini akan melarang siapapun untuk mengubah file di folder berikut:</p>
<ul>
<li>/System</li>
<li>/usr</li>
<li>/bin</li>
<li>/sbin</li>
<li>Folder aplikasi bawaan OSX</li>
</ul>
<p>Di internet banyak saran untuk mendisable SIP ini supaya instalasi bisa berjalan lancar. Saran dari saya : <strong>Jangan disable SIP !!!</strong></p>
<blockquote>
<p>SIP diadakan supaya laptop kita aman, jadi jangan didisable.</p>
</blockquote>
<p>Cara lain yang lebih aman adalah dengan menginstal gem di folder <code class="language-plaintext highlighter-rouge">/usr/local/bin</code> yang bebas untuk dimodifikasi.</p>
<h2 id="membuat-blog-baru">Membuat Blog Baru</h2>
<p>Kita bisa membuat blog dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll new blog-baru-saya
</code></pre></div></div>
<p>Berikut adalah output dari perintah tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>New jekyll site installed in /Users/endymuhardin/tmp/blog-baru-saya.
Running bundle install in /Users/endymuhardin/tmp/blog-baru-saya...
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
Resolving dependencies...
Using public_suffix 2.0.4
Using colorator 1.1.0
Using ffi 1.9.14
Using forwardable-extended 2.6.0
Using sass 3.4.22
Using rb-fsevent 0.9.8
Using kramdown 1.13.0
Using liquid 3.0.6
Using mercenary 0.3.6
Using rouge 1.11.1
Using safe_yaml 1.0.4
Using bundler 1.13.6
Using addressable 2.5.0
Using rb-inotify 0.9.7
Using pathutil 0.14.0
Using jekyll-sass-converter 1.5.0
Using listen 3.0.8
Using jekyll-watch 1.5.0
Using jekyll 3.3.1
Using jekyll-feed 0.8.0
Using minima 2.1.0
Bundle complete! 3 Gemfile dependencies, 21 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
</code></pre></div></div>
<p>Setelah itu, kita bisa jalankan di local dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd blog-baru-saya
jekyll serve
</code></pre></div></div>
<p>Outputnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WARN: Unresolved specs during Gem::Specification.reset:
listen (< 3.1, ~> 3.0)
WARN: Clearing out unresolved specs.
Please report a bug if this causes problems.
Configuration file: /Users/endymuhardin/tmp/blog-baru-saya/_config.yml
Configuration file: /Users/endymuhardin/tmp/blog-baru-saya/_config.yml
Source: /Users/endymuhardin/tmp/blog-baru-saya
Destination: /Users/endymuhardin/tmp/blog-baru-saya/_site
Incremental build: disabled. Enable with --incremental
Generating...
done in 0.61 seconds.
Auto-regeneration: enabled for '/Users/endymuhardin/tmp/blog-baru-saya'
Configuration file: /Users/endymuhardin/tmp/blog-baru-saya/_config.yml
Server address: http://127.0.0.1:4000/
Server running... press ctrl-c to stop.
</code></pre></div></div>
<p>Kita bisa browse ke <code class="language-plaintext highlighter-rouge">http://localhost:4000</code> dan berikut adalah hasilnya</p>
<p><a href="https://lh3.googleusercontent.com/Q1KvHjx3VJodSRNPUsB3cME095LD1UC3Mm7hNvXyhWNSgif3rKvUCBrVYWWsnWp5gXXcv0bGqz4b=w968-h694-no"><img src="https://lh3.googleusercontent.com/Q1KvHjx3VJodSRNPUsB3cME095LD1UC3Mm7hNvXyhWNSgif3rKvUCBrVYWWsnWp5gXXcv0bGqz4b=w968-h694-no" alt="Jekyll Starter Page" /></a></p>
<h2 id="membuat-repository-git">Membuat Repository Git</h2>
<p>Blog Jekyll kita ini harus disimpan dalam repository Git, karena Heroku menggunakan Git sebagai metode deploymentnya.</p>
<p>Pertama, kita inisialisasi repository git di dalam folder blog kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git init
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Initialized empty Git repository in /Users/endymuhardin/tmp/blog-baru-saya/.git/
</code></pre></div></div>
<p>Edit file <code class="language-plaintext highlighter-rouge">.gitignore</code> di dalamnya, hapus tulisan <code class="language-plaintext highlighter-rouge">_site</code>. Folder <code class="language-plaintext highlighter-rouge">_site</code> ini adalah hasil kompilasi dari file markdown menjadi html. Bila kita deploy Jekyll ke Github, folder ini akan dibuat di server Github, sehingga tidak perlu kita simpan dalam repository. Akan tetapi, karena kita akan deploy di Heroku sebagai aplikasi PHP, maka kita membutuhkan folder ini.</p>
<p>Simpan isi folder ke dalam repository lokal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
git commit -m "commit pertama"
</code></pre></div></div>
<p>Semua file sudah tersimpan dalam repository lokal. Apabila di kemudian hari ada perubahan, misalnya penambahan posting baru, kita harus generate ulang file HTMLnya dengan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll build
</code></pre></div></div>
<p>Dan simpan ke repository</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
git commit -m "posting artikel blablabla"
</code></pre></div></div>
<h2 id="membuat-aplikasi-heroku">Membuat Aplikasi Heroku</h2>
<p>Setelah blog kita berjalan dengan baik di localhost, sekarang kita akan mendeploynya di Heroku. Silahkan mendaftar dulu agar mendapatkan akun. Cara pendaftaran tidak akan kita bahas di sini.</p>
<p>Kita membutuhkan aplikasi command line Heroku agar bisa memilih buildpack dan mengatur custom domain. Jadi, pastikan kita menginstal dulu aplikasinya. Cara instalasinya tidak saya bahas di sini, silahkan baca <a href="https://devcenter.heroku.com/articles/heroku-command-line">instruksi di website Heroku</a>.</p>
<p>Selanjutnya, kita akan membuat aplikasi baru di Heroku. Pembuatan aplikasi ini bisa dilakukan melalui web ataupun command line. Pada artikel ini saya akan bahas saja metode command line. Metode web bisa dicoba-coba sendiri di websitenya.</p>
<p>Berikut perintah untuk membuat aplikasi baru</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku create blog-baru-saya
</code></pre></div></div>
<p>Perintah di atas dijalankan dalam folder blog Jekyll kita tadi, yaitu tempat kita menjalankan <code class="language-plaintext highlighter-rouge">jekyll serve</code>. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating ⬢ blog-baru-saya... done
https://blog-baru-saya.herokuapp.com/ | https://git.heroku.com/blog-baru-saya.git
</code></pre></div></div>
<p>Perintah ini akan menambahkan remote repository <code class="language-plaintext highlighter-rouge">heroku</code> ke repository local kita. Hasilnya bisa dicek dengan perintah <code class="language-plaintext highlighter-rouge">git remote -v</code>. Harusnya akan muncul output seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku https://git.heroku.com/blog-baru-saya.git (fetch)
heroku https://git.heroku.com/blog-baru-saya.git (push)
</code></pre></div></div>
<h2 id="konfigurasi-deployment-heroku">Konfigurasi Deployment Heroku</h2>
<p>Sebetulnya, Jekyll ini adalah aplikasi yang dibuat dengan bahasa pemrograman Ruby. Karena itu, bila kita cari di Google tentang cara deployment Jekyll di Heroku, mayoritas referensi akan mengajarkan cara deployment aplikasi Ruby ke Heroku.</p>
<p>Akan tetapi, kita akan mendeploy blog kita ini sebagai aplikasi PHP, karena lebih mudah. Paket PHP (atau sering disebut dengan istilah <code class="language-plaintext highlighter-rouge">buildpack</code>) di Heroku sudah mencakup webserver Apache. Ini sudah memadai untuk menghosting blog kita yang hanya terdiri dari file HTML.</p>
<p>Untuk itu, kita setup dulu aplikasi Heroku kita dengan buildpack PHP.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku buildpacks:set heroku/php
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Buildpack set. Next release on blog-baru-saya will use heroku/php.
Run git push heroku master to create a new release using this buildpack.
</code></pre></div></div>
<p>Agar sah sebagai aplikasi PHP, kita perlu membuat file <code class="language-plaintext highlighter-rouge">composer.json</code> agar Heroku tidak bingung. Buat file kosong dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo '{}' > composer.json
</code></pre></div></div>
<p>Terakhir, kita buat konfigurasi runtime untuk memberi tahu Heroku folder mana yang akan dipublikasikan oleh webserver. Dalam hal ini, folder yang ingin kita publish adalah <code class="language-plaintext highlighter-rouge">_site</code>.</p>
<p>Heroku akan mencari konfigurasi runtime dalam file yang bernama <code class="language-plaintext highlighter-rouge">Procfile</code>. Berikut perintah untuk membuatnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo 'web: vendor/bin/heroku-php-apache2 _site/' > Procfile
</code></pre></div></div>
<p>Karena kita membuat perubahan (menambah 2 file), maka kita perlu melakukan build dan menyimpannya ke repository.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll build
git add .
git commit -m "konfigurasi Heroku"
</code></pre></div></div>
<h2 id="deployment">Deployment</h2>
<p>Untuk melakukan deployment, cukup lakukan <code class="language-plaintext highlighter-rouge">git push</code> ke Heroku.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push heroku master
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Counting objects: 39, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (25/25), done.
Writing objects: 100% (39/39), 11.77 KiB | 0 bytes/s, done.
Total 39 (delta 7), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> PHP app detected
remote: -----> Bootstrapping...
remote: -----> Installing platform packages...
remote: NOTICE: No runtime required in composer.lock; using PHP ^5.5.17
remote: - apache (2.4.20)
remote: - nginx (1.8.1)
remote: - php (5.6.28)
remote: -----> Installing dependencies...
remote: Composer version 1.2.2 2016-11-03 17:43:15
remote: -----> Preparing runtime environment...
remote: -----> Checking for additional extensions to install...
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 13.5M
remote: -----> Launching...
remote: Released v3
remote: https://blog-baru-saya.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/blog-baru-saya.git
* [new branch] master -> master
</code></pre></div></div>
<p>Selesai :D</p>
<p>Kita bisa browse ke URL yang ditunjukkan pada output perintah di atas, yaitu <code class="language-plaintext highlighter-rouge">https://blog-baru-saya.herokuapp.com/</code>. Hasilnya seperti ini</p>
<p><img src="https://lh3.googleusercontent.com/vBrmaOLZfwcgPYEL-9vI9rfc6Y8Nfw0Gabljbpo8mCJfx_SALnOINO4y0cS-oOYYB3lES10BQ9j2=w968-h694-no" alt="[Blog sudah terdeploy](https://lh3.googleusercontent.com/vBrmaOLZfwcgPYEL-9vI9rfc6Y8Nfw0Gabljbpo8mCJfx_SALnOINO4y0cS-oOYYB3lES10BQ9j2=w968-h694-no)" /></p>
<h2 id="custom-domain">Custom Domain</h2>
<p>Blog yang kita deploy tadi terpublikasi dengan domain <code class="language-plaintext highlighter-rouge">herokuapp.com</code>. Tentunya kita ingin menggunakan nama domain sendiri, misalnya <code class="language-plaintext highlighter-rouge">belajarblog.endy.muhardin.com</code>.</p>
<p>Ada dua tahap konfigurasi yang harus dilakukan, pertama di Heroku, dan kedua di DNS server yang mengelola domain <code class="language-plaintext highlighter-rouge">muhardin.com</code>.</p>
<p>Di sisi Heroku, kita tinggal menjalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku domains:add belajarblog.endy.muhardin.com
</code></pre></div></div>
<p>Outputnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Adding belajarblog.endy.muhardin.com to ⬢ blog-baru-saya... done
▸ Configure your app's DNS provider to point to the DNS Target
▸ blog-baru-saya.herokuapp.com.
▸ For help, see https://devcenter.heroku.com/articles/custom-domains
</code></pre></div></div>
<p>Selanjutnya, kita pindah ke konfigurasi DNS Server. Saya menggunakan layanan gratis dari <a href="https://www.namecheap.com/">Namecheap</a> untuk mengelola domain saya.</p>
<p>Login ke Namecheap, dan tambahkan <code class="language-plaintext highlighter-rouge">CNAME</code> record yang menunjuk ke <code class="language-plaintext highlighter-rouge">blog-baru-saya.herokuapp.com.</code>. Jangan lupa titik di belakang URL Heroku, itu penting untuk menunjukkan bahwa nama domainnya absolut, bukan relatif.</p>
<p>Berikut tampilan konfigurasinya di Namecheap</p>
<p><img src="https://lh3.googleusercontent.com/PeX8oiBxl9uTRT48PF6rETG-w42Fnt31MvJSDML80C7Lr1vw90pY7EtPLYnA3mt4LtyiY0NQ-MkP=w1080-h650-no" alt="[Konfigurasi Namecheap](https://lh3.googleusercontent.com/PeX8oiBxl9uTRT48PF6rETG-w42Fnt31MvJSDML80C7Lr1vw90pY7EtPLYnA3mt4LtyiY0NQ-MkP=w1080-h650-no)" /></p>
<p>Nah, sekarang kita sudah bisa mengakses alamat tersebut di browser</p>
<p><img src="https://lh3.googleusercontent.com/q_am7lJ87XVEI-sn5sh-YVFvFxs4jL3x626w88Ygzt9ce7of5WepZIACeeEG5Jx9Hzu0vVmhVTOc=w968-h694-no" alt="[Custom Domain](https://lh3.googleusercontent.com/q_am7lJ87XVEI-sn5sh-YVFvFxs4jL3x626w88Ygzt9ce7of5WepZIACeeEG5Jx9Hzu0vVmhVTOc=w968-h694-no)" /></p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah cara membuat blog gratis dengan Jekyll, Heroku, dan Namecheap. Total biaya yang kita keluarkan hanyalah Rp. 90.000/tahun untuk membayar domain. Tidak mahal kan?</p>
<p>Selamat mencoba dan semoga bermanfaat ;)</p>
Edit Video dengan KDEnlive2016-09-27T07:00:00+07:00https://software.endy.muhardin.com/aplikasi/video-edit-kdenlive<p>Sekarang lagi jamannya vlogging, yaitu blogging dalam bentuk video. Sebetulnya saya sudah lumayan lama juga rekam sesi kuliah atau training dan publish ke Youtube. Tapi baru sekarang sempat cerita proses pembuatannya.</p>
<p>Biasanya, saya dan tim ArtiVisi mengedit video menggunakan <a href="http://www.openshot.org/">aplikasi Openshot</a>. Tapi sayangnya aplikasi ini sering sekali crash. Jadi kita tidak boleh lupa save. Dan cukup melelahkan juga kalau sekali klik langsung crash.</p>
<p>Dari postingan di grup DSLR Cinematography, ternyata ada Om Wowo sesama pengguna open source dalam mengedit video. Beliau menggunakan <a href="https://kdenlive.org/">aplikasi KDEnlive</a>.</p>
<p>Ada beberapa fitur unggulan dari KDEnlive, diantaranya adalah:</p>
<ul>
<li>Proxy Editing : KDEnlive bisa membuat versi low-res dari video yang kita akan edit, sehingga lebih ringan pada saat kita maju mundur dan memotong video tersebut. Dibandingkan kita mengedit langsung di file yang jaman sekarang besar-besar dan beresolusi hingga 4K. Proxy file yang dibuat KDEnlive resolusinya hanya 640x480 saja sehingga jauh lebih ringan.</li>
<li>Audio Sync : kita biasa merekam audio dan video secara terpisah agar kualitas audionya bisa maksimal. Masalah terjadi ketika ingin menggabungkan file audio dan video ini. Satu clip saja bisa memakan waktu 5-15 menit. Bayangkan bila clipnya ada 10. Bisa habis satu jam sendiri untuk sinkronisasi. Dengan fitur audio sync ini, cukup beberapa klik tombol, audio dan video bisa langsung sinkron.</li>
</ul>
<p>Dua fitur di atas sangat meningkatkan produktifitas dalam mengedit video. Dan yang paling penting, dibandingkan dengan Openshot, KDEnlive ini jauh lebih stabil. Openshot bisa crash puluhan kali selama beberapa jam saya mengedit, sedangkan KDEnlive ini baru crash dua kali saja selama seharian mengedit.</p>
<p><a href="https://lh3.googleusercontent.com/OubxnpJ1YEk-K_uIp50WZvZECgnLpm9XQsbN0-rNhcN2h1fNOp9tswJyq6nvwp9v0-VBcW_LqQXE=w1111-h694-no"><img src="https://lh3.googleusercontent.com/OubxnpJ1YEk-K_uIp50WZvZECgnLpm9XQsbN0-rNhcN2h1fNOp9tswJyq6nvwp9v0-VBcW_LqQXE=w1111-h694-no" alt="KDEnlive Screenshot" /></a></p>
<p>Pada waktu rendering, KDEnlive juga punya satu fitur penting, yaitu Shutdown after render. Jadi kita bisa start render, kemudian kita tinggal tidur. Setelah selesai rendering, KDEnlive akan mematikan komputer kita.</p>
<p>Penjelasan lebih detail tentang cara mengedit video bisa ditonton di vlog saya di Youtube.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/AzVvIaWMONU" frameborder="0" allowfullscreen=""></iframe>
Upload ke Youtube dari Command Line2016-09-23T07:00:00+07:00https://software.endy.muhardin.com/linux/upload-youtube-cli<p>Pembaca rutin blog ini tentu tahu bahwa ArtiVisi punya <a href="https://youtube.com/user/artivisi">channel Youtube</a> yang sering diisi rekaman video tutorial pemrograman. Bila belum tahu, segera subscribe, tonton videonya, klik Like, dan jangan skip iklannya supaya kami dapat income ;)</p>
<p>Setelah selesai syuting dan edit, tentunya kita akan mengupload video tersebut ke Youtube agar bermanfaat buat orang banyak. Saat ini, saya mengupload dengan menggunakan browser. Karena satu video bisa berukuran hingga 1 GB, saya membutuhkan waktu lama untuk menunggu uploadnya selesai. Selama upload belum selesai, saya tidak bisa mematikan laptop dan pergi ke tempat lain. Tentu ini sangat membatasi pergerakan dan tidak efektif dalam penggunaan waktu.</p>
<p>Saya ingin proses upload ini bisa berjalan sendiri tanpa interaksi dengan saya. Solusinya tentu saja dengan aplikasi berbasis CLI (command line interface).</p>
<p>Secara garis besar, sistemnya nanti akan terlihat seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/ibDR9rIFNyJ3kx6nj_CblfW5Gngxqsb-xGoqlx9Wnu07OwqR96O48xnHfpOS_LCo5ToFTXFKknOj=w1361-h350-no"><img src="https://lh3.googleusercontent.com/ibDR9rIFNyJ3kx6nj_CblfW5Gngxqsb-xGoqlx9Wnu07OwqR96O48xnHfpOS_LCo5ToFTXFKknOj=w1361-h350-no" alt="Skema Sistem Upload" /></a></p>
<!--more-->
<p>Aplikasinya sudah dibuatkan oleh Arnau Sanchez dan disediakan cuma-cuma <a href="https://github.com/tokland/youtube-upload">di Github</a>. Untuk menggunakannya, berikut adalah langkah-langkahnya:</p>
<ol>
<li>Unduh dan install aplikasinya</li>
<li>Instal modul <code class="language-plaintext highlighter-rouge">google-api-python-client</code> untuk bahasa pemrograman Python</li>
<li>Setup authentication</li>
<li>Mulai mengupload</li>
</ol>
<h2 id="instalasi-aplikasi">Instalasi Aplikasi</h2>
<p>Karena aplikasinya ada di Github, kita bisa unduh berupa zip ataupun dengan cara <code class="language-plaintext highlighter-rouge">git clone</code>. Saya lebih suka <code class="language-plaintext highlighter-rouge">git clone</code> agar lebih mudah diupdate kalau ada versi yang lebih baru.</p>
<p>Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/tokland/youtube-upload.git
</code></pre></div></div>
<p>Outputnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cloning into 'youtube-upload'...
remote: Counting objects: 610, done.
remote: Total 610 (delta 0), reused 0 (delta 0), pack-reused 610
Receiving objects: 100% (610/610), 163.54 KiB | 67.00 KiB/s, done.
Resolving deltas: 100% (387/387), done.
Checking connectivity... done.
</code></pre></div></div>
<p>Selanjutnya, kita lakukan instalasi dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python setup.py install
</code></pre></div></div>
<p>Perintah tersebut akan mengeluarkan output seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/lib/python2.7/distutils/dist.py:267: UserWarning: Unknown distribution option: 'entry_points'
warnings.warn(msg)
/usr/lib/python2.7/distutils/dist.py:267: UserWarning: Unknown distribution option: 'install_requires'
warnings.warn(msg)
running install
running build
running build_py
creating build
creating build/lib.linux-armv7l-2.7
creating build/lib.linux-armv7l-2.7/youtube_upload
copying youtube_upload/playlists.py -> build/lib.linux-armv7l-2.7/youtube_upload
copying youtube_upload/lib.py -> build/lib.linux-armv7l-2.7/youtube_upload
copying youtube_upload/upload_video.py -> build/lib.linux-armv7l-2.7/youtube_upload
copying youtube_upload/main.py -> build/lib.linux-armv7l-2.7/youtube_upload
copying youtube_upload/__init__.py -> build/lib.linux-armv7l-2.7/youtube_upload
copying youtube_upload/categories.py -> build/lib.linux-armv7l-2.7/youtube_upload
creating build/lib.linux-armv7l-2.7/youtube_upload/auth
copying youtube_upload/auth/webkit_qt.py -> build/lib.linux-armv7l-2.7/youtube_upload/auth
copying youtube_upload/auth/console.py -> build/lib.linux-armv7l-2.7/youtube_upload/auth
copying youtube_upload/auth/browser.py -> build/lib.linux-armv7l-2.7/youtube_upload/auth
copying youtube_upload/auth/__init__.py -> build/lib.linux-armv7l-2.7/youtube_upload/auth
copying youtube_upload/auth/webkit_gtk.py -> build/lib.linux-armv7l-2.7/youtube_upload/auth
running build_scripts
creating build/scripts-2.7
copying and adjusting bin/youtube-upload -> build/scripts-2.7
changing mode of build/scripts-2.7/youtube-upload from 644 to 755
running install_lib
creating /usr/local/lib/python2.7/dist-packages/youtube_upload
copying build/lib.linux-armv7l-2.7/youtube_upload/playlists.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload
copying build/lib.linux-armv7l-2.7/youtube_upload/lib.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload
copying build/lib.linux-armv7l-2.7/youtube_upload/upload_video.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload
copying build/lib.linux-armv7l-2.7/youtube_upload/main.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload
copying build/lib.linux-armv7l-2.7/youtube_upload/__init__.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload
copying build/lib.linux-armv7l-2.7/youtube_upload/categories.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload
creating /usr/local/lib/python2.7/dist-packages/youtube_upload/auth
copying build/lib.linux-armv7l-2.7/youtube_upload/auth/webkit_qt.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload/auth
copying build/lib.linux-armv7l-2.7/youtube_upload/auth/console.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload/auth
copying build/lib.linux-armv7l-2.7/youtube_upload/auth/browser.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload/auth
copying build/lib.linux-armv7l-2.7/youtube_upload/auth/__init__.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload/auth
copying build/lib.linux-armv7l-2.7/youtube_upload/auth/webkit_gtk.py -> /usr/local/lib/python2.7/dist-packages/youtube_upload/auth
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/playlists.py to playlists.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/lib.py to lib.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/upload_video.py to upload_video.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/main.py to main.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/__init__.py to __init__.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/categories.py to categories.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/auth/webkit_qt.py to webkit_qt.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/auth/console.py to console.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/auth/browser.py to browser.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/auth/__init__.py to __init__.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/youtube_upload/auth/webkit_gtk.py to webkit_gtk.pyc
running install_scripts
copying build/scripts-2.7/youtube-upload -> /usr/local/bin
changing mode of /usr/local/bin/youtube-upload to 755
running install_data
creating /usr/local/share/youtube_upload
copying client_secrets.json -> /usr/local/share/youtube_upload
running install_egg_info
Writing /usr/local/lib/python2.7/dist-packages/youtube_upload-0.8.0.egg-info
</code></pre></div></div>
<p>Untuk dapat digunakan, aplikasi ini membutuhkan library <code class="language-plaintext highlighter-rouge">google-api-python-client</code> yang dibuatkan oleh Google. Bila kita langsung pakai tanpa library ini, maka akan muncul pesan error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Traceback (most recent call last):
File "/usr/local/bin/youtube-upload", line 9, in <module>
from youtube_upload import main
File "/usr/local/lib/python2.7/dist-packages/youtube_upload/main.py", line 24, in <module>
import googleapiclient.errors
ImportError: No module named googleapiclient.errors
</code></pre></div></div>
<h2 id="instalasi-google-api-python">Instalasi Google API Python</h2>
<p>Untuk menginstal library tersebut, jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo pip install --upgrade google-api-python-client progressbar2
</code></pre></div></div>
<p>Berikut adalah outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Collecting google-api-python-client
Downloading google_api_python_client-1.5.3-py2.py3-none-any.whl (50kB)
100% |████████████████████████████████| 51kB 124kB/s
Collecting progressbar2
Downloading progressbar2-3.10.1-py2.py3-none-any.whl
Requirement already up-to-date: six<2,>=1.6.1 in /usr/lib/python2.7/dist-packages (from google-api-python-client)
Collecting httplib2<1,>=0.8 (from google-api-python-client)
Downloading httplib2-0.9.2.zip (210kB)
100% |████████████████████████████████| 215kB 275kB/s
Collecting uritemplate<1,>=0.6 (from google-api-python-client)
Downloading uritemplate-0.6.tar.gz
Collecting oauth2client<4.0.0,>=1.5.0 (from google-api-python-client)
Downloading oauth2client-3.0.0.tar.gz (77kB)
100% |████████████████████████████████| 81kB 803kB/s
Collecting python-utils>=2.0.0 (from progressbar2)
Downloading python_utils-2.0.0-py2.py3-none-any.whl
Collecting simplejson>=2.5.0 (from uritemplate<1,>=0.6->google-api-python-client)
Downloading simplejson-3.8.2.tar.gz (76kB)
100% |████████████████████████████████| 81kB 780kB/s
Requirement already up-to-date: pyasn1>=0.1.7 in /usr/local/lib/python2.7/dist-packages (from oauth2client<4.0.0,>=1.5.0->google-api-python-client)
Collecting pyasn1-modules>=0.0.5 (from oauth2client<4.0.0,>=1.5.0->google-api-python-client)
Downloading pyasn1_modules-0.0.8-py2.py3-none-any.whl
Requirement already up-to-date: rsa>=3.1.4 in /usr/local/lib/python2.7/dist-packages (from oauth2client<4.0.0,>=1.5.0->google-api-python-client)
Installing collected packages: httplib2, simplejson, uritemplate, pyasn1-modules, oauth2client, google-api-python-client, python-utils, progressbar2
Running setup.py install for httplib2 ... done
Running setup.py install for simplejson ... done
Running setup.py install for uritemplate ... done
Running setup.py install for oauth2client ... done
Successfully installed google-api-python-client-1.5.3 httplib2-0.9.2 oauth2client-3.0.0 progressbar2-3.10.1 pyasn1-modules-0.0.8 python-utils-2.0.0 simplejson-3.8.2 uritemplate-0.6
</code></pre></div></div>
<p>Selanjutnya, kita perlu mengkonfigurasi authentication dulu.</p>
<h2 id="setup-authentication">Setup Authentication</h2>
<p>Karena ini aplikasi non-interaktif, artinya tidak butuh campur tangan kita, maka tentunya dia tidak boleh menanyakan username dan password kepada kita. Soalnya kita ingin script ini berjalan sendiri, mungkin dijadwalkan di tengah malam agar dapat kuota bonus, pada waktu kita sedang tidur.</p>
<p>Untuk itu, kita perlu setup authentication. Google menggunakan OAuth versi 2. Saya sudah membuat <a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbwBK-VWbCHsr9kiDJ5Eo0_o">video tutorial tentang detail teknis implementasinya</a>. Tapi untuk kebutuhan artikel ini, cukuplah kita tahu cara pakainya saja.</p>
<p>Pertama, kita login dulu ke <a href="https://console.developers.google.com">Google API Console</a>. Setelah login dengan akun Gmail, kita akan melihat halaman Dashboard</p>
<p><a href="https://lh3.googleusercontent.com/IIHhc95XGy89MymJaATLXUe7bdKziFT2aVQUUbZCqkWFtLDR8dlnLlkuY4P0nctk6CJfK7JxCAUW=w1359-h686-no"><img src="https://lh3.googleusercontent.com/IIHhc95XGy89MymJaATLXUe7bdKziFT2aVQUUbZCqkWFtLDR8dlnLlkuY4P0nctk6CJfK7JxCAUW=w1359-h686-no" alt="Halaman Dashboard" /></a></p>
<p>Kita perlu buat project dulu, yaitu aplikasi yang akan menggunakan Google API ini. Klik dropdown di atas, dan pilih <code class="language-plaintext highlighter-rouge">Create Project</code></p>
<p><a href="https://lh3.googleusercontent.com/c1JRGY3AudzEr3Kk3Gzsh8MQC9Y0P06kVlKgwWjFWM96xvuigVRqwd1yglLq9l-DljYPPsjgbM8f=w532-h474-no"><img src="https://lh3.googleusercontent.com/c1JRGY3AudzEr3Kk3Gzsh8MQC9Y0P06kVlKgwWjFWM96xvuigVRqwd1yglLq9l-DljYPPsjgbM8f=w532-h474-no" alt="Create Project" /></a></p>
<p>Beri nama projectnya. Nama project bebas, usahakan yang informatif supaya gampang dikenali kapan-kapan.</p>
<p><a href="https://lh3.googleusercontent.com/5m35Iat3aOI1zGCQCqIWZzeD8JeRer0n2XlWhOiIi_C71zud3vhYNA0tmLxTogWv_u92kaWFVa_m=w1138-h586-no"><img src="https://lh3.googleusercontent.com/5m35Iat3aOI1zGCQCqIWZzeD8JeRer0n2XlWhOiIi_C71zud3vhYNA0tmLxTogWv_u92kaWFVa_m=w1138-h586-no" alt="Nama Project" /></a></p>
<p>Project kita sudah siap digunakan. Google memiliki banyak API yang bisa kita pakai, misalnya untuk mengunggah data ke Google Drive, menggunakan fasilitas messaging di Android, peta Google Maps, dan sebagainya. Tapi kali ini kita hanya butuh <code class="language-plaintext highlighter-rouge">Youtube Data API</code></p>
<p><a href="https://lh3.googleusercontent.com/k6vXyhHWHna7tWi2LD00jFQ246oCtFkMMKE6ONfj9AUDb50aRK38zKHkjyqhXjpoJw9Cu_Lo1JwO=w1188-h559-no"><img src="https://lh3.googleusercontent.com/k6vXyhHWHna7tWi2LD00jFQ246oCtFkMMKE6ONfj9AUDb50aRK38zKHkjyqhXjpoJw9Cu_Lo1JwO=w1188-h559-no" alt="Klik Youtube Data API" /></a></p>
<p>Setelah masuk ke dalamnya, klik Enable untuk mengaktifkannya.</p>
<p><a href="https://lh3.googleusercontent.com/_Feu2CE3h_cdx0bFpmamguiXg5CP4GunVFMAMIjGeyaD0engMcVpOnxRd-uO0R4kdOqWEBNsuhZJ=w1359-h529-no"><img src="https://lh3.googleusercontent.com/_Feu2CE3h_cdx0bFpmamguiXg5CP4GunVFMAMIjGeyaD0engMcVpOnxRd-uO0R4kdOqWEBNsuhZJ=w1359-h529-no" alt="Enable Youtube Data API" /></a></p>
<p>Berikutnya, kita akan membuat credentials. Yaitu file yang berisi informasi akun kita. File ini nantinya akan digunakan oleh aplikasi <code class="language-plaintext highlighter-rouge">youtube-upload</code> untuk login dan mengupload video ke akun kita. Tekan tombol <code class="language-plaintext highlighter-rouge">Go to Credentials</code></p>
<p><a href="https://lh3.googleusercontent.com/lQwAM3XVv2vmVBkzEb71rzV4gan3UhPX-NyRhTF_xqCOjfwFJJqI_CtOXwux3hnTdiskeQHj2t6E=w1351-h439-no"><img src="https://lh3.googleusercontent.com/lQwAM3XVv2vmVBkzEb71rzV4gan3UhPX-NyRhTF_xqCOjfwFJJqI_CtOXwux3hnTdiskeQHj2t6E=w1351-h439-no" alt="Go to Credentials" /></a></p>
<p>Kita akan ditanyakan konfigurasi credentials yang dibutuhkan aplikasi. Pilih saja <code class="language-plaintext highlighter-rouge">Other UI</code> karena aplikasi kita bersifat CLI.</p>
<p>Kita juga centang opsi untuk mengakses user data, karena kita ingin video kita masuk ke akun kita sendiri.</p>
<p><a href="https://lh3.googleusercontent.com/NwTYr-JGG5dgPjTCEi5cHUTRyitpEFYdBUySkV18yDVZ_pfhSOnlGTWQuR8o0gq7A8G2txWvPSRW=w1017-h665-no"><img src="https://lh3.googleusercontent.com/NwTYr-JGG5dgPjTCEi5cHUTRyitpEFYdBUySkV18yDVZ_pfhSOnlGTWQuR8o0gq7A8G2txWvPSRW=w1017-h665-no" alt="Konfigurasi Credentials" /></a></p>
<p>Selanjutnya, kita beri nama credentials tersebut. Kita bisa membuat banyak credentials untuk satu aplikasi yang sama. Ini berguna bila kita ingin mengupload dari beberapa komputer yang berbeda. Pemisahan credential akan berguna pada saat kita ingin mencabut akses dari salah satu komputer.</p>
<p><a href="https://lh3.googleusercontent.com/3scGC3ehW0zmgTXvjtuowm0WbP8gZjJcj5ViF_TS18em84GNM8Pz8HhQGAS1PsZ3Jy5k4DA773Do=w804-h512-no"><img src="https://lh3.googleusercontent.com/3scGC3ehW0zmgTXvjtuowm0WbP8gZjJcj5ViF_TS18em84GNM8Pz8HhQGAS1PsZ3Jy5k4DA773Do=w804-h512-no" alt="Nama Credentials" /></a></p>
<p>Pada waktu pertama kali dijalankan, nantinya kita tetap perlu melakukan persetujuan terhadap aplikasi <code class="language-plaintext highlighter-rouge">youtube-upload</code> tersebut. Untuk itu, kita perlu memberikan label yang jelas supaya informatif.</p>
<p><a href="https://lh3.googleusercontent.com/r1cfhzmJceFGXNv5oltV6t_a51zam59_8YDHtwH0RUCnrW54QmledRNotA02eNmqylG5e7TmhtUQ=w945-h682-no"><img src="https://lh3.googleusercontent.com/r1cfhzmJceFGXNv5oltV6t_a51zam59_8YDHtwH0RUCnrW54QmledRNotA02eNmqylG5e7TmhtUQ=w945-h682-no" alt="Auth Label" /></a></p>
<p>File credentials siap, kita bisa unduh ke laptop kita.</p>
<p><a href="https://lh3.googleusercontent.com/N_x81BJElvGOChroTq8QMVdw5D9Y0hMKX6wkwIRFw3ryQTrokuD0u-A5ZRtcPsBG_HgCBhwIP4tP=w1006-h570-no"><img src="https://lh3.googleusercontent.com/N_x81BJElvGOChroTq8QMVdw5D9Y0hMKX6wkwIRFw3ryQTrokuD0u-A5ZRtcPsBG_HgCBhwIP4tP=w1006-h570-no" alt="Download credentials" /></a></p>
<p>Proses pembuatan credentials selesai. Kita bisa lihat daftar credentials yang diijinkan menggunakan aplikasi kita tadi.</p>
<p><a href="https://lh3.googleusercontent.com/UwOisSwqz7_Z5JeCp5JPTCCpNErovqa2Iz9CFayyjInFRopoMuOVo_gZAFdE9sA-Nq62nSb-eyO_=w1361-h449-no"><img src="https://lh3.googleusercontent.com/UwOisSwqz7_Z5JeCp5JPTCCpNErovqa2Iz9CFayyjInFRopoMuOVo_gZAFdE9sA-Nq62nSb-eyO_=w1361-h449-no" alt="Daftar Credentials" /></a></p>
<p>Sekarang, kita sudah bisa mencoba upload.</p>
<h2 id="upload-video">Upload Video</h2>
<p>Pertama, kita siapkan dulu seluruh file yang diperlukan ke dalam satu folder. Saya gunakan folder Videos</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd Videos
ls -lh
</code></pre></div></div>
<p>Berikut adalah isi foldernya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>total 352M
-rw-r--r-- 1 endy endy 433 Sep 22 21:52 client_id.json
-rwxrwxr-x 1 endy endy 45M Sep 22 21:25 GOPR0357.MP4
-rwxrwxr-x 1 endy endy 177M Sep 22 21:51 GOPR0363.MP4
-rwxrwxr-x 1 endy endy 131M Sep 22 22:12 GOPR0370.MP4
</code></pre></div></div>
<p>File <code class="language-plaintext highlighter-rouge">client_id.json</code> adalah file credentials yang kita unduh pada langkah sebelumnya. Sedangkan file <code class="language-plaintext highlighter-rouge">*.MP4</code> adalah video yang ingin diupload.</p>
<p>Kita coba upload menggunakan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-upload --client-secret=client_id.json --title="Gowes Puncak" --description="Gowes Fun bersama Kubic" GOPR0363.MP4
</code></pre></div></div>
<p>Pertama kali dijalankan, Google akan memverifikasi apakah aplikasi ini diijinkan untuk mengakses akun kita. Oleh karena itu, kita akan mendapati output berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Using client secrets: client_id.json
Using credentials file: /home/endy/.youtube-upload-credentials.json
Check this link in your browser: https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.upload+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&client_id=962699408619-lpbe09lvfkb7ju9gk74kcoksftel0flq.apps.googleusercontent.com&access_type=offline
Enter verification code:
</code></pre></div></div>
<p>Kita disuruh copy-paste link yang diberikan di browser agar Google bisa meminta persetujuan. Buka link tersebut di browser. Tentunya kita harus login dulu dengan akun Google.</p>
<p>Saya memiliki beberapa channel, karenanya Google menanyakan channel mana yang akan menerima upload video ini.</p>
<p><a href="https://lh3.googleusercontent.com/dELZDPhOwU8XG_OE3QpRiV8S4hUtH5IMBtusrECUpGmEaJFA1Nj-kJzlwZDOrBSmZx1-hvarVEfu=w1361-h460-no"><img src="https://lh3.googleusercontent.com/dELZDPhOwU8XG_OE3QpRiV8S4hUtH5IMBtusrECUpGmEaJFA1Nj-kJzlwZDOrBSmZx1-hvarVEfu=w1361-h460-no" alt="Pilih channel" /></a></p>
<p>Selanjutnya, Google akan minta persetujuan agar aplikasi <code class="language-plaintext highlighter-rouge">youtube-upload</code> boleh mengunggah video ke channel yang kita pilih barusan.</p>
<p><a href="https://lh3.googleusercontent.com/SEDmBGSL7_Z4Sm3lFBqXfaZCafl9w0d_VWm-h5nIyQoodcx96gEqULkjo2DCFNsnxSfw7MV2d3gm=w1361-h483-no"><img src="https://lh3.googleusercontent.com/SEDmBGSL7_Z4Sm3lFBqXfaZCafl9w0d_VWm-h5nIyQoodcx96gEqULkjo2DCFNsnxSfw7MV2d3gm=w1361-h483-no" alt="Auth Request" /></a></p>
<p>Begitu kita klik Approve, kita akan diberikan verification code.</p>
<p><a href="https://lh3.googleusercontent.com/et9ACNIMIsrZmd7lStbw6NgEA7d3Qh7ike2IKxHyN_jqObssILwewTh84BW1-pNgm9zefT-9803_=w763-h123-no"><img src="https://lh3.googleusercontent.com/et9ACNIMIsrZmd7lStbw6NgEA7d3Qh7ike2IKxHyN_jqObssILwewTh84BW1-pNgm9zefT-9803_=w763-h123-no" alt="Auth Code" /></a></p>
<p>Copy kode tersebut, dan paste di command line kita tadi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-upload --client-secret=client_id.json --title="Gowes Puncak 2" --description="Gowes Fun bersama Kubic" GOPR0363.MP4
Using client secrets: client_id.json
Using credentials file: /home/endy/.youtube-upload-credentials.json
Check this link in your browser: https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.upload+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&client_id=962699408619-lpbe09lvfkb7ju9gk74kcoksftel0flq.apps.googleusercontent.com&access_type=offline
Enter verification code: yaddayaddayaddablablablahorehorehore
</code></pre></div></div>
<p>Setelah dimasukkan, aplikasi akan menyimpannya dalam file bernama <code class="language-plaintext highlighter-rouge">/home/endy/.youtube-upload-credentials.json</code>. Bila kita ingin mengunggah ke beberapa channel berbeda, kita bisa rename file ini dan generate ulang untuk masing-masing channel. Nantinya kita bisa memilih channel tujuan upload dengan menyebutkan nama file credential di opsi command line. Contoh perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-upload \
--client-secret=client_id.json \
--credentials-file=channel_artivisi.json
--title="Gowes Puncak 2" \
--description="Gowes Fun bersama Kubic" \
GOPR0363.MP4
</code></pre></div></div>
<p>Berikut adalah output setelah kita memasukkan verification code</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Start upload: GOPR0363.MP4
100% |##################################################################| 178.0 KiB/s
Video URL: https://www.youtube.com/watch?v=VtEoIAHpyIA
VtEoIAHpyIA
</code></pre></div></div>
<p>Setelah proses upload selesai, kita bisa melihat hasilnya di akun Youtube kita</p>
<p><a href="https://lh3.googleusercontent.com/OqSoC9u4mRRzdJNNUiwYlmAAE7pU-Yb9I7Y861KIrfuuAsCtqV2-sDL3DxCF57BxzskX1lmxriDy=w963-h652-no"><img src="https://lh3.googleusercontent.com/OqSoC9u4mRRzdJNNUiwYlmAAE7pU-Yb9I7Y861KIrfuuAsCtqV2-sDL3DxCF57BxzskX1lmxriDy=w963-h652-no" alt="Video di Youtube" /></a></p>
<p>Aplikasi ini memiliki beberapa opsi tambahan yang bermanfaat, misalnya:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">--category=Education</code> : untuk memasukkan kategori video</li>
<li><code class="language-plaintext highlighter-rouge">--playlist="Belajar Pemrograman Android"</code> : untuk memasukkan video ke playlist</li>
<li><code class="language-plaintext highlighter-rouge">--privacy=private</code> : bila ingin videonya tidak bisa dilihat orang lain</li>
<li>dan sebagainya, bisa dilihat <a href="https://github.com/tokland/youtube-upload">di dokumentasinya</a>.</li>
</ul>
<h2 id="upload-banyak-video">Upload Banyak Video</h2>
<blockquote>
<p>Bagaimana kalau video kita banyak? Misalnya ada 100 file.</p>
</blockquote>
<p>Masing-masing file video tentu punya metadata (judul, kategori, playlist, dan sebagainya) yang berbeda. Untuk mengatasi hal ini, kita buat dulu daftar file berikut metadatanya dalam file text. Misalnya, bila saya ingin menggunakan metadata : <code class="language-plaintext highlighter-rouge">judul</code>, <code class="language-plaintext highlighter-rouge">deskripsi</code>, <code class="language-plaintext highlighter-rouge">playlist</code>, saya akan membuat file <code class="language-plaintext highlighter-rouge">daftar-upload.txt</code> yang isinya seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2016-01-02T00:00:00.0Z,GOPRO001.mp4,Rute Rindu Alam 1,Jalur Rindu Alam dari Mang Ade sampai Gadog\nLokasi: Puncak - Bogor,Gowes Puncak
2016-01-03T00:00:00.0Z,GOPRO002.mp4,Jalur Pipa Gas Depok,Menyusuri single track JPG Depok\nLokasi: Depok - Jawa Barat,Gowes Depok,
2016-01-04T00:00:00.0Z,GOPRO003.mp4,Gunung Pancar,Sentul City ke Hutan Pinus\nLokasi: Sentul - Bogor,Gowes Sentul City
2016-01-05T00:00:00.0Z,GOPRO004.mp4,KM 0 Bojong Koneng,Sentul City ke KM 0 via Bukit Pelangi\nLokasi: Sentul - Bogor,Gowes Sentul City
</code></pre></div></div>
<p>Kemudian, kita letakkan file tersebut di folder yang sama dengan file <code class="language-plaintext highlighter-rouge">GOPRO001.mp4</code>,<code class="language-plaintext highlighter-rouge">GOPRO002.mp4</code>,<code class="language-plaintext highlighter-rouge">GOPRO003.mp4</code>, dan <code class="language-plaintext highlighter-rouge">GOPRO004.mp4</code>.</p>
<p>Selanjutnya, buat script untuk mengupload, misalnya kita beri nama <code class="language-plaintext highlighter-rouge">upload.sh</code>. Isinya sebagai berikut</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nv">OLDIFS</span><span class="o">=</span><span class="nv">$IFS</span>
<span class="nv">IFS</span><span class="o">=</span>,
<span class="k">while </span><span class="nb">read </span>publishdate namafile judul deskripsi playlist
<span class="k">do
</span>youtube-upload <span class="nt">--client-secret</span><span class="o">=</span>client_id.json <span class="nt">--credentials-file</span><span class="o">=</span>channel_artivisi.json <span class="nt">--title</span><span class="o">=</span><span class="s1">'$judul'</span> <span class="nt">--description</span><span class="o">=</span><span class="s1">'$deskripsi'</span> <span class="nt">--playlist</span><span class="o">=</span><span class="s1">'$playlist'</span> <span class="nt">--publish-at</span><span class="o">=</span><span class="nv">$publishdate</span> <span class="nv">$namafile</span>
<span class="k">done</span> < daftar-upload.txt
<span class="nv">IFS</span><span class="o">=</span><span class="nv">$OLDIFS</span>
</code></pre></div></div>
<p>Pasang mode executablenya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x upload.sh
</code></pre></div></div>
<p>Kemudian tinggal kita jalankan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./upload.sh
</code></pre></div></div>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah artikel cara mengupload video di Youtube secara non-interaktif. Waktu kita terbatas, jangan dihabiskan percuma untuk menonton progress bar.</p>
<p>Semoga bermanfaat.</p>
Restore Gitlab dari Amazon Glacier2016-09-21T07:00:00+07:00https://software.endy.muhardin.com/linux/restore-gitlab<p>Hari ini saya membaca <a href="http://istofani.com/wp/digital-ocean-sebuah-mimpi-buruk/">artikel menyedihkan</a> dari salah satu pelaku startup di negara kita ini. Aplikasinya yang dihosting di Digital Ocean crash dan datanya tidak bisa direcover. Entah apakah dia punya backup di tempat lain atau tidak.</p>
<p>Dengan artikel ini, pertama-tama saya mengucapkan turut berduka cita atas kehilangannya. Semoga tetap tabah, bisa mengatasi dengan lancar, dan diberikan ganti yang lebih baik.</p>
<p>Selanjutnya, ini menjadi pelajaran bagi kita semua tentang pentingnya backup. Well, sebenarnya backup tidak penting, <strong>yang penting adalah restore !!!</strong>. Percuma juga kan punya backup kalau tidak bisa direstore :P</p>
<p>Hampir setahun yang lalu, saya sudah menjelaskan <a href="http://software.endy.muhardin.com/linux/backup-duplicity/">tata cara backup yang aman menggunakan enkripsi ke layanan cloud</a>. Tapi tidak mengapa kita ulang sedikit tentang prinsip dan konsep backup. Setelah itu kita akan membahas tentang tata cara restore.</p>
<!--more-->
<h2 id="prinsip-backup">Prinsip Backup</h2>
<p>Prinsip backup secara sederhana dijelaskan Scott Hanselman dengan jargon 3-2-1, yang artinya:</p>
<ul>
<li>Data penting harus memiliki 3 copy</li>
<li>Disimpan dalam 2 format berbeda. Misalnya harddisk + cloud, flashdisk + CD, dan sebagainya</li>
<li>Disimpan di 1 lokasi lain. Beberapa artikel lain menyarankan jaraknya lebih dari 30 km.</li>
</ul>
<p>Oke, selanjutnya apa yang harus dibackup?</p>
<h2 id="isi-backup">Isi Backup</h2>
<p>Kalau kita bicara aplikasi atau layanan kita, setidaknya ada tiga komponen:</p>
<ul>
<li>aplikasinya (kode program yang sudah berjalan di server, maupun aplikasi client seperti Android atau iOS)</li>
<li>datanya (bisa berupa dump database, baik SQL maupun NoSQL, file hasil upload user, laporan yang sudah digenerate, dan sebagainya)</li>
<li>konfigurasinya (optimasi appserver, optimasi OS, optimasi database, konfigurasi koneksi database, dan lain sebagainya)</li>
</ul>
<p>Idealnya, aplikasi kita bisa direkonstruksi dari backup dalam hitungan menit. Tentunya tanpa memperhitungkan waktu transfer data dari lokasi backup.</p>
<p>Bila kita sudah mempraktekkan <a href="http://software.endy.muhardin.com/java/project-bootstrap-02/">Continuous Integration</a>, apalagi <a href="http://software.endy.muhardin.com/java/project-bootstrap-04/">Continuous Delivery</a>, proses rekonstruksi/recovery/restoration harusnya bisa dilakukan dengan satu-dua command saja.</p>
<p>Sedangkan untuk data pribadi, biasanya saya cukup membackup isi folder Dropbox dan Pictures.</p>
<h2 id="contoh-prosedur-restore">Contoh Prosedur Restore</h2>
<p>Sebagai software development house, asset yang paling berharga di ArtiVisi tentunya adalah source code aplikasi yang kita telah buat. Kantor boleh digusur, laptop bisa hilang atau rusak, tapi selama source code ada, kita bisa pulih dengan cepat.</p>
<p>ArtiVisi menggunakan Gitlab untuk menyimpan source code. Setiap hari dibackup secara otomatis ke Amazon S3. Di S3 sudah menunggu policy untuk mengkonversinya menjadi arsip Glacier di hari berikutnya. Dengan demikian, untuk melakukan restorasi secara garis besar prosedurnya adalah sebagai berikut:</p>
<ol>
<li>Restore dulu dari Glacier ke S3. Glacier merupakan layanan archival, jadi filenya tidak dapat diunduh langsung. Kita harus mengembalikannya ke S3 agar bisa diunduh.
2 Siapkan VPS baru untuk menampung hasil restore. Nantinya bisa juga ini langsung dijadikan server yang baru dengan mengubah konfigurasi DNS.</li>
<li>Install Gitlab dengan versi yang sesuai dengan yang digunakan di backup.</li>
<li>Download file backup terakhir dari Amazon S3 ke folder restore gitlab</li>
<li>Restore backup</li>
<li>Restart Gitlab dan Periksa Hasilnya</li>
</ol>
<h3 id="restorasi-glacier">Restorasi Glacier</h3>
<p>Untuk mengembalikan file yang sudah berada dalam Glacier ke S3, kita perlu login dulu ke web console. Kemudian masuk ke layanan S3 untuk melihat daftar file kita. Setelah itu, kita centang file yang ingin direstore, kemudian klik Initiate Restore</p>
<p><a href="https://lh3.googleusercontent.com/CsBm9uzjSWe3lrvPciq56r5q2IRpz92iFOXrXbpmpAWiBFxurCvKllf4v_7C1Dt7F5_b8drUqsDz=w369-h490-no"><img src="https://lh3.googleusercontent.com/CsBm9uzjSWe3lrvPciq56r5q2IRpz92iFOXrXbpmpAWiBFxurCvKllf4v_7C1Dt7F5_b8drUqsDz=w369-h490-no" alt="Initiate Restore" /></a></p>
<p>Amazon akan menanyakan berapa lama kita ingin file tersebut ditaruh di S3. Kita isi saja 3 hari agar kita bisa restore dengan tenang, tidak terburu-buru</p>
<p><a href="https://lh3.googleusercontent.com/cDmv0n0gR1N5esLzFeZuBxmaceVtjiyhnb7lWGx-P1oCO-c50CLBEZXvmNdHLjqA_0AurpgbGELK=w1160-h465-no"><img src="https://lh3.googleusercontent.com/cDmv0n0gR1N5esLzFeZuBxmaceVtjiyhnb7lWGx-P1oCO-c50CLBEZXvmNdHLjqA_0AurpgbGELK=w1160-h465-no" alt="Restore Duration" /></a></p>
<p>Selanjutnya, kita tinggal tunggu 3-5 jam sementara Amazon mengambilkan data kita yang disimpannya entah di mana. Kita bisa cek statusnya di tab Properties</p>
<p><a href="https://lh3.googleusercontent.com/v9eCf2j5JugY6nUnvEAtzx_j3KB3j2XIUa0czgN0B8FGNZ81E5UX9e9EGj7fb1xpmxmB46STR-U9=w1362-h475-no"><img src="https://lh3.googleusercontent.com/v9eCf2j5JugY6nUnvEAtzx_j3KB3j2XIUa0czgN0B8FGNZ81E5UX9e9EGj7fb1xpmxmB46STR-U9=w1362-h475-no" alt="Restore Status" /></a></p>
<p>Setelah selesai, kita akan melihat keterangan tambahan di Storage Class, yaitu Restored until <code class="language-plaintext highlighter-rouge"><waktu yang telah ditentukan></code>.</p>
<p><a href="https://lh3.googleusercontent.com/UP3075Ppg8DA7xM4u0T961_ZvlIrZlC4u7DpwqPii4CiPJYeBc5QuIA7QseRXHy6Nqc4pG4GO94Q=w1362-h512-no"><img src="https://lh3.googleusercontent.com/UP3075Ppg8DA7xM4u0T961_ZvlIrZlC4u7DpwqPii4CiPJYeBc5QuIA7QseRXHy6Nqc4pG4GO94Q=w1362-h512-no" alt="Restore Completed" /></a></p>
<p>Kita juga bisa melakukan langkah-langkah restorasi di atas menggunakan command line. Pertama, kita lihat dulu daftar file backupnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3 ls --summarize --human-readable s3://gitlab-artivisi-backup
</code></pre></div></div>
<p>Outputnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2016-08-31 19:01:34 2.6 GiB 1472670073_gitlab_backup.tar
2016-09-02 19:00:48 2.6 GiB 1472842838_gitlab_backup.tar
2016-09-03 19:00:50 2.6 GiB 1472929237_gitlab_backup.tar
2016-09-04 19:00:54 2.6 GiB 1473015639_gitlab_backup.tar
2016-09-05 19:00:49 2.6 GiB 1473102037_gitlab_backup.tar
2016-09-08 19:01:08 2.6 GiB 1473361253_gitlab_backup.tar
2016-09-09 19:00:50 2.6 GiB 1473447638_gitlab_backup.tar
2016-09-13 19:00:56 2.6 GiB 1473793242_gitlab_backup.tar
2016-09-14 19:00:53 2.6 GiB 1473879639_gitlab_backup.tar
2016-09-15 19:00:58 2.6 GiB 1473966043_gitlab_backup.tar
2016-09-16 19:01:00 2.6 GiB 1474052443_gitlab_backup.tar
2016-09-19 19:00:58 2.6 GiB 1474311643_gitlab_backup.tar
Total Objects: 12
Total Size: 31.7 GiB
</code></pre></div></div>
<p>Tentunya kita ingin ambil yang terbaru, yaitu yang paling bawah. Pastikan dulu storage classnya. Harusnya masih di Glacier. Kalau sudah di S3, maka tidak perlu direstore, langsung saja unduh.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3api head-object --bucket gitlab-artivisi-backup --key 1474311643_gitlab_backup.tar
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"AcceptRanges": "bytes",
"ContentType": "binary/octet-stream",
"LastModified": "Mon, 19 Sep 2016 19:00:58 GMT",
"ContentLength": 2840115200,
"ETag": "\"dad3ad8683b936e03de57bccbc764292-28\"",
"StorageClass": "GLACIER",
"Metadata": {}
}
</code></pre></div></div>
<p>Kita bisa lakukan restore dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3api restore-object --restore-request Days=3 --bucket gitlab-artivisi-backup --key 1474311643_gitlab_backup.tar
</code></pre></div></div>
<p>Bila kita cek lagi info filenya, maka statusnya akan berubah menjadi <code class="language-plaintext highlighter-rouge">Restore : Ongoing Request</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"Restore": "ongoing-request=\"true\"",
"AcceptRanges": "bytes",
"ContentType": "binary/octet-stream",
"LastModified": "Mon, 19 Sep 2016 19:00:58 GMT",
"ContentLength": 2840115200,
"ETag": "\"dad3ad8683b936e03de57bccbc764292-28\"",
"StorageClass": "GLACIER",
"Metadata": {}
}
</code></pre></div></div>
<p>Setelah selesai restore, info file akan menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"Restore": "ongoing-request=\"false\", expiry-date=\"Sat, 24 Sep 2016 00:00:00 GMT\"",
"AcceptRanges": "bytes",
"ContentType": "binary/octet-stream",
"LastModified": "Mon, 19 Sep 2016 19:00:58 GMT",
"ContentLength": 2840115200,
"ETag": "\"dad3ad8683b936e03de57bccbc764292-28\"",
"StorageClass": "GLACIER",
"Metadata": {}
}
</code></pre></div></div>
<p>Sama seperti versi web, kita mendapatkan tanggal tertentu dimana file tersebut bisa diakses.</p>
<blockquote>
<p>Peringatan !!! Jangan restore semua file backup sekaligus. Nanti kena charge mahal.</p>
</blockquote>
<p>Sebetulnya Amazon sudah mengingatkan kita pada waktu kita restore melalui antarmuka web. Begini katanya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You are charged a Glacier retrieval fee if you choose to restore more than 5% of your average monthly storage.
</code></pre></div></div>
<p>Terjemahan bebasnya kira-kira, “Awas jangan restore lebih dari 5%, nanti kena charge lho … “. Detail perhitungannya dijelaskan <a href="https://aws.amazon.com/s3/faqs/?tag=vglnk-c2223-20#How_will_I_be_charged_when_restoring_large_amounts_of_data_from_Amazon_Glacier">di websitenya Amazon</a>.</p>
<blockquote>
<p>Peringatan Kedua !!! File di Glacier jangan buru-buru dihapus</p>
</blockquote>
<p>Ini kesalahan yang saya lakukan di awal-awal karena tidak RTFM. Ternyata kalau kita hapus data di Glacier yang umurnya kurang dari 3 bulan, kita akan dikenakan fee. Detailnya bisa dibaca <a href="https://aws.amazon.com/s3/faqs/#How_am_I_charged_for_deleting_objects_from_Amazon_Glacier_that_are_less_than_3_months_old">di websitenya Amazon</a>.</p>
<h3 id="menyiapkan-target-restorasi">Menyiapkan Target Restorasi</h3>
<p>Kita akan <a href="https://m.do.co/c/910ad80271f7">membuat VPS baru di DigitalOcean</a> untuk menjalankan proses restorasi. Pembuatan VPS ini kita lakukan menggunakan <a href="https://github.com/digitalocean/doctl">aplikasi commandline resmi dari DigitalOcean</a>.</p>
<ol>
<li>
<p>Create droplet baru di DigitalOcean</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> doctl compute droplet create restoregitlab --size=2gb --image ubuntu-16-04-x64 --region nyc1
</code></pre></div> </div>
</li>
<li>
<p>Dapatkan IP address dari droplet yang baru dibuat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> doctl compute droplet list
</code></pre></div> </div>
<p>hasilnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image Status Tags
44271929 restoregitlab 67.205.183.31 2048 2 40 nyc1 Ubuntu 16.04.2 x64 active
</code></pre></div> </div>
</li>
<li>
<p>SSH ke droplet. Kadang harus ditunggu dulu sampai droplet aktif sempurna.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ssh root@67.205.183.31
</code></pre></div> </div>
</li>
<li>
<p>Install Gitlab dengan versi yang sesuai dengan yang digunakan di backup. Daftar installer semua versi ada <a href="https://www.gitlab.com/downloads/archives/">di sini</a> dan <a href="https://packages.gitlab.com/gitlab/gitlab-ce">di sini</a>.</p>
</li>
</ol>
<p><a href="https://lh3.googleusercontent.com/MY94aFy8XDsSbG3RmEvcF-_0RmvBLH9PijpOkumFd9Yz16gs9Yo1hggPH0J6y8RjlUn_SO8tkDyU=w1140-h694-no"><img src="https://lh3.googleusercontent.com/MY94aFy8XDsSbG3RmEvcF-_0RmvBLH9PijpOkumFd9Yz16gs9Yo1hggPH0J6y8RjlUn_SO8tkDyU=w1140-h694-no" alt="Gitlab Versi Lama" /></a></p>
<p>Masukkan versi Gitlab yang diinginkan di perintah <code class="language-plaintext highlighter-rouge">apt-get install gitlab-ce</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install curl openssh-server ca-certificates postfix -y
curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | bash
apt-get install gitlab-ce=8.11.0-ce.0
gitlab-ctl reconfigure
</code></pre></div></div>
<p>Kita bisa pastikan gitlab sudah terinstall dengan benar dengan cara browse ke alamat IP server kita. Gitlab yang baru terinstal akan meminta kita memasukkan password admin seperti ini.</p>
<p><a href="https://lh3.googleusercontent.com/8_VYj8E791LfTkVVm3agTsqQDkzJUWFSygR_R4Bbq2NenH3b65ZWbNiNMw2v7hcV-nKQei4TNMsc=w1106-h561-no"><img src="https://lh3.googleusercontent.com/8_VYj8E791LfTkVVm3agTsqQDkzJUWFSygR_R4Bbq2NenH3b65ZWbNiNMw2v7hcV-nKQei4TNMsc=w1106-h561-no" alt="Gitlab Fresh Install" /></a></p>
<p>Setelah Gitlab yang baru selesai diinstal, sekarang kita akan memulai proses restorasi. Untuk itu kita akan mengunduh file backup dari Amazon S3.</p>
<h3 id="download-file-backup">Download File Backup</h3>
<p>Untuk memudahkan dan mempercepat proses, kita akan gunakan aplikasi commandline yang disediakan Amazon, yaitu Amazon CLI (command line interface).</p>
<ol>
<li>
<p>Install Amazon CLI</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
apt install python-pip -y
pip install awscli
</code></pre></div> </div>
</li>
<li>
<p>Konfigurasi Secret Key</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mkdir .aws
printf "[default]" > .aws/config
printf "[default]" > .aws/credentials
printf "aws_access_key_id = AKIAKIMASUKKANACCESSKEYDISINI" >> .aws/credentials
printf "aws_secret_access_key = blablablamasukkansecretkeydisini" >> .aws/credentials
</code></pre></div> </div>
</li>
<li>
<p>Download file backup terakhir ke folder restore gitlab. Langkah ini bisa dijalankan paralel dengan proses instalasi dengan menggunakan tmux</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> aws s3 cp s3://gitlab-artivisi-backup/1474311643_gitlab_backup.tar /var/opt/gitlab/backups/
</code></pre></div> </div>
</li>
<li>
<p>Stop Gitlab Process</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gitlab-ctl stop unicorn
gitlab-ctl stop sidekiq
gitlab-ctl status
</code></pre></div> </div>
</li>
<li>
<p>Restore backup</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gitlab-rake gitlab:backup:restore BACKUP=1474311643
</code></pre></div> </div>
<p>Bila versi backup berbeda dengan versi yang terinstall, maka Gitlab akan memberikan pesan error yang informatif</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Unpacking backup ... done
GitLab version mismatch:
Your current GitLab version (8.11.7) differs from the GitLab version in the backup!
Please switch to the following version and try again:
version: 8.11.0
</code></pre></div> </div>
</li>
<li>
<p>Restart Gitlab dan Periksa Hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> gitlab-ctl start
gitlab-rake gitlab:check SANITIZE=true
</code></pre></div> </div>
</li>
</ol>
<p>Setelah restore dilakukan dengan sukses, kita akan melihat login screen</p>
<p><a href="https://lh3.googleusercontent.com/_VDioYRvWdEoZV5aMR7rcb4NA0STy6YRVEq1Pax0pN-PPfml-TEQfm31e6ZkwYB8n-JRYyY2tuxj=w997-h470-no"><img src="https://lh3.googleusercontent.com/_VDioYRvWdEoZV5aMR7rcb4NA0STy6YRVEq1Pax0pN-PPfml-TEQfm31e6ZkwYB8n-JRYyY2tuxj=w997-h470-no" alt="Login Screen" /></a></p>
<p>Lalu kita bisa coba login dan mengoperasikan Gitlab seperti biasa.</p>
<p>Setelah kita pastikan hasil restore bekerja dengan baik, kita bisa hapus lagi droplet Digital Ocean supaya kita tidak kena tagihan mahal. Digital Ocean menghitung tagihan per jam. Jadi jangan menunda-nunda untuk menghapus droplet.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>doctl compute droplet delete restoregitlab
</code></pre></div></div>
<h2 id="catatan-tambahan">Catatan Tambahan</h2>
<blockquote>
<p>Om, kan di artikelnya mbak Istofani katanya server dia crash di DigitalOcean, kenapa di artikel ini justru dipake buat restore?</p>
</blockquote>
<p>Mau dimanapun kita pasang server, Digital Ocean, Linode, Openshift, Heroku, Cloud Kilat, Amazon, pada suatu saat semuanya akan mengalami crash. Kalau mereka bisa restore sendiri, bagus. Kita tidak perlu repot. Walaupun demikian tidak seharusnya nasib kita digantungkan pada prosedur orang lain.</p>
<p>Jadi jangan malas mengimplementasikan prosedur backup dan restore.</p>
<p>Total waktu yang saya habiskan untuk membuat prosedur ini, mengetes, screenshot, menulis jadi blog, semuanya butuh waktu 1 hari penuh untuk backup dan 1 hari penuh untuk restore. Memang 2 mandays terasa banyak juga, apalagi kalau kita monetisasi dengan rate harian level software architect atau CTO. Tapi 2 mandays ini bisa menyelamatkan ratusan mandays yang hilang kalau kita tidak punya backup.</p>
<p>Sebagai tambahan motivasi, prosedur 2 mandays ini tidak ada apa-apanya dibandingkan apa yang dilakukan oleh Netflix. Bukan hanya berjaga-jaga terhadap server crash, mereka dengan sengaja membuat crash server mereka sendiri secara acak. Server production, bukan server testing.</p>
<p>Manfaatnya:</p>
<ul>
<li>mereka bisa tahu apa yang terjadi pada saat crash. Apakah layanan mati total, atau sekedar jadi lemot, atau lainnya</li>
<li>mereka bisa tahu, sejauh mana sistem mereka bisa bertahan terhadap kegagalan cloud provider. Bagaimana kalo Amazon mati di satu region? Bagaimana kalau Amazon mati di seluruh Amerika?</li>
<li>mereka bisa tahu, secepat apa mereka mendapatkan notifikasi terhadap error</li>
<li>dan banyak manfaat lainnya, berikut kutipannya</li>
</ul>
<blockquote>
<p>Failures happen and they inevitably happen when least desired or expected. If your application can’t tolerate an instance failure would you rather find out by being paged at 3am or when you’re in the office and have had your morning coffee? Even if you are confident that your architecture can tolerate an instance failure, are you sure it will still be able to next week? How about next month? Software is complex and dynamic and that “simple fix” you put in place last week could have undesired consequences. Do your traffic load balancers correctly detect and route requests around instances that go offline? Can you reliably rebuild your instances? Perhaps an engineer “quick patched” an instance last week and forgot to commit the changes to your source repository?
There are many failure scenarios that Chaos Monkey helps us detect. Over the last year Chaos Monkey has terminated over 65,000 instances running in our production and testing environments. Most of the time nobody notices, but we continue to find surprises caused by Chaos Monkey which allows us to isolate and resolve them so they don’t happen again.</p>
</blockquote>
<p>Selengkapnya bisa <a href="http://techblog.netflix.com/2012/07/chaos-monkey-released-into-wild.html">dibaca di blognya Netflix</a>. Software Chaos Monkey untuk membuat error tersebut juga mereka sediakan open source, silahkan kalau mau coba pasang.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah prosedur untuk melakukan restore backup dari Amazon Glacier. Prosedur ini harus dilakukan secara berkala untuk memastikan backup kita benar-benar dapat digunakan. Percuma saja kita backup setiap hari kalau ternyata pada waktu dibutuhkan kita tidak bisa restore.</p>
<p>Server akan crash, database akan error, harddisk akan corrupt. Pertanyaannya bukanlah</p>
<blockquote>
<p>Apakah server/database/harddisk saya akan error?</p>
</blockquote>
<p>melainkan</p>
<blockquote>
<p>Kapan server/database/harddisk saya akan error?</p>
</blockquote>
<p>Untuk itu, pastikan kita :</p>
<ol>
<li>Lakukan backup 3-2-1</li>
<li>Lakukan latihan restore</li>
<li>Dokumentasikan prosedur restore semudah mungkin agar bisa kita copy paste dan jalankan dengan mudah.</li>
</ol>
Mitos Framework2016-08-04T17:00:00+07:00https://software.endy.muhardin.com/programming/mitos-framework<p>Saya perhatikan, mungkin sekitar sebulan sekali, di <a href="https://web.facebook.com/groups/35688476100/">grup facebook PHP</a> akan muncul perdebatan berikut:</p>
<blockquote>
<p>Mendingan pakai framework atau tanpa framework (istilahnya di grup sana : native)</p>
</blockquote>
<p>Well, daripada tiap bulan saya komen yang sama-sama terus, mending tulis di sini sekali. Lumayan paste link sebulan sekali, naikin pageview :D</p>
<p>Oke, beberapa mitos dan salah paham tentang urusan framework vs native … Jreng jreng …</p>
<!--more-->
<h2 id="pake-framework-berarti-tidak-perlu-paham-native">Pake Framework berarti tidak (perlu) paham Native</h2>
<p>Banyak orang yang menganggap kalau pakai Eloquent (PHP), ActiveRecords (Ruby), Hibernate (Java), gak perlu lagi paham SQL, relasi database, join, primary-foreign key, dan teman-temannya.</p>
<blockquote>
<p>Salah besar gan !!!</p>
</blockquote>
<p>Yang benar, kita <em>gak perlu menulis SQL</em>. Beda ya <em>gak perlu nulis</em> sama <em>gak perlu paham</em>. Bukan beti ini (beda tipis), tapi beda banget.</p>
<blockquote>
<p>Gak percaya?</p>
</blockquote>
<p>Panjang jelasinnya gan. Coba deh google pake beberapa keyword ini:</p>
<ul>
<li>lazy loading</li>
<li>n+1 problem</li>
<li>cartesian problem</li>
</ul>
<p>Itu masalah dan dilema yang terjadi kalau kita pakai framework akses database. Untuk bisa paham apa artinya dan bagaimana solusinya, kudu paham SQL dan konsep relasional database.</p>
<p>Jadi gak asal pake-pake aja gan. Iya kali situ main pake-pake aja buat bikin PR sekolah atau skripsi, gak paham jeroannya. Kalo yang udah kerja beneran, bikin aplikasi production, bener-bener aplikasinya dipake orang banyak, sekalinya error yang ngamuk 100 cabang se-Indonesia Raya atau rame di social media, pastinya arsiteknya paham lah luar dalam frameworknya.</p>
<h2 id="pake-native-lebih-pintarhebat-daripada-pake-framework">Pake Native lebih pintar/hebat daripada pake Framework</h2>
<p>Ini ada hubungan dengan mitos pertama. Dianggapnya kalo pake framework, tinggal pake, gak perlu ngerti konsep dasar. Jadi kalo native pasti lebih ngerti pemrograman, algoritma, query, performance tuning, dan lain sebagainya.</p>
<p>Kalo menurut saya sih, yang beranggapan kayak gini kurang jam terbang aja. Coba nanti udah bikin lebih dari 3 aplikasi deh, gak usah banyak-banyak. Misalnya,</p>
<ul>
<li>project pertama : dapat order bikin aplikasi kayak tokopedia/bukalapak</li>
<li>project kedua : dapat order bikin aplikasi monitoring CCTV lewat web</li>
<li>project ketiga : suruh bikin aplikasi penerimaan siswa baru</li>
</ul>
<p>Dari ketiga project ini (dan semua project selanjutnya), pasti ada user management dan login kan ya?</p>
<blockquote>
<p>Nah, siapa orangnya yang di project keempat, masih mau coding user management dan login dari nol? Ayo tunjuk tangan …</p>
</blockquote>
<p>Coba kalo udah sampe sini. Mendingan belajar NoSQL atau masih mau <code class="language-plaintext highlighter-rouge">select * from tbl_user where username = $user</code> ?</p>
<p>Pinteran mana yang ngabisin waktu implement ulang fitur sama yang pakai framework trus waktunya dipake belajar MongoDB?</p>
<blockquote>
<p>So, pake framework itu bukan gara2 kurang pinter gan. Males aja ngabisin masa remaja coding itu-itu terus.</p>
</blockquote>
<p>Jangan dibandingin sama temen kos agan yang bikin skripsi pake Laravel lah.</p>
<h2 id="pake-native-hasilnya-lebih-bagus-daripada-pakai-framework">Pake Native hasilnya lebih bagus daripada pakai Framework</h2>
<p>Bagus di sini sering diartikan:</p>
<ul>
<li>lebih hemat resource (memori, CPU, harddisk, bandwidth)</li>
<li>response lebih cepat</li>
<li>query lebih efisien</li>
<li>bugnya lebih sedikit</li>
<li>dan sebagainya</li>
</ul>
<p>Ya iya kali gan kalo yang bikin framework model-model saya gini yang kurang ilmu apalagi amalnya. Lah ini yang bikin framework orang-orang terhebat di bidangnya.</p>
<p>Contohnya, framework akses database di Java. Namanya Hibernate. Kemampuannya, dia bisa generate SQL untuk berbagai merek dan versi database. Sekali tulis program, bisa jalan di MySQL versi 3, 4, 5 atau PostgreSQL versi 8, 9, dan merek lain seperti Oracle, SQL Server, DB2, dan masih banyak lagi. Pokoknya semua merek database populer dia bisa deh.</p>
<p>Nah, SQL yang dia generate, itu kan pastinya dicoding sama yang bikin Hibernate. Bikinnya rame-rame, namanya juga aplikasi open source. Jadi yang bikin generator SQL ke MySQL beda orang dengan yang bikin generator Oracle. Yang nulis generator MySQL orang yang udah puluhan tahun bikin SQL query buat MySQL. Demikian juga yang coding generator Oracle orang yang udah malang melintang bikin puluhan aplikasi pakai Oracle.</p>
<p>Jangan dibayangin pembuat framework kayak model-model agan, mau join tiga tabel aja nanya di fesbuk. Yang model gini sih, SQL yang dia tulis belum tentu lebih bagus daripada hasil generate framework :P</p>
<p>Quick test aja gan. Kalo ente nulis SQL, apa udah mikirin:</p>
<ul>
<li>SQL injection</li>
<li><a href="http://software.endy.muhardin.com/java/database-transaction/">Database Transaction</a></li>
<li>Optimasi generator primary key</li>
<li>Pagination</li>
</ul>
<p>Nah yang bikin framework populer itu udah mikirin hal-hal kayak gini. Pada saat artikel ini ditulis, Spring Framework udah versi 4, Hibernate versi 5, Laravel versi 5, Code Igniter versi 3, Ruby on Rails versi 5. Itu nomor versi sampe 5 gitu artinya udah ribuan bug yang difix, ribuan performance problem yang dituning, ribuan skenario aneh-aneh yang direquest user dan udah dibikinin.</p>
<p>Kalo masih nulis SQL model kayak gini ya kaga usah ngomong coding native lebih kenceng/bagus/sedikit bug gan.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$kueri</span> <span class="o">=</span> <span class="s1">'select password from tbl_user where username = '</span><span class="mf">.</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"username"</span><span class="p">];</span>
</code></pre></div></div>
<p>atau versi Java</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">kueri</span> <span class="o">=</span> <span class="s">"select password from tbl_user where username = "</span><span class="o">+</span><span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"username"</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="pake-native-lebih-fleksibel-framework-kaku">Pake Native lebih fleksibel, Framework kaku</h2>
<p>Yang punya klaim kayak gini, coba kasi contoh real world, fleksibilitas seperti apa yang mau dibikin, dan framework mana yang gak bisa akomodasi?</p>
<p>Pada sebagian besar kasus, yang berpendapat gini biasanya belum paham frameworknya. Sehingga dikira gak bisa, padahal fiturnya ada, dia aja yang belum baca dokumentasinya.</p>
<p>Contohnya relasi database. Framework Hibernate mendukung relasi:</p>
<ul>
<li>one to many biasa</li>
<li>one to many dengan join table</li>
<li>inheritance dengan single table</li>
<li>inheritance, satu tabel per class</li>
<li>dan kasus-kasus eksotik lainnya</li>
</ul>
<p>Kurang fleksibel gimana lagi? Coba kasus mana yang gak ada solusinya?</p>
<p>Pada kasus sisanya, dia mencoba menggunakan framework tidak sesuai peruntukannya. Atau dia pengen melakukan hal yang semestinya tidak dilakukan, baik pakai framework ataupun ngga.</p>
<p>Contohnya:</p>
<blockquote>
<p>Pake Laravel ribet, masa gak bisa otomatis field di HTML masuk ke Model?</p>
</blockquote>
<p>Bisa, di Laravel harus bikin variabel <code class="language-plaintext highlighter-rouge">$fillable</code>. Kalo bahasa di Rails, <code class="language-plaintext highlighter-rouge">$fillable</code> ini namanya <code class="language-plaintext highlighter-rouge">param permit</code>.</p>
<p>Nah, ini hal yang semestinya gak dilakukan. Ada security hole pada proses mass assignment.</p>
<blockquote>
<p>Apa itu mass assignment? Apa problemnya?</p>
</blockquote>
<p>Nah good question. Silahkan digoogle sendiri apa itu mass assignment problem. Coba kamu bikin aplikasi tanpa framework. Kepikiran gak masalah ini?</p>
<p>Yang bikin framework udah kepentok masalah ini, dan udah bikin solusinya. Lah kalo istilahnya aja baru denger sekarang, masa iya udah terpasang solusinya di coding native yang kamu bikin?</p>
<p>Di 0.0001% kasus sisanya, mungkin memang ada skenario yang kita mau kerjain, dan belum disediakan sama framework. Trus gimana?</p>
<blockquote>
<p>Framework yang populer dan banyak digunakan orang, biasanya open source. Artinya source code ada, tersedia, bisa dimodif sesuka hati. Ya bikinlah solusinya.</p>
</blockquote>
<h2 id="framework-emang-gampang-tapi-kalo-error-lebih-susah-ditangani">Framework emang gampang, tapi kalo error lebih susah ditangani</h2>
<p>Salah gan. Framework lebih susah. Kan harus belajar aturan-aturan dia. Asumsi pembuatnya. Mau query taruh di mana, mau render HTML taruh di mana, mau pake Stored Procedure gimana caranya, dan sebagainya.</p>
<p>Nah, abis dipelajari, udah paham, pas error ya gak susah juga ditangani. Kan udah paham. Kalo belum paham sih jangankan framework, kapan hari ada juga yang komentar gini</p>
<blockquote>
<p>pake PHP susah ya, kalo error gak keluar pesan errornya</p>
</blockquote>
<p>Yah … speechless dah. Pake native tuh.</p>
<h2 id="penutup">Penutup</h2>
<p>Ini bukan belain framework dan native jelek ya. Cuma kebanyakan yang komentar framework jelek saya liat belum cukup melihat dunia. Coba deh bikin aplikasi barang 5 biji aja. Nanti baru paham plus minusnya.</p>
<p>Jangan baru bikin peer sekolah satu aplikasi, itupun <a href="http://software.endy.muhardin.com/manajemen/aplikasi-prakarya-vs-aplikasi-production/">kualitasnya prakarya</a>, trus komentar. Bukan apa-apa, kasian nanti menyesatkan orang.</p>
<p>Jangan juga karena males baca, males nyoba, susah dikit nyerah, trus berkesimpulan native lebih baik daripada framework. Kasian agan sendiri, gak maju-maju nanti hidupnya.</p>
<p>Ada banyak pertimbangan orang kenapa pakai framework, diantaranya:</p>
<ul>
<li>Konsistensi struktur. Misalnya kita udah bikin 4 aplikasi, gak pake framework. Kira-kira struktur foldernya sama gak antar aplikasi? Pastinya gak sama. Trus kalo disuruh nambah fitur lagi di aplikasi pertama, susah atau gampang mengingat lagi struktur kode programnya?</li>
<li>Teamwork. Misalnya projectnya agak gede, dikerjain 3 orang, gak pake framework. Kira-kira seragam gak cara codingnya? Trus pas yang 1 orang sakit/resign/bosen/sibuk nyari pokemon, trus kita ganti orang baru, susah atau gampang transisinya? Emang mau tiap ada orang baru ngajarin lagi?</li>
</ul>
<blockquote>
<p>Jadi, kita harus selalu pake framework dong??</p>
</blockquote>
<p>Ya kalo peer sekolah, tugas kuliah, disuruh dosen jangan pake framework, ya jangan dipake. Bedain peer sekolah sama kerjaan. Tugas sekolah itu tujuannya biar ngerti. Kalo disuruh bikin aplikasi webmail, ya kerjain tuh parsing protokol POP3 dan IMAP. Jangan pakai library. Biar paham kalo mau ambil email, commandnya apa, mau hapus email sintaksnya gimana. Biar ngerti request HTTP itu gimana formatnya, response header apa artinya.</p>
<p>Kalo kerjaan komersil, harus deliver aplikasi ke client, on time, on budget, harus secure, performance kenceng, fungsi jalan bener, ya pake framework.</p>
<p>Paling penting, jangan ngejudge begini begitu padahal belum baca dokumentasi, belum pernah ngerjain kerjaan berbayar, belum pernah kerja tim, belum pernah maintain aplikasi bertahun-tahun.</p>
<p>Nah, demikian sedikit sharing. Semoga manfaat :D</p>
Intro Docker2016-07-22T17:00:00+07:00https://software.endy.muhardin.com/linux/intro-docker<p>Beberapa tahun terakhir ini, terjadi perubahan yang cukup signifikan dalam hal deployment aplikasi. Untuk menjalankan aplikasi yang serius, sebelumnya kita perlu membeli server dulu, melakukan instalasi sistem operasi dan kelengkapan software lainnya, menginstal aplikasi yang kita buat, kemudian melakukan tuning dan hardening. Setelah itu, kita harus menyewa tempat di perusahaan penyedia internet dan menitipkan server kita tadi di sana. Kegiatan tersebut relatif merepotkan. Belum lagi urusan yang menyertainya seperti pemeliharaan hardware, update software, backup, dan lainnya.</p>
<p>Di jaman sekarang, banyak perusahaan yang menyediakan layanan cloud. Mereka menangani segala urusan repot tadi, sehingga kita cukup memikirkan pembuatan dan pemeliharaan aplikasi kita sendiri. Tidak perlu lagi pusing memikirkan urusan hardware dan software lain yang dibutuhkan.</p>
<p>Salah satu teknologi yang sedang hot di tahun 2015-2016 ini adalah <a href="https://www.docker.com/">Docker</a>. Dalam artikel ini, kita akan bahas:</p>
<ul>
<li>Apa itu Docker?</li>
<li>Bagaimana menjalankan aplikasi Java dalam Docker?</li>
<li>Bagaimana menjalankan proses build dalam Docker?</li>
</ul>
<!--more-->
<h2 id="apa-itu-docker-">Apa itu Docker ?</h2>
<p>Docker adalah salah satu bentuk dari virtualisasi. Mari kita bahas dulu apa itu virtualisasi.</p>
<h3 id="memahami-virtualisasi">Memahami Virtualisasi</h3>
<p>Virtualisasi adalah kegiatan membuat virtual machine, atau mesin palsu.</p>
<blockquote>
<p>Apa itu mesin palsu?</p>
</blockquote>
<p>Secara sederhana, virtual machine adalah lawan kata dari physical machine atau mesin fisik. Physical machine adalah server seperti yang biasa kita beli dan gunakan, memiliki berbagai komponen seperti power supply, mainboard, memory, disk, dan sebagainya. Komponen ini bisa kita lihat dan pegang bendanya.</p>
<p>Virtual machine adalah seolah-olah ada mesin dan komponen tersebut. Aplikasi merasa bahwa dia berjalan sendirian dalam satu mesin, walaupun sebenarnya dia berjalan di atas mesin lain, bersama-sama dengan aplikasi lain.</p>
<blockquote>
<p>Untuk apa dipalsukan?</p>
</blockquote>
<p>Ada beberapa keperluan, diantaranya:</p>
<ul>
<li>kita ingin menjalankan sistem operasi yang berbeda. Misalnya laptop kita menggunakan MacOS, tapi ingin menjalankan Windows atau Linux</li>
<li>kita ingin memaksimalkan penggunaan mesin fisik. Daripada satu aplikasi satu mesin, lebih baik beberapa aplikasi dijalankan di mesin yang sama. Virtualisasi menyediakan pemisahan antar aplikasi sehingga tidak saling mengganggu.</li>
<li>keterbatasan ruang dan sumber daya (listrik, AC)</li>
</ul>
<p>Karena ada beberapa sistem operasi yang berjalan dalam satu mesin fisik, kita menggunakan istilah host (tuan rumah) dan guest (tamu) untuk membedakannya. Host adalah sistem operasi yang benar-benar diinstal di mesin fisik. Guest adalah sistem yang diinstal di atas host.</p>
<p>Secara garis besar, ada dua strategi yang umum digunakan dalam membuat virtualisasi ini:</p>
<ul>
<li>hypervisor / full virtualization</li>
<li>container / partial virtualization</li>
</ul>
<p>Pada hypervisor, semua hardware dibuatkan palsunya, dan masing-masing sistem guest mendapatkan satu set hardware palsu tadi. Kalau digambarkan, strategi hypervisor terlihat seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/KVLalZ4_mMDOXBGq0bh2uyYkgU1e0S2NvswA3rERlJafbkFmMVXl3jko7_DDhusPAG0UfAY6rDnb=w800-no"><img src="https://lh3.googleusercontent.com/KVLalZ4_mMDOXBGq0bh2uyYkgU1e0S2NvswA3rERlJafbkFmMVXl3jko7_DDhusPAG0UfAY6rDnb=w800-no" alt="Skema Hypervisor" /></a></p>
<p>Sedangkan pada strategi container, semua guest berbagi pakai sebagian besar hardware. Host cuma mengatur pemisahan folder dan ijin akses antar guest sehingga tidak bisa saling melihat. Kalau kita ilustrasikan, kira-kira gambarnya seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/loB_DRpmMrvifR816TiLF4gHqNbaSD4GQG94sQJ7xatlzWC2qfqHLjh1KS-ecS6UiyFc96rRXlcU=w800"><img src="https://lh3.googleusercontent.com/loB_DRpmMrvifR816TiLF4gHqNbaSD4GQG94sQJ7xatlzWC2qfqHLjh1KS-ecS6UiyFc96rRXlcU=w800" alt="Skema Container" /></a></p>
<p>Masing-masing tentunya ada plus dan minusnya. Bila kita menggunakan hypervisor:</p>
<ul>
<li>sistem operasi host dan guest tidak ada hubungan sama sekali. Jadi kita bisa menginstal sistem operasi apapun sebagai guest.</li>
<li>overhead lebih tinggi, karena adanya hardware palsu. Konsekuensinya, performance menjadi lebih terbatas. Penggunaan resource juga tidak bisa terlalu dioptimalkan, karena begitu sudah dibooking oleh salah satu guest tidak bisa digunakan guest lain, walaupun guest pertama sedang santai.</li>
<li>lebih mudah mengatur security, karena hubungan antara host dan guest sangat sedikit.</li>
</ul>
<p>Bila kita menggunakan container:</p>
<ul>
<li>sistem operasi guest berbagi kernel dan aplikasi utama dengan host. Dengan demikian kita tidak bisa menginstal sistem operasi yang berbeda dengan host, seperti Windows di host Linux.</li>
<li>penggunaan resource hardware bisa lebih dioptimalkan. Karena host mengetahui secara detail kondisi masing-masing guest, maka dia bisa mengalokasikan resource yang menganggur kepada guest yang sedang sibuk.</li>
<li>pengaturan security menjadi lebih sulit, karena host dan guest berhubungan erat, sehingga mungkin terjadi kebocoran antar guest.</li>
</ul>
<h3 id="produk-virtualisasi">Produk Virtualisasi</h3>
<p>Ada banyak produk virtualisasi yang beredar di pasaran, diantaranya</p>
<p>Yang berbasis hypervisor</p>
<ul>
<li>Oracle Virtualbox</li>
<li>VMWare</li>
<li>KVM</li>
<li>Microsoft Hyper-V</li>
</ul>
<p>Yang berbasis container</p>
<ul>
<li>OpenVZ</li>
<li>LXC</li>
</ul>
<p>Dari seluruh pilihan ini, kita akan membahas Docker. Saat ini Docker sedang naik daun, dan digunakan orang-orang untuk mendeploy aplikasi mereka. Komunitasnya juga sangat aktif, sehingga banyak aplikasi-aplikasi yang dikembangkan untuk mendukung dan memudahkan deployment dengan Docker. Saking banyaknya aplikasi tersebut, sulit untuk mengetahui fungsi, kegunaan, dan status development dari masing-masingnya. Bila Anda ingin mengetahui lebih jauh, silahkan baca petanya <a href="https://www.mindmeister.com/389671722/docker-ecosystem">di sini</a>.</p>
<p>Topik pembahasan kita pada artikel ini – yaitu Docker – pada awalnya menggunakan LXC sebagai basisnya. Akan tetapi pada rilis 0.9 di tahun 2014, Docker mengganti LXC dengan libcontainer yang dikembangkan sendiri oleh programmer Docker. Kita tidak akan bahas panjang lebar tentang perbedaan antara keduanya. Bagi mereka yang kepo, dipersilahkan membaca <a href="http://jancorg.github.io/blog/2015/01/03/libcontainer-overview/">artikel ini</a> dan <a href="https://www.flockport.com/lxc-vs-docker/">artikel itu</a> yang membahas perbedaan keduanya. Dengan mengadopsi libcontainer, Docker bisa berjalan di berbagai platform, seperti yang dijelaskan dalam <a href="http://www.infoworld.com/article/2607966/application-virtualization/4-reasons-why-docker-s-libcontainer-is-a-big-deal.html">artikel InfoWorld ini</a>.</p>
<h2 id="konsep-docker">Konsep Docker</h2>
<p>Docker terdiri dari beberapa komponen, seperti digambarkan dalam diagram berikut</p>
<p><img src="https://lh3.googleusercontent.com/G48TWSnpblDOtKAs-21yhCGPjBJandKPT7ShEdVbhrHffbbt3DicQkj3Y97YYbIUHmWI32aY90e4=w600-h314-no" alt="Arsitektur Docker" /></p>
<p><em>Gambar diambil dari <a href="https://docs.docker.com/engine/understanding-docker/">sini</a></em></p>
<p>Untuk memahami diagram tersebut, kita perlu memahami beberapa istilah yang digunakan di dunia Docker</p>
<ul>
<li>Docker Container : adalah virtual machine atau guest operating system. Aplikasi kita berjalan di dalam docker container dan merasa bahwa dia berjalan di dalam sistem operasi biasa. Docker container berjalan di atas docker engine.</li>
<li>Docker Client : adalah seperangkat perintah command line untuk mengoperasikan docker container, misalnya membuat container, start/stop container, menghapus (destroy), dan sebagainya. Docker client hanya bertugas mengirim perintah saja. Pekerjaan sesungguhnya dilakukan oleh docker daemon.</li>
<li>Docker Daemon : adalah aplikasi yang berjalan di host machine. Docker server berjalan di background (sebagai daemon) dan menunggu perintah dari docker client. Begitu mendapatkan perintah, docker server bekerja membuat container, menjalankan/mematikan container, dan sebagainya.</li>
<li>Docker Engine : adalah gabungan aplikasi yang menjalankan docker container, docker client, dan docker daemon. Dia menyediakan layanan umum agar container dapat berjalan dengan baik, misalnya: networking, storage, alokasi memori, CPU, dan sebagainya.</li>
<li>Docker Image : adalah template yang digunakan untuk membuat container. Contohnya, ada image Ubuntu, CentOS, dan sebagainya. Kita juga bisa membuat image baru dari image yang lama. Misalnya kita buat image Ubuntu yang sudah terinstal Java dan MySQL.</li>
<li>Docker Machine : adalah sistem untuk mengelola docker engine. Dia bisa menginstal engine baru di berbagai target. Saat ini dia mendukung VirtualBox, Digital Ocean, dan Amazon. Dengan Docker Machine, kita bisa membuat dan menjalankan container dengan docker client, dan hasilnya container kita berjalan di local (dengan VirtualBox) atau di cloud (dengan DigitalOcean atau Amazon).</li>
</ul>
<p>Perbedaan antara Docker Engine dan Docker Machine dapat dilihat pada ilustrasi berikut. Docker engine terdiri dari docker client, daemon, dan container runtime seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/wKZstwquZqf0qhBJLYHnIaBoJu8J2l2avxvzCywiKOmj0D4hf4XKnOy4ffw2Malz7fGs6p6RReC1=w800"><img src="https://lh3.googleusercontent.com/wKZstwquZqf0qhBJLYHnIaBoJu8J2l2avxvzCywiKOmj0D4hf4XKnOy4ffw2Malz7fGs6p6RReC1=w800" alt="Docker Engine" /></a></p>
<p>Sedangkan docker machine bertugas untuk menyiapkan docker engine di target deployment, seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/HsajbHv3NEd5yHaDk4U_olaaWO6rU8A8dk1FVaBccLL-k6wJurXNHKsBZRPMOIxAG6d7SSLWdiF8=w800"><img src="https://lh3.googleusercontent.com/HsajbHv3NEd5yHaDk4U_olaaWO6rU8A8dk1FVaBccLL-k6wJurXNHKsBZRPMOIxAG6d7SSLWdiF8=w800" alt="Docker Machine" /></a></p>
<p>Kedua gambar di atas diambil dari <a href="https://docs.docker.com/machine/overview/">dokumentasi Docker</a>.</p>
<ul>
<li>Docker Compose. Best practices dalam Docker adalah menjalankan satu proses dalam satu container. Misalnya, bila kita ingin menjalankan Wordpress, kita butuh satu container untuk webserver, satu container untuk database server, satu container untuk menyimpan data dari database server, dan satu container lagi untuk menyimpan file dari webserver. Untuk memudahkan pengelolaannya, Docker menyediakan aplikasi yang bernama Docker Compose.</li>
<li>Docker Swarm. Ini adalah solusi clustering yang disediakan oleh docker.</li>
</ul>
<h2 id="instalasi-docker">Instalasi Docker</h2>
<p>Instalasi Docker tidak akan kita bahas panjang lebar, karena cara dan langkah-langkahnya bisa berubah-ubah seiring waktu. Silahkan lihat <a href="https://docs.docker.com/engine/installation/">dokumentasi di website docker</a>.</p>
<p>Sebaiknya instalasi dilakukan dengan koneksi internet yang mumpuni dan tidak terbatas kuota. Pada saat instalasi, kita akan mendownload image berukuran ratusan MB.</p>
<p>Setelah instalasi selesai, pastikan kita bisa menjalankan perintah berikut di command line.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker --version
</code></pre></div></div>
<p>Outputnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Docker version 1.10.3, build 20f81dd
</code></pre></div></div>
<p>Cek juga apakah <code class="language-plaintext highlighter-rouge">docker-machine</code> sudah terinstal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine --version
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine version 0.6.0, build e27fb87
</code></pre></div></div>
<p>Dengan kedua aplikasi tersebut, kita sudah bisa bermain-main dengan Docker. Sebagian besar tutorial di internet mengajarkan kita untuk menjalankan docker di laptop kita sendiri. Akan tetapi, saya tidak menganjurkan Anda untuk melakukannya. Proses pembuatan image docker sangat rakus internet. Sekali membuat image, dia akan mendownload beratus-ratus megabytes. Ini tidak masalah kalau koneksi internet kita kencang dan bebas kuota. Tapi kalau kita pakai paket data provider seluler, siap-siap saja mengeluarkan <a href="http://naruto.wikia.com/wiki/Izanagi">jurus Izanagi</a> dengan kartu perdana internet :D</p>
<p><img src="https://lh3.googleusercontent.com/QT-5o3C5xuUyK0uP1r_YLO1CfJFAG-U4c8jxzNvzq_RHN0JFmvnQWeZ6YFZickgq49Eyq5Ul7ypL=w560-h315-no" alt="Danzo Izanagi" /></p>
<p>Untungnya, ada solusi yang lebih quota-friendly, yaitu menggunakan layanan cloud. Dengan menjalankan Docker Machine di cloud, kita menggunakan koneksi internet server virtual kita untuk membuat image. Sedangkan kuota yang kita gunakan hanya sebatas untuk koneksi SSH ke server kita.</p>
<p>Docker mendukung berbagai cloud services, diantaranya:</p>
<ul>
<li>Amazon Web Services</li>
<li>Microsoft Azure</li>
<li>Digital Ocean</li>
<li><a href="https://docs.docker.com/machine/drivers/">dan sebagainya</a></li>
</ul>
<p>Kali ini kita akan gunakan cloud provider murah meriah, Digital Ocean.</p>
<h2 id="menjalankan-docker-machine-di-cloud-services">Menjalankan Docker Machine di Cloud Services</h2>
<p>Di Digital Ocean, kita bisa langsung membuat VPS dan menyiapkan docker machine dalam satu perintah.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine create --driver digitalocean --digitalocean-size 1gb --digitalocean-access-token yaddayaddayadda docker-ocean
</code></pre></div></div>
<p>Kita bisa mengatur berbagai variabel dengan perintah tersebut, pada contoh di atas, yang diatur adalah:</p>
<ul>
<li>ukuran VPS, yaitu 1gb</li>
<li>nama docker machine, sekaligus nama VPS, yaitu <code class="language-plaintext highlighter-rouge">docker-ocean</code></li>
</ul>
<p>Berikut adalah output dari perintah tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Running pre-create checks...
Creating machine...
(docker-ocean) Creating SSH key...
(docker-ocean) Creating Digital Ocean droplet...
(docker-ocean) Waiting for IP address to be assigned to the Droplet...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with ubuntu(systemd)...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env docker-ocean
</code></pre></div></div>
<p>Pastikan kita mengisi API token Digital Ocean dengan benar. Setelah perintah tersebut dijalankan, kita bisa mulai menjalankan docker image.</p>
<p>Sebelum mulai menjalankan perintah docker, terlebih dulu kita harus menginisialisasi environment variable di command prompt yang kita pakai. Gunakan perintah berikut untuk melakukan inisialisasi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eval $(docker-machine env docker-ocean)
</code></pre></div></div>
<p>Perintah tersebut akan menaruh variabel yang dibutuhkan Docker, seperti lokasi alamat IP docker-machine, sertifikat SSL, dan sebagainya. Kita bisa lihat daftar variabelnya dengan menjalankan perintah tersebut tanpa <code class="language-plaintext highlighter-rouge">eval</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-machine env docker-ocean
</code></pre></div></div>
<p>berikut adalah outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://104.131.94.223:2376"
export DOCKER_CERT_PATH="/Users/endymuhardin/.docker/machine/machines/docker-ocean"
export DOCKER_MACHINE_NAME="docker-ocean"
# Run this command to configure your shell:
# eval $(docker-machine env docker-ocean)
</code></pre></div></div>
<h2 id="menjalankan-docker-image">Menjalankan Docker Image</h2>
<p>Selanjutnya, kita bisa menjalankan Docker image yang sudah dibuatkan orang lain. Sebagai contoh, mari kita jalankan Tomcat Server.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -it --rm -p 8888:8080 tomcat:8.5
</code></pre></div></div>
<p>Kita bisa lihat dari outputnya bahwa docker mulai bekerja mengunduh image dan menjalankan service.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Unable to find image 'tomcat:8.5' locally
8.5: Pulling from library/tomcat
5c90d4a2d1a8: Pull complete
ab30c63719b1: Pull complete
be275827e8b7: Pull complete
4cbd0b70645a: Pull complete
7d811bfac6eb: Pull complete
d35e5f0a148b: Extracting 45.12 MB/53.37 MB
a17d585d8b66: Download complete
1b424810697e: Download complete
ecbe3919f2cd: Download complete
f6d7b2464610: Download complete
1b51665f96fb: Download complete
74b0dba450b9: Download complete
Digest: sha256:1adbbf78f7fae48233d3734905152fe1fec3a43a0cdc64dc926a71ecb2744809
Status: Downloaded newer image for tomcat:8.5
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr/lib/jvm/java-8-openjdk-amd64/jre
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
22-Jul-2016 09:13:33.980 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version: Apache Tomcat/8.5.4
22-Jul-2016 09:13:33.988 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Jul 6 2016 08:43:30 UTC
22-Jul-2016 09:13:33.988 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server number: 8.5.4.0
22-Jul-2016 09:13:33.989 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
22-Jul-2016 09:13:33.989 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 4.2.0-27-generic
22-Jul-2016 09:13:33.990 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
22-Jul-2016 09:13:33.991 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /usr/lib/jvm/java-8-openjdk-amd64/jre
22-Jul-2016 09:13:33.991 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 1.8.0_91-8u91-b14-1~bpo8+1-b14
22-Jul-2016 09:13:33.992 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Oracle Corporation
22-Jul-2016 09:13:33.993 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
22-Jul-2016 09:13:33.993 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
22-Jul-2016 09:13:33.994 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
22-Jul-2016 09:13:33.995 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
22-Jul-2016 09:13:34.002 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
22-Jul-2016 09:13:34.003 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
22-Jul-2016 09:13:34.004 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
22-Jul-2016 09:13:34.004 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
22-Jul-2016 09:13:34.005 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded APR based Apache Tomcat Native library 1.2.8 using APR version 1.5.1.
22-Jul-2016 09:13:34.006 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent APR capabilities: IPv6 [true], sendfile [true], accept filters [false], random [true].
22-Jul-2016 09:13:34.006 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent APR/OpenSSL configuration: useAprConnector [false], useOpenSSL [true]
22-Jul-2016 09:13:34.013 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized (OpenSSL 1.0.2h 3 May 2016)
22-Jul-2016 09:13:34.223 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
22-Jul-2016 09:13:34.266 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
22-Jul-2016 09:13:34.269 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-nio-8009"]
22-Jul-2016 09:13:34.273 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
22-Jul-2016 09:13:34.276 INFO [main] org.apache.catalina.startup.Catalina.load Initialization processed in 1380 ms
22-Jul-2016 09:13:34.337 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service Catalina
22-Jul-2016 09:13:34.338 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/8.5.4
22-Jul-2016 09:13:34.369 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /usr/local/tomcat/webapps/host-manager
22-Jul-2016 09:13:35.391 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /usr/local/tomcat/webapps/host-manager has finished in 1,022 ms
22-Jul-2016 09:13:35.399 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /usr/local/tomcat/webapps/docs
22-Jul-2016 09:13:35.462 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /usr/local/tomcat/webapps/docs has finished in 63 ms
22-Jul-2016 09:13:35.470 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /usr/local/tomcat/webapps/examples
22-Jul-2016 09:13:36.256 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /usr/local/tomcat/webapps/examples has finished in 786 ms
22-Jul-2016 09:13:36.257 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /usr/local/tomcat/webapps/ROOT
22-Jul-2016 09:13:36.307 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /usr/local/tomcat/webapps/ROOT has finished in 49 ms
22-Jul-2016 09:13:36.311 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /usr/local/tomcat/webapps/manager
22-Jul-2016 09:13:36.375 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /usr/local/tomcat/webapps/manager has finished in 63 ms
22-Jul-2016 09:13:36.384 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler [http-nio-8080]
22-Jul-2016 09:13:36.394 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler [ajp-nio-8009]
22-Jul-2016 09:13:36.395 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 2119 ms
</code></pre></div></div>
<p>Setelah selesai browse ke alamat IP server yang didapatkan pada waktu inisialisasi <code class="language-plaintext highlighter-rouge">docker-machine</code> tadi, yaitu <code class="language-plaintext highlighter-rouge">http://104.131.94.223:8080</code></p>
<h2 id="membuat-image-docker">Membuat Image Docker</h2>
<p>Kita juga bisa membuat image custom sesuai kebutuhan kita. Misalnya aplikasi yang kita buat membutuhkan Java, Maven, dan MySQL agar berjalan dengan baik. Kita juga ingin database MySQL langsung dikonfigurasi sesuai aplikasi kita, yaitu dengan nama database tertentu dan user tertentu yang bisa mengakses database tersebut.</p>
<p>Sebagai contoh, kita akan menggunakan aplikasi yang kita gunakan pada <a href="http://software.endy.muhardin.com/java/project-bootstrap-01/">seri tutorial Continuous Integration sebelumnya</a>. Source code projectnya bisa <a href="https://github.com/endymuhardin/belajar-ci.git">diambil di Github</a></p>
<p>Aplikasi tersebut membutuhkan database yang disetup dengan konfigurasi:</p>
<ul>
<li>nama database : belajar</li>
<li>username database : belajar</li>
<li>password database : java</li>
</ul>
<p>Untuk itu, kita membuat konfigurasi Dockerfile untuk menginisiasi docker instance yang memiliki database tersebut. Berikut isi Dockerfilenya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM maven:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y mysql-server \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
VOLUME ~/.m2 /var/lib/mysql /tmp
COPY setup-database.sh /setup-database.sh
ENTRYPOINT ["/setup-database.sh"]
CMD ["/bin/bash"]
</code></pre></div></div>
<p>Pada Dockerfile di atas, ada beberapa hal yang kita lakukan:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">FROM maven:latest</code> : kita mulai dengan image <code class="language-plaintext highlighter-rouge">maven</code> yang sudah dibuatkan orang. Biasanya memang kita tidak membuat image dari awal, tapi menggunakan image yang sudah ada yang paling mendekati dengan keinginan kita. Selanjutnya kita customize lebih lanjut.</li>
<li><code class="language-plaintext highlighter-rouge">ENV DEBIAN_FRONTEND=noninteractive</code> : biasanya waktu kita menginstal MySQL, kita akan ditanyakan password <code class="language-plaintext highlighter-rouge">root</code> database server. Karena proses instalasinya tidak interaktif, maka kita ingin melewati pertanyaan password tersebut dan langsung saja menggunakan nilai default. Untuk itu kita suruh <code class="language-plaintext highlighter-rouge">apt-get</code> untuk tidak bertanya-tanya.</li>
<li><code class="language-plaintext highlighter-rouge">VOLUME</code> : kita menandai beberapa folder dalam container menjadi volume. Nantinya volume ini bisa kita mapping ke folder di sistem host sehingga isinya bisa langsung diakses walaupun containernya sedang mati. Volume juga bisa dipakai untuk menyelamatkan file dalam container agar tidak terhapus pada saat container dihapus. Selain itu, dengan menggunakan volume, kita bisa berbagi pakai file dan folder dengan container lain. Misalnya kita ingin membuat replikasi atau clustering dimana file-file aplikasi harus bisa diakses dari semua instance/container.</li>
<li><code class="language-plaintext highlighter-rouge">COPY setup-database.sh /setup-database.sh</code> : kita copy script inisialisasi database yang sudah kita siapkan. Kita akan lihat isinya sebentar lagi</li>
<li><code class="language-plaintext highlighter-rouge">ENTRYPOINT ["/setup-database.sh"]</code> : kita ingin script tersebut dijalankan pada waktu docker booting</li>
<li><code class="language-plaintext highlighter-rouge">CMD ["/bin/bash"]</code> : setelah script selesai dijalankan, jalankan <code class="language-plaintext highlighter-rouge">bash</code> agar kita mendapat command prompt</li>
</ul>
<p>Berikut adalah isi file <code class="language-plaintext highlighter-rouge">setup-database.sh</code></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nb">set</span> <span class="nt">-e</span>
service mysql start
<span class="nv">dbname</span><span class="o">=</span>belajar
<span class="nv">dbusername</span><span class="o">=</span>belajar
<span class="nv">dbpassword</span><span class="o">=</span>java
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$MYSQL_DATABASE</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">dbname</span><span class="o">=</span><span class="s2">"</span><span class="nv">$MYSQL_DATABASE</span><span class="s2">"</span>
<span class="k">fi
if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$MYSQL_USERNAME</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">dbusername</span><span class="o">=</span><span class="s2">"</span><span class="nv">$MYSQL_USERNAME</span><span class="s2">"</span>
<span class="k">fi
if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$MYSQL_PASSWORD</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">dbpassword</span><span class="o">=</span><span class="s2">"</span><span class="nv">$MYSQL_PASSWORD</span><span class="s2">"</span>
<span class="k">fi
</span><span class="nb">echo</span> <span class="s2">"GRANT ALL ON </span><span class="se">\`</span><span class="nv">$dbname</span><span class="se">\`</span><span class="s2">.* to '"</span><span class="nv">$dbusername</span><span class="s2">"'@'localhost' IDENTIFIED BY '"</span><span class="nv">$dbpassword</span><span class="s2">"'"</span> | mysql <span class="nt">-uroot</span>
<span class="nb">echo</span> <span class="s2">"CREATE DATABASE IF NOT EXISTS </span><span class="se">\`</span><span class="nv">$dbname</span><span class="se">\`</span><span class="s2">"</span> | mysql <span class="nt">-uroot</span>
<span class="nb">exec</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div></div>
<p>Seperti bisa dibaca sendiri, script ini membuat database dengan nama <code class="language-plaintext highlighter-rouge">belajar</code>, kemudian membuat user yang bisa mengakses database tersebut.</p>
<p>Keseluruhan file (<code class="language-plaintext highlighter-rouge">Dockerfile</code> dan <code class="language-plaintext highlighter-rouge">setup-database.sh</code>) disimpan dalam satu folder. Kita namakan saja <code class="language-plaintext highlighter-rouge">maven-builder</code>.</p>
<p>Untuk menjalankannya, kita masuk dulu ke folder tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd maven-builder
</code></pre></div></div>
<p>Kemudian build dan start containernya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build -t maven-builder .
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Sending build context to Docker daemon 3.072 kB
Step 1 : FROM maven:latest
latest: Pulling from library/maven
5c90d4a2d1a8: Already exists
ab30c63719b1: Already exists
c6072700a242: Pull complete
5f444d070427: Pull complete
620b5227cf38: Pull complete
3cfd33220efa: Pull complete
864a98a84dd2: Extracting 41.22 MB/130 MB
734cc28150de: Download complete
2503296da541: Download complete
Digest: sha256:e8b86eefc2a429f063b5ef3175984cbaa7dbccaa2978205f9a4123ccee93cac5
Status: Downloaded newer image for maven:latest
---> 2c43f939de6e
Step 2 : ENV DEBIAN_FRONTEND noninteractive
---> Running in 854cb3c87389
---> 25973e7ec664
Removing intermediate container 854cb3c87389
Step 3 : RUN apt-get update && apt-get install -y mysql-server && apt-get clean && rm -rf /var/lib/apt/lists/*
---> Running in d523fb0cbb9a
Get:1 http://security.debian.org jessie/updates InRelease [63.1 kB]
Ign http://httpredir.debian.org jessie InRelease
Get:2 http://httpredir.debian.org jessie-backports InRelease [166 kB]
Get:3 http://security.debian.org jessie/updates/main amd64 Packages [368 kB]
Get:4 http://httpredir.debian.org jessie-updates InRelease [142 kB]
Get:5 http://httpredir.debian.org jessie Release.gpg [2373 B]
Get:6 http://httpredir.debian.org jessie-backports/main amd64 Packages [815 kB]
Get:7 http://httpredir.debian.org jessie Release [148 kB]
Get:8 http://httpredir.debian.org jessie-updates/main amd64 Packages [17.6 kB]
Get:9 http://httpredir.debian.org jessie/main amd64 Packages [9032 kB]
Fetched 10.8 MB in 7s (1442 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
libaio1 libdbd-mysql-perl libdbi-perl libhtml-template-perl libmysqlclient18
libterm-readkey-perl mysql-client-5.5 mysql-common mysql-server-5.5
mysql-server-core-5.5
Suggested packages:
libclone-perl libmldbm-perl libnet-daemon-perl libsql-statement-perl
libipc-sharedcache-perl mailx tinyca
The following NEW packages will be installed:
libaio1 libdbd-mysql-perl libdbi-perl libhtml-template-perl libmysqlclient18
libterm-readkey-perl mysql-client-5.5 mysql-common mysql-server
mysql-server-5.5 mysql-server-core-5.5
0 upgraded, 11 newly installed, 0 to remove and 1 not upgraded.
Need to get 8718 kB of archives.
After this operation, 96.3 MB of additional disk space will be used.
Get:1 http://security.debian.org/ jessie/updates/main mysql-common all 5.5.50-0+deb8u1 [81.8 kB]
Get:2 http://security.debian.org/ jessie/updates/main libmysqlclient18 amd64 5.5.50-0+deb8u1 [675 kB]
Get:3 http://security.debian.org/ jessie/updates/main mysql-client-5.5 amd64 5.5.50-0+deb8u1 [1659 kB]
Get:4 http://security.debian.org/ jessie/updates/main mysql-server-core-5.5 amd64 5.5.50-0+deb8u1 [3414 kB]
Get:5 http://security.debian.org/ jessie/updates/main mysql-server-5.5 amd64 5.5.50-0+deb8u1 [1769 kB]
Get:6 http://security.debian.org/ jessie/updates/main mysql-server all 5.5.50-0+deb8u1 [80.0 kB]
Get:7 http://httpredir.debian.org/debian/ jessie/main libaio1 amd64 0.3.110-1 [9312 B]
Get:8 http://httpredir.debian.org/debian/ jessie/main libdbi-perl amd64 1.631-3+b1 [816 kB]
Get:9 http://httpredir.debian.org/debian/ jessie/main libterm-readkey-perl amd64 2.32-1+b1 [28.0 kB]
Get:10 http://httpredir.debian.org/debian/ jessie/main libhtml-template-perl all 2.95-1 [66.8 kB]
Get:11 http://httpredir.debian.org/debian/ jessie/main libdbd-mysql-perl amd64 4.028-2+b1 [119 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 8718 kB in 2s (3440 kB/s)
Selecting previously unselected package libaio1:amd64.
(Reading database ... 17556 files and directories currently installed.)
Preparing to unpack .../libaio1_0.3.110-1_amd64.deb ...
Unpacking libaio1:amd64 (0.3.110-1) ...
Selecting previously unselected package mysql-common.
Preparing to unpack .../mysql-common_5.5.50-0+deb8u1_all.deb ...
Unpacking mysql-common (5.5.50-0+deb8u1) ...
Selecting previously unselected package libmysqlclient18:amd64.
Preparing to unpack .../libmysqlclient18_5.5.50-0+deb8u1_amd64.deb ...
Unpacking libmysqlclient18:amd64 (5.5.50-0+deb8u1) ...
Selecting previously unselected package libdbi-perl.
Preparing to unpack .../libdbi-perl_1.631-3+b1_amd64.deb ...
Unpacking libdbi-perl (1.631-3+b1) ...
Selecting previously unselected package libdbd-mysql-perl.
Preparing to unpack .../libdbd-mysql-perl_4.028-2+b1_amd64.deb ...
Unpacking libdbd-mysql-perl (4.028-2+b1) ...
Selecting previously unselected package libterm-readkey-perl.
Preparing to unpack .../libterm-readkey-perl_2.32-1+b1_amd64.deb ...
Unpacking libterm-readkey-perl (2.32-1+b1) ...
Selecting previously unselected package mysql-client-5.5.
Preparing to unpack .../mysql-client-5.5_5.5.50-0+deb8u1_amd64.deb ...
Unpacking mysql-client-5.5 (5.5.50-0+deb8u1) ...
Selecting previously unselected package mysql-server-core-5.5.
Preparing to unpack .../mysql-server-core-5.5_5.5.50-0+deb8u1_amd64.deb ...
Unpacking mysql-server-core-5.5 (5.5.50-0+deb8u1) ...
Setting up mysql-common (5.5.50-0+deb8u1) ...
Selecting previously unselected package mysql-server-5.5.
(Reading database ... 17924 files and directories currently installed.)
Preparing to unpack .../mysql-server-5.5_5.5.50-0+deb8u1_amd64.deb ...
Unpacking mysql-server-5.5 (5.5.50-0+deb8u1) ...
Selecting previously unselected package libhtml-template-perl.
Preparing to unpack .../libhtml-template-perl_2.95-1_all.deb ...
Unpacking libhtml-template-perl (2.95-1) ...
Selecting previously unselected package mysql-server.
Preparing to unpack .../mysql-server_5.5.50-0+deb8u1_all.deb ...
Unpacking mysql-server (5.5.50-0+deb8u1) ...
Processing triggers for systemd (215-17+deb8u4) ...
Setting up libaio1:amd64 (0.3.110-1) ...
Setting up libmysqlclient18:amd64 (5.5.50-0+deb8u1) ...
Setting up libdbi-perl (1.631-3+b1) ...
Setting up libdbd-mysql-perl (4.028-2+b1) ...
Setting up libterm-readkey-perl (2.32-1+b1) ...
Setting up mysql-client-5.5 (5.5.50-0+deb8u1) ...
Setting up mysql-server-core-5.5 (5.5.50-0+deb8u1) ...
Setting up mysql-server-5.5 (5.5.50-0+deb8u1) ...
invoke-rc.d: policy-rc.d denied execution of stop.
invoke-rc.d: policy-rc.d denied execution of start.
Setting up libhtml-template-perl (2.95-1) ...
Setting up mysql-server (5.5.50-0+deb8u1) ...
Processing triggers for libc-bin (2.19-18+deb8u4) ...
Processing triggers for systemd (215-17+deb8u4) ...
---> 79ea513295b6
Removing intermediate container d523fb0cbb9a
Step 4 : VOLUME ~/.m2 /var/lib/mysql /tmp
---> Running in 1d5412b43eab
---> 22876399ea7e
Removing intermediate container 1d5412b43eab
Step 5 : COPY setup-database.sh /setup-database.sh
---> 7f1c3ab644de
Removing intermediate container c6ce1f913ef5
Step 6 : ENTRYPOINT /setup-database.sh
---> Running in 90f8ddf3b584
---> b719a20423fe
Removing intermediate container 90f8ddf3b584
Step 7 : CMD /bin/bash
---> Running in a84f2ccda6a5
---> 73c465f72d48
Removing intermediate container a84f2ccda6a5
Successfully built 73c465f72d48
</code></pre></div></div>
<p>Seperti kita bisa lihat pada output di atas, docker melakukan seperti apa yang biasa kita lakukan, yaitu <code class="language-plaintext highlighter-rouge">apt-get install</code> paket-paket yang dibutuhkan, sesuai yang kita tulis dalam <code class="language-plaintext highlighter-rouge">Dockerfile</code>.</p>
<p>Setelah selesai proses build, kita bisa jalankan image tersebut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -it --rm maven-builder
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ ok ] Starting MySQL database server: mysqld ..
[info] Checking for tables which need an upgrade, are corrupt or were
not closed cleanly..
root@c0b5d12aa88d:/#
</code></pre></div></div>
<p>Kita sudah mendapatkan bash prompt. Selanjutnya kita bisa clone project aplikasi kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/endymuhardin/belajar-ci.git
Cloning into 'belajar-ci'...
remote: Counting objects: 201, done.
remote: Total 201 (delta 0), reused 0 (delta 0), pack-reused 201
Receiving objects: 100% (201/201), 22.59 KiB | 0 bytes/s, done.
Resolving deltas: 100% (46/46), done.
Checking connectivity... done.
</code></pre></div></div>
<p>Selanjutnya, kita bisa masuk ke foldernya dan menjalankan proses build</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd belajar-ci
mvn clean test jacoco:report
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building belajar-ci 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-clean-plugin/2.5/maven-clean-plugin-2.5.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-clean-plugin/2.5/maven-clean-plugin-2.5.pom (4 KB at 6.8 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/22/maven-plugins-22.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/22/maven-plugins-22.pom (13 KB at 344.1 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/maven-parent/21/maven-parent-21.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/maven-parent/21/maven-parent-21.pom (26 KB at 757.1 KB/sec)
... log proses download saya potong ...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
09:52:52.349 [main] DEBUG o.s.t.c.j.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.muhardin.endy.belajar.BelajarCiApplicationTests]
09:52:52.414 [main] DEBUG o.s.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
09:52:52.493 [main] DEBUG o.s.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
09:52:52.638 [main] DEBUG o.s.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.muhardin.endy.belajar.BelajarCiApplicationTests] from class [org.springframework.test.context.web.WebTestContextBootstrapper]
... log spring saya potong ...
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.3.2.RELEASE)
2016-07-22 09:53:33.805 INFO 659 --- [ main] c.m.e.b.c.c.ProductControllerTests : Starting ProductControllerTests on c0b5d12aa88d with PID 659 (/belajar-ci/target/test-classes started by root in /belajar-ci)
2016-07-22 09:53:33.848 INFO 659 --- [ main] c.m.e.b.c.c.ProductControllerTests : No active profile set, falling back to default profiles: default
2016-07-22 09:53:35.097 INFO 659 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6ba64dcf: startup date [Fri Jul 22 09:53:35 UTC 2016]; root of context hierarchy
... log test dipotong juga ...
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ belajar-ci ---
[INFO] Building jar: /Users/endymuhardin/workspace/belajar/belajar-ci/target/belajar-ci-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.3.2.RELEASE:repackage (default) @ belajar-ci ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.539 s
[INFO] Finished at: 2016-02-22T11:03:02+07:00
[INFO] Final Memory: 29M/219M
</code></pre></div></div>
<p>Kita bisa lihat bahwa outputnya sama persis seperti di artikel sebelumnya. Dengan demikian, kita bisa simpulkan bahwa aplikasi kita berjalan dengan sempurna seperti kalau dijalankan di mesin biasa.</p>
<p>Setelah selesai, kita bisa langsung keluar dari prompt, dan docker instance tadi akan dihapus dari docker-machine.</p>
<h2 id="penutup">Penutup</h2>
<p>Docker adalah teknologi virtualisasi yang sedang naik daun saat ini. Dengan menggunakan docker, kita bisa mempaketkan aplikasi kita menjadi satu image yang lengkap, terdiri dari : sistem operasi, service yang dibutuhkan (seperti database), software pendukung (Java, Ruby, Python), dan tentunya aplikasi kita sendiri.</p>
<p>Image ini selanjutnya bisa kita deploy dengan mudah di berbagai layanan cloud yang disediakan berbagai perusahaan, ataupun kita setup sendiri. Dengan membuat docker image, kita tidak perlu lagi repot-repot menginstal sistem operasi, melakukan konfigurasi, dan ritual rutin lainnya <em>setiap kali</em> mendeploy aplikasi. Cukup kita buat <code class="language-plaintext highlighter-rouge">Dockerfile</code> sekali, selanjutnya tinggal kita deploy berapa kalipun kita mau.</p>
<p>Metode ini sangat sesuai digunakan dengan arsitektur microservice, dimana kita membuat banyak aplikasi kecil-kecil yang saling berkolaborasi. Masing-masing aplikasi bisa direplikasi sesuai load yang diterima tanpa harus mengganggu aplikasi lainnya.</p>
<p>Seperti biasa, semua kode program yang dibahas disini bisa didapatkan <a href="https://github.com/endymuhardin/belajar-docker">di Github</a>.</p>
<p>Selamat mencoba, semoga bermanfaat.</p>
Memahami Mapping Relasi di Hibernate2016-02-25T02:00:00+07:00https://software.endy.muhardin.com/java/memahami-mapping-relasi-hibernate<p>Salah satu permasalahan yang sulit dipahami pada saat belajar Hibernate adalah mapping relasi. Oleh karena itu, pada artikel kali ini, kita akan membahas berbagai konsep relasi database, bagaimana cara mappingnya, dan apa konsekuensinya.</p>
<!--more-->
<p>Sebelum masuk ke mapping relasi, terlebih dulu kita pahami masalah relasi <em>aggregation</em> dan <em>composition</em>. Kedua relasi ini skema databasenya sama, tapi berbeda perlakuan dalam kode programnya.</p>
<h2 id="aggregation-vs-composition">Aggregation vs Composition</h2>
<p>Relasi <em>aggregation</em> artinya mengumpulkan atau mengelompokkan beberapa benda menjadi satu. Misalnya <code class="language-plaintext highlighter-rouge">Mahasiswa</code> mengambil beberapa <code class="language-plaintext highlighter-rouge">MataKuliah</code> dalam satu semester. Atau <code class="language-plaintext highlighter-rouge">Karyawan</code> tergabung dalam satu <code class="language-plaintext highlighter-rouge">Divisi</code> atau <code class="language-plaintext highlighter-rouge">Departemen</code> dalam perusahaan. Karakteristik utama <em>aggregation</em> adalah masing-masing benda tersebut bisa berdiri sendiri. Artinya, walaupun tidak tergabung dalam <code class="language-plaintext highlighter-rouge">Divisi</code> manapun, objek <code class="language-plaintext highlighter-rouge">Karyawan</code> tetap ada dalam sistem. Demikian juga walaupun tidak ada <code class="language-plaintext highlighter-rouge">Karyawan</code> dalam <code class="language-plaintext highlighter-rouge">Divisi</code> tertentu, datanya tetap ada dalam sistem.</p>
<p>Dengan demikian, siklus hidup objek yang tergabung dalam relasi aggregation tidak saling mempengaruhi. Kita bisa menghapus salah satu <code class="language-plaintext highlighter-rouge">Mahasiswa</code> tanpa mempengaruhi <code class="language-plaintext highlighter-rouge">MataKuliah</code>. Demikian juga sebaliknya, kita bisa menghapus <code class="language-plaintext highlighter-rouge">MataKuliah</code> tertentu, sedangkan data <code class="language-plaintext highlighter-rouge">Mahasiswa</code> tetap ada.</p>
<p>Dalam notasi UML, relasi <em>aggregation</em> ini ditulis dengan belah ketupat berwarna putih. Dalam desain database, relasi <em>aggregation</em> ini disebut juga dengan istilah <em>non identifying relationship</em>.</p>
<p><img src="https://lh3.googleusercontent.com/iva3mLlegQrfrGFoB7rEkYe81WqxNg4Okf5AR3wkwWdmxd-MSw1OOHN5mKh9UpckZaN9X_HZYQsD" alt="Aggregation" /></p>
<p>Relasi <em>composition</em> artinya gabungan beberapa benda menjadi satu benda utuh. Misalnya, satu <code class="language-plaintext highlighter-rouge">Transaksi</code> di minimarket terdiri dari banyak <code class="language-plaintext highlighter-rouge">TransaksiDetail</code> yang masing-masingnya terdiri dari satu jenis <code class="language-plaintext highlighter-rouge">Produk</code> dengan jumlah yang berbeda-beda. Atau suatu <code class="language-plaintext highlighter-rouge">Berita</code> memiliki banyak <code class="language-plaintext highlighter-rouge">Komentar</code>. Tanpa komponen pendukungnya, maka benda induknya tidak bermakna. <code class="language-plaintext highlighter-rouge">Komentar</code> tanpa <code class="language-plaintext highlighter-rouge">Berita</code> tidak bermakna, apanya yang mau dikomentari?. Demikian juga <code class="language-plaintext highlighter-rouge">Transaksi</code> tanpa <code class="language-plaintext highlighter-rouge">Produk</code> yang dibeli, tidak ada maknanya.</p>
<p>Dengan demikian, siklus hidup anggota relasi komposisi saling berkaitan. Bila kita menghapus data <code class="language-plaintext highlighter-rouge">Berita</code> tertentu, maka seluruh <code class="language-plaintext highlighter-rouge">Berita</code>nya harus ikut dihapus, karena dia tidak bermakna tanpa induknya. Demikian juga sebaliknya, bila kita menghapus data <code class="language-plaintext highlighter-rouge">TransaksiDetail</code>, maka data <code class="language-plaintext highlighter-rouge">Transaksi</code> juga menjadi invalid.</p>
<p>Relasi <em>composition</em> ini di notasi UML dinyatakan dengan belah ketupat berwarna hitam. Dalam desain database, relasi <em>composition</em> ini disebut juga dengan istilah <em>identifying relationship</em>.</p>
<p><img src="https://lh3.googleusercontent.com/5CTPUEeUz4a0OqoMm3OBWO-WnY_bj3eohpAzKTUlenY8uP2YkTFb6d1YgmkttLGRqL5AqLZ1NDRe" alt="Composition" /></p>
<p>Secara relasi database, umumnya orang tidak membedakan skema antara hubungan <em>aggregation</em> dan <em>composition</em>. Skema antara <code class="language-plaintext highlighter-rouge">Departemen</code> dan <code class="language-plaintext highlighter-rouge">Karyawan</code> bisa dibuat seperti ini</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">departemen</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">kode</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">UNIQUE</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">nama</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span>
<span class="p">);</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">karyawan</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">nik</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">UNIQUE</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">nama</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">id_departemen</span> <span class="nb">INT</span><span class="p">,</span>
<span class="k">CONSTRAINT</span> <span class="n">fk_dept</span> <span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id_departemen</span><span class="p">)</span>
<span class="k">REFERENCES</span> <span class="n">departemen</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Demikian juga untuk <code class="language-plaintext highlighter-rouge">Berita</code> dan <code class="language-plaintext highlighter-rouge">Komentar</code> skemanya mirip-mirip</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">berita</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">waktu_publikasi</span> <span class="nb">DATETIME</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">judul</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">UNIQUE</span><span class="p">,</span>
<span class="n">isi</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span>
<span class="p">);</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">komentar</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">waktu_publikasi</span> <span class="nb">DATETIME</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">email</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">UNIQUE</span><span class="p">,</span>
<span class="n">nama</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">isi</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">id_berita</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">CONSTRAINT</span> <span class="n">fk_berita</span> <span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id_berita</span><span class="p">)</span>
<span class="k">REFERENCES</span> <span class="n">berita</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Di jaman dulu lazim juga orang membedakan desain skema antara <em>non-identifying</em> dan <em>identifying</em> relationship. Untuk yang <em>identifying</em> relationship, foreign key dipasang sebagai primary key seperti ini</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">komentar</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">waktu_publikasi</span> <span class="nb">DATETIME</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">email</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">UNIQUE</span><span class="p">,</span>
<span class="n">nama</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">isi</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">id_berita</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="k">CONSTRAINT</span> <span class="n">fk_berita</span> <span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id_berita</span><span class="p">)</span>
<span class="k">REFERENCES</span> <span class="n">berita</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Akan tetapi, karena kerepotan yang ditimbulkan oleh pemakaian <em>composite key</em> (primary key yang terdiri dari beberapa kolom), maka banyak juga orang (termasuk saya) yang tidak menganut pembedaan ini. Jadi skemanya disamakan saja antara <em>non-identifying</em> dan <em>identifying</em>. Paling kita tambahkan constraint <code class="language-plaintext highlighter-rouge">NOT NULL</code> di kolom foreign key untuk relasi <em>identifying</em> dan kita berikan klausa <code class="language-plaintext highlighter-rouge">CASCADE</code>.</p>
<p>Penjelasan lebih lengkap bisa dibaca <a href="http://stackoverflow.com/a/762994">di sini</a> dan <a href="http://stackoverflow.com/a/2814663">di sini</a></p>
<p>Sengaja tidak saya buatkan diagram, supaya pembaca berlatih membayangkan skema dari kode program :D
Kemampuan membayangkan struktur dari kode ini sangat penting bagi seorang programmer dan hanya bisa didapatkan dari sering berlatih.</p>
<blockquote>
<p>Lalu bagaimana cara kita mapping relasi tadi di Hibernate?</p>
</blockquote>
<h2 id="hibernate-mapping">Hibernate Mapping</h2>
<p>Ada perbedaan mendasar antara relasi di ORM (baik itu Hibernate di Java, ActiveRecord di Rails, ataupun Eloquent di Laravel) dengan relasi di database, yaitu masalah arah (direction).</p>
<p>Relasi di database tidak mengenal arah, sedangkan di ORM mengenal arah.</p>
<blockquote>
<p>Apa maksudnya?</p>
</blockquote>
<p>Kita ilustrasikan dengan kode program Java, tapi prinsip yang sama juga berlaku dalam Rails (Ruby) dan Laravel (PHP).</p>
<p>Berikut adalah definisi class tanpa relasi untuk relasi di atas.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span> <span class="nd">@Table</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"berita"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Berita</span> <span class="o">{</span>
<span class="nd">@Id</span> <span class="nd">@GeneratedValue</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"waktu_publikasi"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="nd">@Temporal</span><span class="o">(</span><span class="nc">TemporalType</span><span class="o">.</span><span class="na">TIMESTAMP</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">waktuPublikasi</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">judul</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">isi</span><span class="o">;</span>
<span class="c1">// getter setter tidak ditampilkan</span>
<span class="o">}</span>
<span class="nd">@Entity</span> <span class="nd">@Table</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"komentar"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Komentar</span> <span class="o">{</span>
<span class="nd">@Id</span> <span class="nd">@GeneratedValue</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"waktu_publikasi"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="nd">@Temporal</span><span class="o">(</span><span class="nc">TemporalType</span><span class="o">.</span><span class="na">TIMESTAMP</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">waktuPublikasi</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">email</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">nama</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">isi</span><span class="o">;</span>
<span class="c1">// getter setter tidak ditampilkan</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Untuk mendefinisikan relasinya, pertama kita harus menentukan dulu di sisi mana kita mau membuat relasi. Apakah arahnya dari <code class="language-plaintext highlighter-rouge">Berita</code> ke <code class="language-plaintext highlighter-rouge">Komentar</code> atau sebaliknya?</p>
<p>Yang paling umum adalah kita definisikan di sisi Komentar, seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Berita</span> <span class="o">{</span>
<span class="c1">// tidak ada perubahan, sama seperti sebelumnya</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Komentar</span> <span class="o">{</span>
<span class="c1">// kode program sebelumnya yang tidak berubah tidak ditulis lagi</span>
<span class="nd">@ManyToOne</span>
<span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_berita"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Berita</span> <span class="n">berita</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dengan demikian, kita bisa mengakses objek <code class="language-plaintext highlighter-rouge">Berita</code> dari sisi <code class="language-plaintext highlighter-rouge">Komentar</code> seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">ks</span> <span class="o">=</span> <span class="n">cariKomentarBerdasarkanEmail</span><span class="o">(</span><span class="s">"endy@muhardin.com"</span><span class="o">);</span>
<span class="c1">// tampilkan judul berita</span>
<span class="k">for</span><span class="o">(</span><span class="nc">Komentar</span> <span class="n">k</span> <span class="o">:</span> <span class="n">ks</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Judul Berita : "</span><span class="o">+</span> <span class="n">k</span><span class="o">.</span><span class="na">getBerita</span><span class="o">().</span><span class="na">getJudul</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Tapi sebaliknya tidak bisa. Kita tidak bisa menampilkan <code class="language-plaintext highlighter-rouge">Komentar</code> untuk <code class="language-plaintext highlighter-rouge">Berita</code> tertentu, karena variabelnya tidak ada di sisi <code class="language-plaintext highlighter-rouge">Berita</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Berita</span> <span class="n">b</span> <span class="o">=</span> <span class="n">cariBeritaBerdasarkanJudul</span><span class="o">(</span><span class="s">"Tol Brexit"</span><span class="o">);</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">ks</span> <span class="o">=</span> <span class="n">b</span><span class="o">.</span><span class="na">getDaftarKomentar</span><span class="o">();</span> <span class="c1">// error, tidak ada variabel daftarKomentar di class Berita</span>
</code></pre></div></div>
<p>Fenomena ini disebut dengan istilah arah atau <code class="language-plaintext highlighter-rouge">direction</code> dalam bahasa Inggris. Relasi satu arah (dari <code class="language-plaintext highlighter-rouge">Komentar</code> ke <code class="language-plaintext highlighter-rouge">Berita</code>) disebut unidirectional. Kita bisa membuat relasi satu arah di sisi mana saja tanpa mempengaruhi skema database, karena database tidak mengenal konsep <code class="language-plaintext highlighter-rouge">direction</code>.</p>
<p>Mari kita pindahkan relasinya ke sisi <code class="language-plaintext highlighter-rouge">Berita</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Berita</span> <span class="o">{</span>
<span class="c1">// kode program lainnya sama, tidak ditulis lagi</span>
<span class="nd">@OneToMany</span>
<span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_berita"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">daftarKomentar</span>
<span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Komentar</span> <span class="o">{</span>
<span class="c1">// tidak ada perubahan, sama seperti yang tanpa relasi</span>
<span class="c1">// tidak ada juga relasi @ManyToOne</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Bila mappingnya seperti itu, maka kita bisa menampilkan komentar untuk suatu berita seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Berita</span> <span class="n">b</span> <span class="o">=</span> <span class="n">cariBeritaBerdasarkanJudul</span><span class="o">(</span><span class="s">"Tol Brexit"</span><span class="o">);</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">ks</span> <span class="o">=</span> <span class="n">b</span><span class="o">.</span><span class="na">getDaftarKomentar</span><span class="o">();</span> <span class="c1">// ok, karena ada variabel daftarKomentar dalam class Berita</span>
</code></pre></div></div>
<p>Tapi tidak bisa menampilkan <code class="language-plaintext highlighter-rouge">Berita</code> untuk <code class="language-plaintext highlighter-rouge">Komentar</code> tertentu</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">ks</span> <span class="o">=</span> <span class="n">cariKomentarBerdasarkanEmail</span><span class="o">(</span><span class="s">"endy@muhardin.com"</span><span class="o">);</span>
<span class="c1">// tampilkan judul berita</span>
<span class="k">for</span><span class="o">(</span><span class="nc">Komentar</span> <span class="n">k</span> <span class="o">:</span> <span class="n">ks</span><span class="o">){</span>
<span class="c1">// error, karena tidak ada variabel berita dalam class Komentar</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Judul Berita : "</span><span class="o">+</span> <span class="n">k</span><span class="o">.</span><span class="na">getBerita</span><span class="o">().</span><span class="na">getJudul</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<blockquote>
<p>Wah bagaimana dong, saya mau bisa dua-duanya?</p>
</blockquote>
<p>Dasar manusia, tidak ada puasnya :P
Tapi no problem, kita bisa bikin relasi dua arah (bidirectional) di sisi <code class="language-plaintext highlighter-rouge">Berita</code> dan <code class="language-plaintext highlighter-rouge">Komentar</code>. Berikut saya tampilkan mapping yang <em>salah</em> dulu</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Berita</span> <span class="o">{</span>
<span class="c1">// kode program lainnya sama, tidak ditulis lagi</span>
<span class="nd">@OneToMany</span>
<span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_berita"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">daftarKomentar</span>
<span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Komentar</span> <span class="o">{</span>
<span class="c1">// kode program sebelumnya yang tidak berubah tidak ditulis lagi</span>
<span class="nd">@ManyToOne</span>
<span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_berita"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Berita</span> <span class="n">berita</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kita tinggal copas saja deklarasi mapping relasi dari kedua contoh di atas. Dengan demikian kita punya mapping dua arah.</p>
<blockquote>
<p>Lalu kenapa dibilang salah?</p>
</blockquote>
<p>Di jaman dulu, mapping seperti ini dibiarkan sama Hibernate, tapi nanti kacau pas dijalankan. Dia akan menjalankan query duplikat pada waktu kita insert/update relasi. Tapi di jaman sekarang, kesalahan mapping seperti ini akan diperingatkan pada waktu aplikasi dijalankan. Berikut pesan errornya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: com.muhardin.endy.belajar.hibernate.mapping.entity.Komentar column: id_berita (should be mapped with insert="false" update="false")
</code></pre></div></div>
<p>Karena error ini, saya jadi tidak bisa mendemokan apa yang terjadi kalau kita salah mapping seperti ini. Sebetulnya bisa dengan menggunakan Hibernate versi jadul, tapi terlalu ribet. Jadi ya bersyukur saja sudah langsung dicegat sehingga bisa langsung kita perbaiki.</p>
<p>Dia bilang ada mapping yang diulang pada kolom <code class="language-plaintext highlighter-rouge">id_berita</code>.</p>
<blockquote>
<p>Kenapa demikian?</p>
</blockquote>
<p>Karena relasi dua arah ini (Berita -> Komentar dan Komentar -> Berita) sebetulnya mengacu pada satu relasi database yang sama, yaitu relasi foreign key <code class="language-plaintext highlighter-rouge">id_berita</code> di tabel <code class="language-plaintext highlighter-rouge">komentar</code> yang mengarah ke kolom <code class="language-plaintext highlighter-rouge">id</code> di tabel <code class="language-plaintext highlighter-rouge">berita</code>. Karena relasi aslinya di database hanya satu, maka kedua relasi di Hibernate ini harus memiliki satu penanggung jawab saja.</p>
<p>Ada dua pilihan solusi di sini, apakah penanggung jawabnya ada di sisi <code class="language-plaintext highlighter-rouge">Berita</code> atau di sisi <code class="language-plaintext highlighter-rouge">Komentar</code>. Biasanya, penanggung jawab ada di sisi many, karena di database foreign keynya ada di tabel many, yaitu tabel <code class="language-plaintext highlighter-rouge">komentar</code>. Dengan demikian, deklarasi mapping yang lebih lengkap harusnya ada di dalam class <code class="language-plaintext highlighter-rouge">Komentar</code>. Sedangkan di class <code class="language-plaintext highlighter-rouge">Berita</code> cukup dinyatakan bahwa mappingnya ada di sisi seberang. Ini dilakukan menggunakan modifier <code class="language-plaintext highlighter-rouge">mappedBy</code>. Isinya adalah nama variabel/properti class <code class="language-plaintext highlighter-rouge">Berita</code> di dalam class <code class="language-plaintext highlighter-rouge">Komentar</code>. Ingat ya, <em>nama variabel dalam Java</em>, bukan <em>nama kolom dalam database</em> !!!</p>
<p>Mapping yang benar adalah seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Berita</span> <span class="o">{</span>
<span class="c1">// kode program lainnya sama, tidak ditulis lagi</span>
<span class="nd">@OneToMany</span><span class="o">(</span><span class="n">mappedBy</span><span class="o">=</span><span class="s">"berita"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">daftarKomentar</span>
<span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Komentar</span> <span class="o">{</span>
<span class="c1">// kode program sebelumnya yang tidak berubah tidak ditulis lagi</span>
<span class="nd">@ManyToOne</span>
<span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_berita"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Berita</span> <span class="n">berita</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Nah, setelah diperbaiki, kita bisa menjalankan aplikasinya dengan lancar. Berikut kode program untuk menyimpan data ke database</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Berita</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Berita</span><span class="o">();</span>
<span class="n">b</span><span class="o">.</span><span class="na">setJudul</span><span class="o">(</span><span class="s">"Tol Brexit"</span><span class="o">);</span>
<span class="n">b</span><span class="o">.</span><span class="na">setIsi</span><span class="o">(</span><span class="s">"Tol brexit macet parah"</span><span class="o">);</span>
<span class="n">b</span><span class="o">.</span><span class="na">setWaktuPublikasi</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
<span class="nc">Komentar</span> <span class="n">k</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Komentar</span><span class="o">();</span>
<span class="n">k</span><span class="o">.</span><span class="na">setEmail</span><span class="o">(</span><span class="s">"endy@muhardin.com"</span><span class="o">);</span>
<span class="n">k</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Endy Muhardin"</span><span class="o">);</span>
<span class="n">k</span><span class="o">.</span><span class="na">setIsi</span><span class="o">(</span><span class="s">"Wih, ngeri gan"</span><span class="o">);</span>
<span class="n">k</span><span class="o">.</span><span class="na">setWaktuPublikasi</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
<span class="n">k</span><span class="o">.</span><span class="na">setBerita</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>
<span class="n">b</span><span class="o">.</span><span class="na">getDaftarKomentar</span><span class="o">().</span><span class="na">add</span><span class="o">(</span><span class="n">k</span><span class="o">);</span>
<span class="nc">BeritaDao</span> <span class="n">bd</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="nc">BeritaDao</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">bd</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>
</code></pre></div></div>
<p>Dia akan menghasilkan query seperti ini</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">berita</span> <span class="p">(</span><span class="n">isi</span><span class="p">,</span> <span class="n">judul</span><span class="p">,</span> <span class="n">waktu_publikasi</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">)</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">komentar</span> <span class="p">(</span><span class="n">id_berita</span><span class="p">,</span> <span class="n">email</span><span class="p">,</span> <span class="n">isi</span><span class="p">,</span> <span class="n">nama</span><span class="p">,</span> <span class="n">waktu_publikasi</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">)</span>
</code></pre></div></div>
<blockquote>
<p>Bagaimana kalau kita mau penanggung jawabnya di sisi one?</p>
</blockquote>
<p>Berikut mappingnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Berita</span> <span class="o">{</span>
<span class="c1">// kode program lainnya sama, tidak ditulis lagi</span>
<span class="nd">@OneToMany</span>
<span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_berita"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">daftarKomentar</span>
<span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Komentar</span> <span class="o">{</span>
<span class="c1">// kode program sebelumnya yang tidak berubah tidak ditulis lagi</span>
<span class="nd">@ManyToOne</span>
<span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"id_berita"</span><span class="o">,</span> <span class="n">nullable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">,</span> <span class="n">insertable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">,</span> <span class="n">updatable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Berita</span> <span class="n">berita</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Walaupun demikian, mapping seperti ini tidak dianjurkan. Kita bisa lihat bahwa dengan mapping seperti ini, querynya kurang optimal. Ada 3 query yang dijalankan untuk menyimpan data ke database</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">berita</span> <span class="p">(</span><span class="n">isi</span><span class="p">,</span> <span class="n">judul</span><span class="p">,</span> <span class="n">waktu_publikasi</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">)</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">komentar</span> <span class="p">(</span><span class="n">email</span><span class="p">,</span> <span class="n">isi</span><span class="p">,</span> <span class="n">nama</span><span class="p">,</span> <span class="n">waktu_publikasi</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">)</span>
<span class="k">update</span> <span class="n">komentar</span> <span class="k">set</span> <span class="n">id_berita</span><span class="o">=?</span> <span class="k">where</span> <span class="n">id</span><span class="o">=?</span>
</code></pre></div></div>
<p>Bila kita ingin penanggung jawabnya ada di sisi <code class="language-plaintext highlighter-rouge">Berita</code>, biasanya disebabkan karena <code class="language-plaintext highlighter-rouge">Komentar</code> sifatnya opsional, bisa ada dan bisa tidak ada. Untuk itu akan lebih ideal kalau kita membuat tabel <em>bridging</em> atau join table. Skemanya menjadi seperti ini</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">berita</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">waktu_publikasi</span> <span class="nb">DATETIME</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">judul</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">UNIQUE</span><span class="p">,</span>
<span class="n">isi</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span>
<span class="p">);</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">komentar</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">waktu_publikasi</span> <span class="nb">DATETIME</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">email</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">UNIQUE</span><span class="p">,</span>
<span class="n">nama</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">isi</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span>
<span class="p">);</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">komentar_berita</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">id_berita</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">id_komentar</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">CONSTRAINT</span> <span class="n">fk_berita</span> <span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id_berita</span><span class="p">)</span>
<span class="k">REFERENCES</span> <span class="n">berita</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="k">CONSTRAINT</span> <span class="n">fk_komentar</span> <span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id_komentar</span><span class="p">)</span>
<span class="k">REFERENCES</span> <span class="n">komentar</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Dan mappingnya menjadi seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Berita</span> <span class="o">{</span>
<span class="c1">// kode program lainnya sama, tidak ditulis lagi</span>
<span class="nd">@OneToMany</span>
<span class="nd">@JoinTable</span><span class="o">(</span>
<span class="n">name</span><span class="o">=</span><span class="s">"komentar_berita"</span><span class="o">,</span>
<span class="n">joinColumns</span><span class="o">=</span><span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_berita"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">),</span>
<span class="n">inverseJoinColumns</span><span class="o">=</span><span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_komentar"</span><span class="o">,</span> <span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="o">)</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Komentar</span><span class="o">></span> <span class="n">daftarKomentar</span>
<span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Komentar</span> <span class="o">{</span>
<span class="c1">// kode program sebelumnya yang tidak berubah tidak ditulis lagi</span>
<span class="nd">@ManyToOne</span>
<span class="nd">@JoinTable</span><span class="o">(</span>
<span class="n">name</span> <span class="o">=</span> <span class="s">"komentar_berita"</span><span class="o">,</span>
<span class="n">joinColumns</span> <span class="o">=</span> <span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"id_komentar"</span><span class="o">,</span> <span class="n">insertable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">,</span> <span class="n">updatable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">),</span>
<span class="n">inverseJoinColumns</span> <span class="o">=</span> <span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"id_berita"</span><span class="o">,</span> <span class="n">insertable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">,</span> <span class="n">updatable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="o">)</span>
<span class="kd">private</span> <span class="nc">Berita</span> <span class="n">berita</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Ada sedikit perbedaan dalam cara menyimpan datanya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Berita</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Berita</span><span class="o">();</span>
<span class="n">b</span><span class="o">.</span><span class="na">setJudul</span><span class="o">(</span><span class="s">"Tol Brexit"</span><span class="o">);</span>
<span class="n">b</span><span class="o">.</span><span class="na">setIsi</span><span class="o">(</span><span class="s">"Tol brexit macet parah"</span><span class="o">);</span>
<span class="n">b</span><span class="o">.</span><span class="na">setWaktuPublikasi</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
<span class="nc">Komentar</span> <span class="n">k</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Komentar</span><span class="o">();</span>
<span class="n">k</span><span class="o">.</span><span class="na">setEmail</span><span class="o">(</span><span class="s">"endy@muhardin.com"</span><span class="o">);</span>
<span class="n">k</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Endy Muhardin"</span><span class="o">);</span>
<span class="n">k</span><span class="o">.</span><span class="na">setIsi</span><span class="o">(</span><span class="s">"Wih, ngeri gan"</span><span class="o">);</span>
<span class="n">k</span><span class="o">.</span><span class="na">setWaktuPublikasi</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
<span class="n">b</span><span class="o">.</span><span class="na">getDaftarKomentar</span><span class="o">().</span><span class="na">add</span><span class="o">(</span><span class="n">k</span><span class="o">);</span>
<span class="nc">BeritaDao</span> <span class="n">bd</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="nc">BeritaDao</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">bd</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>
</code></pre></div></div>
<p>Kita tidak perlu lagi memasangkan <code class="language-plaintext highlighter-rouge">Berita</code> ke <code class="language-plaintext highlighter-rouge">Komentar</code>. Ini akan ditangani secara otomatis oleh Hibernate.</p>
<p>Berikut SQL yang dihasilkan untuk menyimpan data</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">berita</span> <span class="p">(</span><span class="n">isi</span><span class="p">,</span> <span class="n">judul</span><span class="p">,</span> <span class="n">waktu_publikasi</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">)</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">komentar</span> <span class="p">(</span><span class="n">email</span><span class="p">,</span> <span class="n">isi</span><span class="p">,</span> <span class="n">nama</span><span class="p">,</span> <span class="n">waktu_publikasi</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">)</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">komentar_berita</span> <span class="p">(</span><span class="n">id_berita</span><span class="p">,</span> <span class="n">id_komentar</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="mapping-aggregation-vs-composition">Mapping Aggregation vs Composition</h2>
<p>Di atas tadi sudah kita bahas perbedaan aggregation dan composition, yaitu mengenai masalah siklus hidup. Untuk mengimplementasikan relasi aggregation, kita tidak boleh menghapus sisi many apabila sisi one dihapus. Teknisnya, ada beberapa hal yang harus kita lakukan:</p>
<ol>
<li>Penanggung jawabnya harus di sisi many</li>
<li>Jangan gunakan opsi cascade di sisi one</li>
<li>Pada waktu menghapus sisi one, set dulu null di sisi many</li>
</ol>
<p>Sedangkan untuk relasi composition, berlaku sebaliknya. Kita ingin sisi many juga terhapus pada saat kita menghapus sisi one. Untuk itu:</p>
<ol>
<li>Penanggung jawab boleh di sisi one atau many. Bebas saja</li>
<li>Gunakan opsi cascade dan orphanRemoval. <code class="language-plaintext highlighter-rouge">@OneToMany(cascade=Cascade.ALL, orphanRemoval=true)</code>. Orphan removal ini gunanya supaya pada saat kita membuang many dari <code class="language-plaintext highlighter-rouge">List</code>, Hibernate akan menghapusnya dari database pada saat kita <code class="language-plaintext highlighter-rouge">save</code> sisi one.</li>
</ol>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah penjelasan tentang serba serbi mapping relasi pada ORM. Konsep ini berlaku di berbagai produk ORM yang populer seperti ActiveRecord di Ruby on Rails dan Eloquent di Laravel. Kode program yang ada di artikel ini bisa dilihat dan diunduh <a href="https://github.com/endymuhardin/belajar-hibernate-mapping">di Github</a>, dan dicoba sendiri. Silahkan checkout sesuai komentar di commit message untuk mencoba <a href="https://github.com/endymuhardin/belajar-hibernate-mapping/commits/master">berbagai variasinya</a>.</p>
<p>Semoga bermanfaat</p>
Setup Continuous Delivery2016-02-25T02:00:00+07:00https://software.endy.muhardin.com/java/project-bootstrap-04<p>Pada <a href="">artikel sebelumnya</a>, kita telah melakukan deployment ke cloud provider Openshift dan Heroku dari komputer lokal kita. Sekarang, kita akan mengotomasi proses deployment ini dengan Travis, sehingga apabila build berjalan sukses, aplikasi akan otomatis dideploy ke server dan bisa diakses seluruh umat manusia.</p>
<!--more-->
<h2 id="instalasi-travis-tools">Instalasi Travis Tools</h2>
<p>Travis sudah menyediakan tools command line untuk memudahkan proses konfigurasi. Tools ini dibuat berbasis Ruby, sehingga kita harus pastikan dulu ada Ruby yang terinstal. Jalankan perintah berikut untuk memastikan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby -v
</code></pre></div></div>
<p>Berikut outputnya di komputer saya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin15]
</code></pre></div></div>
<p>Selanjutnya, kita install Travis tools.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install travis
Fetching: websocket-1.2.2.gem (100%)
Successfully installed websocket-1.2.2
Fetching: pusher-client-0.6.2.gem (100%)
Successfully installed pusher-client-0.6.2
Fetching: launchy-2.4.3.gem (100%)
Successfully installed launchy-2.4.3
Fetching: highline-1.7.8.gem (100%)
Successfully installed highline-1.7.8
Fetching: net-http-pipeline-1.0.1.gem (100%)
Successfully installed net-http-pipeline-1.0.1
Fetching: net-http-persistent-2.9.4.gem (100%)
Successfully installed net-http-persistent-2.9.4
Fetching: backports-3.6.8.gem (100%)
Successfully installed backports-3.6.8
Fetching: gh-0.14.0.gem (100%)
Successfully installed gh-0.14.0
Fetching: faraday_middleware-0.10.0.gem (100%)
Successfully installed faraday_middleware-0.10.0
Fetching: travis-1.8.2.gem (100%)
Successfully installed travis-1.8.2
Parsing documentation for websocket-1.2.2
Installing ri documentation for websocket-1.2.2
Parsing documentation for pusher-client-0.6.2
Installing ri documentation for pusher-client-0.6.2
Parsing documentation for launchy-2.4.3
Installing ri documentation for launchy-2.4.3
Parsing documentation for highline-1.7.8
Installing ri documentation for highline-1.7.8
Parsing documentation for net-http-pipeline-1.0.1
Installing ri documentation for net-http-pipeline-1.0.1
Parsing documentation for net-http-persistent-2.9.4
Installing ri documentation for net-http-persistent-2.9.4
Parsing documentation for backports-3.6.8
Installing ri documentation for backports-3.6.8
Parsing documentation for gh-0.14.0
Installing ri documentation for gh-0.14.0
Parsing documentation for faraday_middleware-0.10.0
Installing ri documentation for faraday_middleware-0.10.0
Parsing documentation for travis-1.8.2
Installing ri documentation for travis-1.8.2
Done installing documentation for websocket, pusher-client, launchy, highline, net-http-pipeline, net-http-persistent, backports, gh, faraday_middleware, travis after 9 seconds
10 gems installed
</code></pre></div></div>
<h2 id="setup-deployment-travis-ke-openshift">Setup Deployment Travis ke Openshift</h2>
<p>Dengan tools command line <code class="language-plaintext highlighter-rouge">travis</code>, setup deployment sangat mudah sekali. Cukup jalankan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>travis setup openshift
</code></pre></div></div>
<p>Selanjutnya kita akan dipandu untuk mengisi informasi yang dibutuhkan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Shell completion not installed. Would you like to install it now? |y|
Detected repository as endymuhardin/belajar-ci, is this correct? |yes|
OpenShift user: endy.muhardin@gmail.com
OpenShift password: ****************
OpenShift application name: |belajar-ci| belajar
OpenShift domain: endymuhardin
Deploy only from endymuhardin/belajar-ci? |yes|
Encrypt Password? |yes|
</code></pre></div></div>
<p>Setelah semua kita isi, file <code class="language-plaintext highlighter-rouge">.travis.yml</code> akan diupdate sesuai dengan konfigurasi kita. Password Openshift kita akan dienkripsi sehingga tidak terbaca oleh orang lain.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">deploy</span><span class="pi">:</span>
<span class="na">provider</span><span class="pi">:</span> <span class="s">openshift</span>
<span class="na">user</span><span class="pi">:</span> <span class="s">endy.muhardin@gmail.com</span>
<span class="na">password</span><span class="pi">:</span>
<span class="na">secure</span><span class="pi">:</span> <span class="s">yadda-yadda-yadda</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">belajar</span>
<span class="na">domain</span><span class="pi">:</span> <span class="s">endymuhardin</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">repo</span><span class="pi">:</span> <span class="s">endymuhardin/belajar-ci</span>
</code></pre></div></div>
<p>Berikutnya, kita bisa commit dan push ke Github seperti biasa. Travis akan mendeteksinya, melakukan build, dan kemudian mendeploynya ke Openshift.</p>
<h2 id="setup-deployment-travis-ke-heroku">Setup Deployment Travis ke Heroku</h2>
<p>Tidak jauh berbeda dengan Openshift, setup Heroku juga dilakukan melalui tools command line <code class="language-plaintext highlighter-rouge">travis</code>. Jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>travis setup heroku
</code></pre></div></div>
<p>Dan kita akan ditanyai beberapa informasi yang dibutuhkan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Heroku application name: |belajar-ci| aplikasibelajar
Deploy only from endymuhardin/belajar-ci? |yes|
Encrypt API key? |yes|
</code></pre></div></div>
<p>Setelah selesai, file <code class="language-plaintext highlighter-rouge">.travis.yml</code> kita akan diupdate. Berikut tambahannya</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">deploy</span><span class="pi">:</span>
<span class="na">provider</span><span class="pi">:</span> <span class="s">heroku</span>
<span class="na">api_key</span><span class="pi">:</span>
<span class="na">secure</span><span class="pi">:</span> <span class="s">yadda-yadda-yadda</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">aplikasibelajar</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">repo</span><span class="pi">:</span> <span class="s">endymuhardin/belajar-ci</span>
</code></pre></div></div>
<p>Kita tinggal commit dan push, Travis akan mendeploy aplikasi kita ke Heroku.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah rangkaian setup project baru yang fully automated. Dengan setup ini, begitu seorang programmer melakukan push, maka <em>pabrik software</em> akan segera bekerja, melakukan:</p>
<ul>
<li>Download source code terbaru</li>
<li>Melakukan kompilasi</li>
<li>Menjalankan semua test</li>
<li>Memeriksa apakah test coverage memadai</li>
<li>Mendeploy aplikasi ke server</li>
</ul>
<p>Semua kegiatan di atas dilakukan tanpa campur tangan manusia.</p>
<p>Tentunya setup di atas hanya gratis bagi project open source. Untuk project yang closed-source, setupnya akan lebih rumit dan belum tentu gratis. Kita juga bisa menggunakan Travis dan Coveralls, tapi dengan membayar iuran setiap bulannya, ditambah dengan iuran Github juga.</p>
<p>Semoga bermanfaat :D</p>
Setup Deployment ke PaaS2016-02-24T02:00:00+07:00https://software.endy.muhardin.com/java/project-bootstrap-03<p>Pada <a href="http://software.endy.muhardin.com/java/project-bootstrap-02/">artikel sebelumnya</a>, kita telah mengotomasi proses build dari aplikasi kita. Langkah selanjutnya adalah mendeploy aplikasi kita tersebut ke server yang bisa diakses orang banyak, supaya bisa dilakukan testing oleh manusia.</p>
<p>Ada banyak provider server yang bisa digunakan, ada yang berbayar dan ada yang gratisan. Di antara yang gratisan adalah <a href="https://www.openshift.com/">Openshift</a> dan <a href="https://www.heroku.com/">Heroku</a>. Openshift menyediakan tiga aplikasi yang dapat diinstal tanpa bayar. Sedangkan Heroku membatasi aplikasi gratisnya hanya bisa jalan 18 jam sehari.</p>
<p>Paket gratis ini cukup memadai untuk keperluan testing. Sedangkan nanti bila aplikasi kita sudah digunakan di production, kita bisa menggunakan paket berbayar atau sewa server sendiri baik VPS maupun colocation.</p>
<p>Kedua provider ini mendukung deployment menggunakan <code class="language-plaintext highlighter-rouge">git push</code>, jadi kita tidak perlu upload file <code class="language-plaintext highlighter-rouge">jar</code> atau <code class="language-plaintext highlighter-rouge">war</code> berukuran besar. Cukup push saja source code, nanti dia akan melakukan build dan deployment.</p>
<p>Mari kita mulai …</p>
<!--more-->
<h2 id="heroku">Heroku</h2>
<p>Pada dasarnya, deployment ke Heroku tidak jauh berbeda caranya dengan Openshift. Satu-satunya perbedaan yang cukup signifikan adalah paket gratisan Heroku cuma menyediakan database PostgreSQL. Tidak terlalu merepotkan karena kita menggunakan JPA. Tinggal mengubah tiga baris konfigurasi dan script migrasi database saja.</p>
<h3 id="struktur-folder-aplikasi">Struktur Folder Aplikasi</h3>
<p>Kita kerjakan dulu script migrasi database. Struktur folder kita yang asli hanyak mengakomodasi satu jenis database. Karena kita ingin ada dua script yang berbeda, kita perlu mengubah foldernya menjadi seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/KuXfp03PAiMLbH5616hUTa0XC9ljmjV0Sq5tsgFyGoh_IgdR76SC2K27HqVpMtDCfFX-eIsqiK0a=w1280-no"><img src="https://lh3.googleusercontent.com/KuXfp03PAiMLbH5616hUTa0XC9ljmjV0Sq5tsgFyGoh_IgdR76SC2K27HqVpMtDCfFX-eIsqiK0a=w1280-no" alt="Struktur Folder Migrasi" /></a></p>
<p>Karena perubahan tersebut, kita harus memberi tahu Flyway di mana harus mencari scriptnya. Tambahkan konfigurasi berikut pada file <code class="language-plaintext highlighter-rouge">src/main/resources/application.properties</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flyway.locations=classpath:db/migration/mysql
</code></pre></div></div>
<p>Dengan demikian, lokasi default script ada di folder <code class="language-plaintext highlighter-rouge">src/main/resources/db/migration/mysql</code>.</p>
<h3 id="membuat-aplikasi-heroku">Membuat Aplikasi Heroku</h3>
<p>Selanjutnya, kita buat aplikasi di Heroku. Login dulu ke Heroku sehingga dapat membuka management console seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/vJIL0M5lHm_zXw9wBqhHFURVRs_aS92ZhL0Hr3yHiYRiRjDqHPNPp0FyG5k2Fhiojf7N5Mx6hvrt=w1280-no"><img src="https://lh3.googleusercontent.com/vJIL0M5lHm_zXw9wBqhHFURVRs_aS92ZhL0Hr3yHiYRiRjDqHPNPp0FyG5k2Fhiojf7N5Mx6hvrt=w1280-no" alt="Heroku Dashboard" /></a></p>
<p>Klik <code class="language-plaintext highlighter-rouge">New App</code> di pojok kanan atas untuk membuat aplikasi baru</p>
<p><a href="https://lh3.googleusercontent.com/7S3Ou3h7xF44D52vWwCzZAxS8L5FOfkc9YR5IcTmVIkZADmxozhd8VNsXa3yC3ITRnA3vZrWAWdw=w1280-no"><img src="https://lh3.googleusercontent.com/7S3Ou3h7xF44D52vWwCzZAxS8L5FOfkc9YR5IcTmVIkZADmxozhd8VNsXa3yC3ITRnA3vZrWAWdw=w1280-no" alt="New App" /></a></p>
<p>Isikan nama aplikasi yang ingin kita buat, misalya <code class="language-plaintext highlighter-rouge">aplikasibelajar</code>. Setelah klik OK, kita akan mendapati halaman administrasi aplikasi kita.</p>
<p><a href="https://lh3.googleusercontent.com/DNLuwV09DmIc5-cmY9MPbthkvVUgLTLSN0_zJRsK_nipgsOMiQ1TX3algAx1dETnqC9sJ9zlD9i7=w1280-no"><img src="https://lh3.googleusercontent.com/DNLuwV09DmIc5-cmY9MPbthkvVUgLTLSN0_zJRsK_nipgsOMiQ1TX3algAx1dETnqC9sJ9zlD9i7=w1280-no" alt="Settings" /></a></p>
<p>Perhatikan nilai <code class="language-plaintext highlighter-rouge">Git URL</code> pada tab <code class="language-plaintext highlighter-rouge">Settings</code>. Kita akan membutuhkan nilainya untuk melakukan deployment nanti.</p>
<p>Klik tab <code class="language-plaintext highlighter-rouge">Resources</code>, dan tambahkan add-ons PostgreSQL</p>
<p><a href="https://lh3.googleusercontent.com/gCR_yDwsszVcWwT8BgqT_Zyb8rAMui3NYzbYMVRGlHW4qBKzloY8ROTMSDiLrr_TgG-7D_lM4V0P=w1280-no"><img src="https://lh3.googleusercontent.com/gCR_yDwsszVcWwT8BgqT_Zyb8rAMui3NYzbYMVRGlHW4qBKzloY8ROTMSDiLrr_TgG-7D_lM4V0P=w1280-no" alt="Add PostgreSQL" /></a></p>
<p>Pilih saja paket Hobby yang gratis</p>
<p><a href="https://lh3.googleusercontent.com/U2QFH7vZHoQDfUvBN4AQsbytOaJ7VWqwD5a8n8V2ow2J6Rje8bn2AFiWtndaj7ZCRXoAmdnQu2gw=w1280-no"><img src="https://lh3.googleusercontent.com/U2QFH7vZHoQDfUvBN4AQsbytOaJ7VWqwD5a8n8V2ow2J6Rje8bn2AFiWtndaj7ZCRXoAmdnQu2gw=w1280-no" alt="Pilih paket" /></a></p>
<p>Database PostgreSQL sudah berhasil ditambahkan.</p>
<p><a href="https://lh3.googleusercontent.com/t-vug3Drj-_hSp_Y6uMn857e1jjbg2YEn5geqDwOOA4fxj2t1dCNhyFNaZSG_nv3Ow2lgmDNXe4S=w1280-no"><img src="https://lh3.googleusercontent.com/t-vug3Drj-_hSp_Y6uMn857e1jjbg2YEn5geqDwOOA4fxj2t1dCNhyFNaZSG_nv3Ow2lgmDNXe4S=w1280-no" alt="Database PostgreSQL" /></a></p>
<p>Klik titik tiga di kanannya untuk mengetahui detail konfigurasinya agar bisa kita pasang di aplikasi.</p>
<p><a href="https://lh3.googleusercontent.com/gTQ0-E-whswg7BM3deNaW4HVijl5l5L1NUJRos2hBiobkq8OJAKmvOI67xmZopYJOumx5nL6fho1=w1280-no"><img src="https://lh3.googleusercontent.com/gTQ0-E-whswg7BM3deNaW4HVijl5l5L1NUJRos2hBiobkq8OJAKmvOI67xmZopYJOumx5nL6fho1=w1280-no" alt="Daftar Database" /></a></p>
<p>Pada halaman di atas terlihat daftar database yang kita miliki, hanya ada satu di sana. Klik nama databasenya untuk melihat detail konfigurasinya</p>
<p><a href="https://lh3.googleusercontent.com/3ihLGkqas3LezBYpsCr-J0zJ-JbsRh1BymgIHT7bZsGXrCiZQ4iyF-Kt147pgHyLDFYtCL582kYG=w1280-no"><img src="https://lh3.googleusercontent.com/3ihLGkqas3LezBYpsCr-J0zJ-JbsRh1BymgIHT7bZsGXrCiZQ4iyF-Kt147pgHyLDFYtCL582kYG=w1280-no" alt="Database Setting" /></a></p>
<p>Di situ kita bisa melihat informasi koneksi database. Informasi ini akan kita pasang di konfigurasi aplikasi.</p>
<h3 id="konfigurasi-aplikasi">Konfigurasi Aplikasi</h3>
<p>Seperti pada Openshift, kita akan membuat konfigurasi terpisah untuk koneksi database Heroku. Tambahkan file <code class="language-plaintext highlighter-rouge">application-heroku.properties</code> di dalam folder <code class="language-plaintext highlighter-rouge">src/main/resources</code>. Isinya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spring.datasource.url=jdbc:postgresql://ec2-54-83-198-159.compute-1.amazonaws.com:5432/d1sjircg9n9989
spring.datasource.username=oxfyfvocxwboqn
spring.datasource.password=1fpn8BZHFIKAALWnvLAUAPBByt
flyway.locations=classpath:db/migration/postgresql
</code></pre></div></div>
<p>Nilai konfigurasi tersebut didapatkan dari setting database yang disediakan Heroku seperti pada screenshot sebelumnya.</p>
<p>Selanjutnya, kita buat script migrasi untuk database PostgreSQL. Sebetulnya tidak terlalu jauh berbeda karena tabel kita cuma satu dan tidak rumit. Berikut isi file <code class="language-plaintext highlighter-rouge">V0.0.1.20160222__Skema Awal.sql</code> yang diletakkan di dalam folder <code class="language-plaintext highlighter-rouge">src/main/resources/db/migration/postgresql</code>.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- tabel Product --</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">product</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">36</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span>
<span class="n">code</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span> <span class="k">unique</span><span class="p">,</span>
<span class="n">name</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">price</span> <span class="nb">decimal</span><span class="p">(</span><span class="mi">19</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Terakhir, jangan lupa menambahkan dependensi untuk driver PostgreSQL di <code class="language-plaintext highlighter-rouge">pom.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.postgresql<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>postgresql<span class="nt"></artifactId></span>
<span class="nt"><scope></span>runtime<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Setelah selesai, jangan lupa <code class="language-plaintext highlighter-rouge">commit</code> ke repo lokal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
git commit -m "konfigurasi database PostgreSQL di Heroku"
</code></pre></div></div>
<h3 id="deployment">Deployment</h3>
<p>Agar Heroku paham cara menjalankan aplikasi kita, buat sebuah file bernama <code class="language-plaintext highlighter-rouge">Procfile</code>. Isinya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>web: java $JAVA_OPTS -jar -Dspring.profiles.active=heroku target/*.jar --server.port=$PORT
</code></pre></div></div>
<p>File tersebut menunjukkan bahwa aplikasi kita adalah aplikasi web, dijalankan dengan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar target/*.jar -Dspring.profiles.active=heroku
</code></pre></div></div>
<p>Selebihnya <code class="language-plaintext highlighter-rouge">$JAVA_OPTS</code> dan <code class="language-plaintext highlighter-rouge">$PORT</code> adalah variabel yang disediakan Heroku.</p>
<p>Deployment dilakukan dengan cara <code class="language-plaintext highlighter-rouge">git push</code>, sama seperti Openshift. Untuk itu, kita daftarkan url git Heroku yang telah kita dapatkan di laman administrasi di atas.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add heroku https://git.heroku.com/aplikasibelajar.git
</code></pre></div></div>
<p>Selanjutnya, kita lakukan deployment</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push heroku master
</code></pre></div></div>
<p>Sama dengan Openshift, Heroku juga membuatkan file-file template untuk deployment. Kita bisa menimpa repository yang dibuatkan Heroku dengan opsi <code class="language-plaintext highlighter-rouge">--force</code> sehingga perintahnya menjadi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push heroku master --force
</code></pre></div></div>
<p>Untuk memantau apakah aplikasi kita berhasil terdeploy dengan baik, kita bisa menampilkan log aplikasi dengan cara mengklik tombol titik tiga di kanan atas laman administrasi</p>
<p><a href="https://lh3.googleusercontent.com/pXk4gPwG1cWjPkPXEDpnQg0mX1zPo6qx7HYerrWpuZikO_MxCYY89XMqOa2zKOPmj0yA1LjdaB_V=w1280-no"><img src="https://lh3.googleusercontent.com/pXk4gPwG1cWjPkPXEDpnQg0mX1zPo6qx7HYerrWpuZikO_MxCYY89XMqOa2zKOPmj0yA1LjdaB_V=w1280-no" alt="View Log Button" /></a></p>
<p>Selanjutnya, kita bisa lihat apakah ada error yang terjadi di log aplikasi</p>
<p><a href="https://lh3.googleusercontent.com/53eCLZjK12alrsbu8fyeZ4qq3hFMpuE3omVDGd3BupHlSDJbPv0IW1ecQL7msdmLqaHyY0AlBcuC=w1280-no"><img src="https://lh3.googleusercontent.com/53eCLZjK12alrsbu8fyeZ4qq3hFMpuE3omVDGd3BupHlSDJbPv0IW1ecQL7msdmLqaHyY0AlBcuC=w1280-no" alt="Log Aplikasi" /></a></p>
<p>Bila semuanya lancar, kita dapat mengakses aplikasi kita dengan URL yang tercantum di laman administrasi</p>
<p><a href="https://lh3.googleusercontent.com/kHtUIutukivieFArVdrsOLZcbSOFeiPq4nFpiksILhvWTRCQsZYNcXet-MIb63bZrLB6kpnewPXS=w1280-no"><img src="https://lh3.googleusercontent.com/kHtUIutukivieFArVdrsOLZcbSOFeiPq4nFpiksILhvWTRCQsZYNcXet-MIb63bZrLB6kpnewPXS=w1280-no" alt="Output Heroku" /></a></p>
<h2 id="openshift">Openshift</h2>
<p><strong>UPDATE !!!</strong></p>
<blockquote>
<p>Pada saat artikel ini ditulis, Openshift masih menjalankan versi lama.
Saat ini OpenShift versi lama sudah tidak menerima pendaftaran baru.
Sedangkan OpenShift versi baru hanya menyediakan akun trial yang aplikasi berikut datanya akan terhapus setelah satu bulan.
Oleh karena itu, bagian Openshift ini sudah obsolete dan tidak bisa dijadikan referensi lagi.</p>
</blockquote>
<p>Untuk bisa mendeploy ke Openshift, terlebih dulu kita harus mendaftar. Cara pendaftaran sudah pernah saya bahas di <a href="http://software.endy.muhardin.com/aplikasi/membuat-blog-gratis-di-openshift/">artikel sebelumnya</a>. Langsung saja <a href="https://www.openshift.com/">ke websitenya</a> dan mendaftar.</p>
<h3 id="pembuatan-aplikasi-di-openshift">Pembuatan Aplikasi di Openshift</h3>
<p>Setelah login, kita akan melihat daftar aplikasi yang kita miliki.</p>
<p><a href="https://lh3.googleusercontent.com/1aqd3dM7DQXOCgBEqPGnsrzk5UluU2kwtU5CGBpC6xxaK1Nt0Mjw2OLujqfZQXjuE8j4KuN3PZBQ=w1280-no"><img src="https://lh3.googleusercontent.com/1aqd3dM7DQXOCgBEqPGnsrzk5UluU2kwtU5CGBpC6xxaK1Nt0Mjw2OLujqfZQXjuE8j4KuN3PZBQ=w1280-no" alt="Daftar Aplikasi" /></a></p>
<p>Ada tombol <code class="language-plaintext highlighter-rouge">Add Application</code> untuk menambah aplikasi baru. Klik tombol tersebut. Kita akan mendapati daftar cartridge (template aplikasi) yang disediakan Openshift untuk mendeploy aplikasi kita. Aplikasi web java biasanya menggunakan cartridge Tomcat, JBoss, atau application server lainnya. Akan tetapi, karena Spring Boot sudah menyertakan (embedded) Tomcat, kita tidak perlu lagi menggunakan cartridge yang sudah ada. Gunakan cartridge <code class="language-plaintext highlighter-rouge">DIY</code> (Do It Yourself), karena kita akan mengkonfigurasi aplikasi kita secara manual.</p>
<p><a href="https://lh3.googleusercontent.com/6c0LMSjOeahK8ekWm3s8FEFezIeBCFxORwx9bQEfYL5pKbYrHlCZMQ1aITY12ltqDdscFtEBZGkz=w1280-no"><img src="https://lh3.googleusercontent.com/6c0LMSjOeahK8ekWm3s8FEFezIeBCFxORwx9bQEfYL5pKbYrHlCZMQ1aITY12ltqDdscFtEBZGkz=w1280-no" alt="Pilih DIY" /></a></p>
<p>Selanjutnya, kita akan melihat form pembuatan aplikasi. Cukup isikan saja nama aplikasi yang akan dibuat, misalnya <code class="language-plaintext highlighter-rouge">belajar</code>.</p>
<p><a href="https://lh3.googleusercontent.com/uDCwC_KCzpBD8YKeuqbek4rKcBAyZ2Ou2izqfsZmPk5QuZveMEZdct8bmm0_OETvJ8p_grO3FWbv=w1280-no"><img src="https://lh3.googleusercontent.com/uDCwC_KCzpBD8YKeuqbek4rKcBAyZ2Ou2izqfsZmPk5QuZveMEZdct8bmm0_OETvJ8p_grO3FWbv=w1280-no" alt="Nama Aplikasi" /></a></p>
<p>Klik OK di bagian paling bawah, dan aplikasi kita akan sukses dibuat.</p>
<p><a href="https://lh3.googleusercontent.com/f1kOBThw_3YwCBH8ainqOk_tS-MUwKXM1gRBw9vJ3RptffOvqp4b1iNb4NaEKV9KZHEVa6RfESpj=w1280-no"><img src="https://lh3.googleusercontent.com/f1kOBThw_3YwCBH8ainqOk_tS-MUwKXM1gRBw9vJ3RptffOvqp4b1iNb4NaEKV9KZHEVa6RfESpj=w1280-no" alt="Sukses" /></a></p>
<p>Kita bisa klik ke halaman Application Overview untuk melihat setting aplikasi kita.</p>
<p><a href="https://lh3.googleusercontent.com/doaPkBpvFUULJYTjnNyG4uwSpm3RmgFvktmkoZbs--fj_I7TMpo0TyCUnqbufIoLVRp4RvTgAnjj=w1280-no"><img src="https://lh3.googleusercontent.com/doaPkBpvFUULJYTjnNyG4uwSpm3RmgFvktmkoZbs--fj_I7TMpo0TyCUnqbufIoLVRp4RvTgAnjj=w1280-no" alt="Halaman Settings" /></a></p>
<p>Karena aplikasi kita membutuhkan database, tambahkan cartridge MySQL dengan cara klik tombol ‘Add MySQL 5.5’. Kita akan disajikan halaman konfirmasi.</p>
<p><a href="https://lh3.googleusercontent.com/DEltdcKmAuKm3wJDHfmLv0QVMPGJTcn96WXvSBQEhdMC2vQvTPiGimh_GZO-s_sjrj3I1gTa1-Ic=w1280-no"><img src="https://lh3.googleusercontent.com/DEltdcKmAuKm3wJDHfmLv0QVMPGJTcn96WXvSBQEhdMC2vQvTPiGimh_GZO-s_sjrj3I1gTa1-Ic=w1280-no" alt="Menambahkan MySQL Cartridge" /></a></p>
<p>Klik Add Cartridge</p>
<p><a href="https://lh3.googleusercontent.com/gBr5F6fz0OXHchd6tW5AQAdOqZsHWHi_uYyVskAJgnrkk9HaUKdKODtQcQ21hstuiC9nq9XG5-4R=w1280-no"><img src="https://lh3.googleusercontent.com/gBr5F6fz0OXHchd6tW5AQAdOqZsHWHi_uYyVskAJgnrkk9HaUKdKODtQcQ21hstuiC9nq9XG5-4R=w1280-no" alt="Konfigurasi MySQL" /></a></p>
<p>Database kita sudah dibuatkan oleh Openshift. Tidak perlu menghafalkan nilai konfigurasinya, karena kita nanti akan menggunakan variabel yang sudah disediakan oleh openshift, yaitu <code class="language-plaintext highlighter-rouge">OPENSHIFT_MYSQL_DB_HOST</code>, <code class="language-plaintext highlighter-rouge">OPENSHIFT_MYSQL_DB_PORT</code>, <code class="language-plaintext highlighter-rouge">OPENSHIFT_APP_NAME</code>, <code class="language-plaintext highlighter-rouge">OPENSHIFT_MYSQL_DB_USERNAME</code>, dan <code class="language-plaintext highlighter-rouge">OPENSHIFT_MYSQL_DB_PASSWORD</code>.</p>
<h3 id="konfigurasi-di-aplikasi">Konfigurasi di Aplikasi</h3>
<p>Aplikasi kita perlu dikonfigurasi supaya bisa diproses dengan baik oleh Openshift. Ada beberapa file yang perlu kita sediakan:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">.openshift/settings.xml</code> : konfigurasi Maven yang akan dipakai dalam Openshift</li>
<li><code class="language-plaintext highlighter-rouge">.openshift/action_hooks/build</code> : script yang dijalankan untuk melakukan proses build</li>
<li><code class="language-plaintext highlighter-rouge">.openshift/action_hooks/start</code> : script yang dijalankan untuk menyalakan aplikasi kita</li>
<li><code class="language-plaintext highlighter-rouge">.openshift/action_hooks/stop</code> : script yang akan dijalankan untuk mematikan aplikasi kita</li>
</ul>
<p>Mari kita buat dulu struktur file dan foldernya. Lakukan command berikut dalam folder aplikasi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p .openshift/action_hooks
touch .openshift/settings.xml
touch .openshift/action_hooks/{build,start,stop}
chmod +x .openshift/action_hooks/*
</code></pre></div></div>
<p>Perintah di atas bisa dijalankan di Linux dan MacOSX. Bila Anda menggunakan Windows, silahkan gunakan Windows Explorer untuk membuat file dan foldernya. Untuk mengeset permission menjadi executable, gunakan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git update-index --chmod=+x .openshift/action_hooks/build
</code></pre></div></div>
<p>Berikut isi dari file <code class="language-plaintext highlighter-rouge">settings.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><settings></span>
<span class="nt"><localRepository></span>${OPENSHIFT_DATA_DIR}/m2/repository<span class="nt"></localRepository></span>
<span class="nt"></settings></span>
</code></pre></div></div>
<p>Dan ini adalah isi file <code class="language-plaintext highlighter-rouge">build</code> yang berada dalam folder <code class="language-plaintext highlighter-rouge">.openshift/action_hooks</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash
set -x
if [ ! -d $OPENSHIFT_DATA_DIR/m2/repository ]
then
cd $OPENSHIFT_DATA_DIR
mkdir m2/repository
fi
if [ ! -d $OPENSHIFT_DATA_DIR/logs ]
then
cd $OPENSHIFT_DATA_DIR
mkdir logs
fi
if [ ! -d $OPENSHIFT_DATA_DIR/jdk1.8.0_20 ]
then
cd $OPENSHIFT_DATA_DIR
wget http://www.java.net/download/jdk8u20/archive/b17/binaries/jdk-8u20-ea-bin-b17-linux-x64-04_jun_2014.tar.gz
tar xvf *.tar.gz
rm -f *.tar.gz
fi
if [ ! -d $OPENSHIFT_DATA_DIR/apache-maven-3.3.9 ]
then
cd $OPENSHIFT_DATA_DIR
wget http://www-us.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz
tar xvf *.tar.gz
rm -f *.tar.gz
fi
export M2=$OPENSHIFT_DATA_DIR/apache-maven-3.3.9/bin
export MAVEN_OPTS="-Xms384m -Xmx412m"
export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20
export PATH=$JAVA_HOME/bin:$M2:$PATH
cd $OPENSHIFT_REPO_DIR
mvn -s .openshift/settings.xml clean package -DskipTests=true
</code></pre></div></div>
<p>Pada script <code class="language-plaintext highlighter-rouge">build</code> di atas, kita mengunduh dan menginstall Java SDK versi 8 dan Maven versi 3.3.3. Jangan lupa mengupdate URL download sesuai dengan versi terbaru yang tersedia pada waktu kita mendeploy.</p>
<p>Setelah itu, kita mengeset environment variable. Terakhir, kita jalankan proses kompilasi tanpa menjalankan test. Hasilnya adalah file <code class="language-plaintext highlighter-rouge">*.jar</code> di dalam folder <code class="language-plaintext highlighter-rouge">target</code> yang siap dijalankan.</p>
<p>Berikut isi file <code class="language-plaintext highlighter-rouge">start</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash
export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20
export PATH=$JAVA_HOME/bin:$PATH
cd $OPENSHIFT_REPO_DIR
nohup java -Xms384m -Xmx412m -jar -Dspring.profiles.active=openshift target/*.jar --server.port=${OPENSHIFT_DIY_PORT} --server.address=${OPENSHIFT_DIY_IP} &
</code></pre></div></div>
<p>Script <code class="language-plaintext highlighter-rouge">start</code> di atas hanya mengatur environment variable dan menjalankan aplikasi. Kita menggunakan profile <code class="language-plaintext highlighter-rouge">openshift</code> agar Spring Boot membaca file konfigurasi yang sesuai. Isi file ini akan kita bahas di bawah.</p>
<p>Ini adalah isi file <code class="language-plaintext highlighter-rouge">stop</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH
PID=$(ps -ef | grep java.*\.jar | grep -v grep | awk '{ print $2 }')
if [ -z "$PID" ]
then
client_result "Application is already stopped"
else
kill $PID
fi
</code></pre></div></div>
<p>Pada script <code class="language-plaintext highlighter-rouge">start</code> di atas, kita menyuruh Spring untuk menggunakan profile <code class="language-plaintext highlighter-rouge">openshift</code>. Dengan fitur profile ini, kita bisa membedakan konfigurasi koneksi database antara setting di komputer kita dan di server Openshift. Untuk itu, kita buat file <code class="language-plaintext highlighter-rouge">application-openshift.properties</code> di dalam folder <code class="language-plaintext highlighter-rouge">src/main/resources</code>. File ini akan dibaca apabila profile <code class="language-plaintext highlighter-rouge">openshift</code> aktif. Lebih lanjut mengenai profile bisa dibaca pada <a href="">artikel terdahulu</a>.</p>
<p>Berikut isi file <code class="language-plaintext highlighter-rouge">application-openshift.properties</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spring.datasource.url=jdbc:mysql://${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/${OPENSHIFT_APP_NAME}
spring.datasource.username=${OPENSHIFT_MYSQL_DB_USERNAME}
spring.datasource.password=${OPENSHIFT_MYSQL_DB_PASSWORD}
</code></pre></div></div>
<h3 id="deployment-ke-openshift">Deployment ke Openshift</h3>
<p>Deployment dilakukan dengan cara <code class="language-plaintext highlighter-rouge">git push</code>. Untuk itu kita perlu mendapatkan alamat repository git yang berada di Openshift. Informasinya ada di halaman setting aplikasi kita di Openshift.</p>
<p><a href="https://lh3.googleusercontent.com/vlFQG6Z2d3hdAhKOO7SfdHmTvidcWvVhVPAjIT9In_VucK03ouipIc8NB7AryuuhI1GbrTkmSWRw=w1280-no"><img src="https://lh3.googleusercontent.com/vlFQG6Z2d3hdAhKOO7SfdHmTvidcWvVhVPAjIT9In_VucK03ouipIc8NB7AryuuhI1GbrTkmSWRw=w1280-no" alt="Openshift Git URL" /></a></p>
<p>Copy alamat repository tersebut, kemudian daftarkan sebagai remote repository di project kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add openshift ssh://abcdefghijklmn1234567890@belajar-endymuhardin.rhcloud.com/~/git/belajar.git
</code></pre></div></div>
<p>Terakhir, lakukan push</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push openshift master
</code></pre></div></div>
<p>Bila kita melakukan push di repository yang kita buat sendiri (bukan hasil clone dari Openshift), perintah di atas akan menimbulkan error. Ini disebabkan karena repository yang dibuatkan Openshift telah memiliki file di dalamnya, bukan repository kosong. File-file yang dibuatkan Openshift ini adalah file contoh/template konfigurasi untuk memudahkan deployment. Akan tetapi, karena kita sudah buat sendiri file-file tersebut (misalnya file <code class="language-plaintext highlighter-rouge">build</code>, <code class="language-plaintext highlighter-rouge">start</code>, dan <code class="language-plaintext highlighter-rouge">stop</code>), maka kita tidak membutuhkan lagi file yang dibuatkan Openshift.</p>
<p>Untuk itu, kita bisa langsung saja menimpa repository yang dibuatkan Openshift dengan opsi <code class="language-plaintext highlighter-rouge">--force</code>. Perintahnya menjadi sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push openshift master --force
</code></pre></div></div>
<p>Aplikasi kita bisa diakses di alamat yang sudah disediakan Openshift</p>
<p><a href="https://lh3.googleusercontent.com/33GT-WK5AaVFKzpOGpdhlUVlwZsKc_gdj9Wsed8s2y4m88270eHVJ9HMRCrceqlL_pyQza9pQ0Fe=w1280-no"><img src="https://lh3.googleusercontent.com/33GT-WK5AaVFKzpOGpdhlUVlwZsKc_gdj9Wsed8s2y4m88270eHVJ9HMRCrceqlL_pyQza9pQ0Fe=w1280-no" alt="Alamat Aplikasi" /></a></p>
<p>Bila kita coba mengaksesnya, kita akan mendapatkan response JSON.</p>
<p><a href="https://lh3.googleusercontent.com/Ej-1LQGRaOPc4eH7kvNmIt6KNyBpRoyyjmhDRl1OjMYxcd8Hi6_wDHgCCbiWc6MhslhyMu2Hrw1b=w1280-no"><img src="https://lh3.googleusercontent.com/Ej-1LQGRaOPc4eH7kvNmIt6KNyBpRoyyjmhDRl1OjMYxcd8Hi6_wDHgCCbiWc6MhslhyMu2Hrw1b=w1280-no" alt="Output" /></a></p>
<h2 id="deployment-subfolder">Deployment Subfolder</h2>
<p>Project yang kita kerjakan tidak selalu hanya terdiri dari satu aplikasi. Adakalanya project kita terdiri dari aplikasi web dan mobile yang bekerja sama. Untuk itu, biasanya di dalam repository kita akan ada dua folder untuk masing-masing aplikasi web dan aplikasi mobile, seperti terlihat pada screenshot di bawah.</p>
<p><a href="https://lh3.googleusercontent.com/fWzlT2nSNMAWTN6SMsxY189Pq_R7VHq5EK75luWrLS4elrHJ1farvhn5jQRv0BJj8qDKaSaY50Se=w305-h404-no"><img src="https://lh3.googleusercontent.com/fWzlT2nSNMAWTN6SMsxY189Pq_R7VHq5EK75luWrLS4elrHJ1farvhn5jQRv0BJj8qDKaSaY50Se=w305-h404-no" alt="Screenshot Project Multi Aplikasi" /></a></p>
<p>Pada situasi seperti ini, kita tidak bisa langsung push begitu saja ke Heroku dan Openshift, karena mereka mengasumsikan project kita berada di top level folder. Mereka mengharapkan ada file <code class="language-plaintext highlighter-rouge">pom.xml</code> di root folder. Sedangkan bila project kita terdiri dari aplikasi web dan mobile, <code class="language-plaintext highlighter-rouge">pom.xml</code> akan berada dalam subfolder <code class="language-plaintext highlighter-rouge">web</code> seperti pada screenshot di atas.</p>
<p>Solusinya adalah menggunakan fitur <code class="language-plaintext highlighter-rouge">subtree</code> yang ada di Git. Bila kita ingin mendeploy folder <code class="language-plaintext highlighter-rouge">belajar-ci-web</code> di screenshot di atas, perintahnya adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git subtree push --prefix belajar-ci-web openshift master
</code></pre></div></div>
<p>Tentunya sebelumnya kita harus mendaftarkan dulu lokasi repository <code class="language-plaintext highlighter-rouge">openshift</code> seperti penjelasan di atas. Sama seperti penjelasan sebelumnya, repository yang dibuatkan Openshift dan Heroku biasanya sudah ada isinya, sehingga kita timpa menggunakan opsi <code class="language-plaintext highlighter-rouge">--force</code>. Akan tetapi, untuk perintah <code class="language-plaintext highlighter-rouge">git subtree</code> ini, pemakaian opsi <code class="language-plaintext highlighter-rouge">--force</code> ini agak sedikit berbeda. Kita harus menggunakan format lengkap dari push, yaitu seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push namaremote namabranchdilokal:namabranchdiremote --force
</code></pre></div></div>
<p>Nama branch di lokal diganti dengan isi subfolder, kita bisa ambil dengan perintah <code class="language-plaintext highlighter-rouge">git subtree split --prefix belajar-ci-web</code>. Sedangkan di tujuan (yaitu di Openshift), nama branch tujuannya adalah <code class="language-plaintext highlighter-rouge">master</code>. Perintah yang kita jalankan menjadi seperti ini</p>
<pre><code>git push openshift `git subtree split --prefix belajar-ci-web`:master --force</code></pre>
<p>Untuk mendeploy subfolder ke Heroku caranya sama. Tinggal ganti saja tujuan remotenya.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah cara deployment aplikasi ke penyedia layanan PaaS di cloud. Setelah kita bisa melakukannya secara manual, pada <a href="http://software.endy.muhardin.com/java/project-bootstrap-04/">artikel berikutnya kita akan otomasi deployment ini menggunakan Travis CI</a>. Stay tuned ……</p>
Setup Continuous Integration2016-02-23T02:00:00+07:00https://software.endy.muhardin.com/java/project-bootstrap-02<p>Pada <a href="http://software.endy.muhardin.com/java/project-bootstrap-01/">artikel sebelumnya</a>, kita telah membuat struktur project lengkap dari database sampai ke web. Project tersebut juga telah dilengkapi dengan automated test dan sampel data.</p>
<p>Akan tetapi, di artikel terdahulu tersebut, kita harus menjalankan semua test tersebut melalui command line. Dengan demikian, bila ada programmer yang malas membuat automated test dan menjalankannya, kita tidak bisa mendeteksinya.</p>
<p>Untuk itu, kita akan mengkonfigurasi continuous integration, yaitu suatu scheduler yang memantau repository Git kita, dan menjalankan proses build pada waktu ada update di repository. Dengan demikian, apabila terjadi error, semua anggota tim bisa langsung mendapatkan notifikasi.</p>
<p>Ada beberapa tools untuk menjalankan proses ini. Pada jaman dahulu saya pernah juga menulis artikel tentang penggunaan <a href="http://software.endy.muhardin.com/aplikasi/cruise-control/">CruiseControl</a> dan <a href="http://software.endy.muhardin.com/java/luntbuild/">Luntbuild</a>. Tapi itu artikel jadul sekali, yang populer pada jaman sekarang adalah <a href="https://travis-ci.org/">Travis</a> dan <a href="http://jenkins-ci.org/">Jenkins</a></p>
<!--more-->
<h2 id="travis">Travis</h2>
<p>Travis adalah tools continuous integration (kita singkat saja CI yah, capek ngetiknya) yang berbasis cloud. Kita tinggal registrasi, daftarkan project kita, dan dia akan melakukan build terhadap project kita.</p>
<p>Travis CI sangat terintegrasi dengan Github. Jadi kalau kita sudah punya akun Github, kita bisa langsung login menggunakan Github.</p>
<p><a href="https://lh3.googleusercontent.com/YVjGIJ31L9iwcOTdSAqzXULEoIqMGjFVnB9nFn-BGON6xnuEEYwz8YjQrvujRg_RR2bMF5y_KHvG=w1280-no"><img src="https://lh3.googleusercontent.com/YVjGIJ31L9iwcOTdSAqzXULEoIqMGjFVnB9nFn-BGON6xnuEEYwz8YjQrvujRg_RR2bMF5y_KHvG=w1280-no" alt="Travis CI Home Page" /></a></p>
<p>Setelah kita login, Travis akan mengakses akun Github kita dan menampilkan daftar repository kita dalam Github. Untuk mengaktifkan fitur CI, kita tinggal klik saja project yang ingin diproses.</p>
<p><a href="https://lh3.googleusercontent.com/uXb5IrJuZUJaGcpM41-d-cgW8lId4PBU89M10Y1r-4UnoGgIi4aVyQKEpAyjzCFVvDGsXI1gheiS=w1280-no"><img src="https://lh3.googleusercontent.com/uXb5IrJuZUJaGcpM41-d-cgW8lId4PBU89M10Y1r-4UnoGgIi4aVyQKEpAyjzCFVvDGsXI1gheiS=w1280-no" alt="Enable CI di Travis" /></a></p>
<p>Setelah dienable, Travis akan memantau project kita. Pada saat terjadi <code class="language-plaintext highlighter-rouge">git push</code>, Travis akan menjalankan proses build sesuai konfigurasi yang kita tulis di file <code class="language-plaintext highlighter-rouge">.travis.yml</code>. Berikut contoh file yang saya gunakan</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">language</span><span class="pi">:</span> <span class="s">java</span>
<span class="na">jdk</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">oraclejdk8</span>
<span class="na">services</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">mysql</span>
<span class="na">before_install</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">mysql -uroot -e "grant all on belajar.* to belajar@localhost identified by 'java'"</span>
<span class="pi">-</span> <span class="s">mysql -uroot -e "drop database if exists belajar"</span>
<span class="pi">-</span> <span class="s">mysql -uroot -e "create database belajar"</span>
</code></pre></div></div>
<p>Pada konfigurasi di atas, kita menyatakan bahwa project kita dibuat menggunakan bahasa pemrograman Java. Travis akan mencoba melakukan build menggunakan Maven atau Gradle. Bila di project kita ada file <code class="language-plaintext highlighter-rouge">pom.xml</code>, Travis akan menggunakan Maven. Demikian juga bila ditemukan file <code class="language-plaintext highlighter-rouge">build.gradle</code>, Travis akan menggunakan Gradle. Dokumentasi lengkapnya bisa dibaca <a href="https://docs.travis-ci.com/user/languages/java">di dokumentasi Travis</a>.</p>
<p>Aplikasi kita menggunakan database MySQL. Untuk mengaktifkannya kita tulis konfigurasi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>services:
- mysql
</code></pre></div></div>
<p>Agar tidak banyak modifikasi source code, kita juga samakan username, password, dan nama database. Ini dikonfigurasi menggunakan script <code class="language-plaintext highlighter-rouge">before install</code>.</p>
<p>Setelah konfigurasi <code class="language-plaintext highlighter-rouge">.travis.yml</code> kita sediakan, kita lakukan <code class="language-plaintext highlighter-rouge">git push</code>. Travis akan mulai melakukan proses build.</p>
<p><a href="https://lh3.googleusercontent.com/klkqYfrSvq8NNxMJ2S7f-0ZMWawEFt4KKEmkD6YkrfDi4XBruAnZG7jH23kAzNivBU8h8nOpskf1=w1280-no"><img src="https://lh3.googleusercontent.com/klkqYfrSvq8NNxMJ2S7f-0ZMWawEFt4KKEmkD6YkrfDi4XBruAnZG7jH23kAzNivBU8h8nOpskf1=w1280-no" alt="Proses Build di Travis" /></a></p>
<h2 id="coveralls">Coveralls</h2>
<blockquote>
<p>Bagaimana bila programmer kita malas membuat automated test?</p>
</blockquote>
<p>Pertama, tentu kita butuh notifikasi dulu bila mereka tidak membuat test. Untuk keperluan ini, kita bisa menggunakan coverage testing. Konsepnya sudah pernah saya bahas <a href="http://software.endy.muhardin.com/java/ruthless-testing-2/">di artikel terdahulu</a>.</p>
<p>Pada artikel tersebut, laporan dari coverage report bisa kita lihat di komputer kita sendiri. Belum dipublish supaya bisa dilihat semua orang, dan juga belum ada notifikasinya. Untuk itu, kita menggunakan layanan tambahan yang disediakan oleh <a href="https://coveralls.io/">Coveralls</a>.</p>
<p><a href="https://lh3.googleusercontent.com/z2J6XPuXliM_xCd4owr3MZ2CCZ-R81Uhc17p00jAJ1vZPzkO_-HTNnFiSWDwMlvh1i9_mGEmDWEK=w1280-no"><img src="https://lh3.googleusercontent.com/z2J6XPuXliM_xCd4owr3MZ2CCZ-R81Uhc17p00jAJ1vZPzkO_-HTNnFiSWDwMlvh1i9_mGEmDWEK=w1280-no" alt="Coveralls Sign In Page" /></a></p>
<p>Sama seperti Travis, Coveralls juga sudah terintegrasi dengan Github. Kita bisa login menggunakan akun Github, dan dia akan membaca project-project yang tersedia di akun Github kita.</p>
<p><a href="https://lh3.googleusercontent.com/UYBoUBRJVmqfxqFxkjlYzu9Eb8mgsxqAee2Bkn5hewrQOyQAsNkvjD59Gk1fI4lK4UfUfy-rGgln=w1280-no"><img src="https://lh3.googleusercontent.com/UYBoUBRJVmqfxqFxkjlYzu9Eb8mgsxqAee2Bkn5hewrQOyQAsNkvjD59Gk1fI4lK4UfUfy-rGgln=w1280-no" alt="Coveralls Add Repo" /></a></p>
<p>Kita bisa mendaftarkan project yang ingin kita tampilkan coverage reportnya. Setelah dienable, akan tampil instruksi untuk mengirim laporan coverage ke Coveralls.</p>
<p><a href="https://lh3.googleusercontent.com/3i4-oNere292oOkYbhFQQzgTw5ei0EN389pdh_gWGup1NF0VnzbzV5f9Y93cJXKnMCB6WpA2KMbl=w1280-no"><img src="https://lh3.googleusercontent.com/3i4-oNere292oOkYbhFQQzgTw5ei0EN389pdh_gWGup1NF0VnzbzV5f9Y93cJXKnMCB6WpA2KMbl=w1280-no" alt="Setup Coveralls" /></a></p>
<p>Sebenarnya Coveralls sendiri tidak melakukan coverage testing. Dia mengandalkan tools lain untuk melakukannya dan menghasilkan report. Di Java, kita bisa menggunakan Jacoco, Cobertura, Saga, dan sebagainya. Oleh karena itu, kita perlu mengaktifkannya dulu di proses build kita. Tambahkan baris berikut di <code class="language-plaintext highlighter-rouge">pom.xml</code> di dalam tag <code class="language-plaintext highlighter-rouge"><build><plugins></code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.jacoco<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>jacoco-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.7.5.201505241946<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><excludes></span>
<span class="nt"><exclude></span>**/BelajarCiApplication.*<span class="nt"></exclude></span>
<span class="nt"></excludes></span>
<span class="nt"></configuration></span>
<span class="nt"><executions></span>
<span class="nt"><execution></span>
<span class="nt"><id></span>prepare-agent<span class="nt"></id></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>prepare-agent<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"></execution></span>
<span class="nt"></executions></span>
<span class="nt"></plugin></span>
</code></pre></div></div>
<p>Kita kecualikan main class kita, karena isinya cuma <code class="language-plaintext highlighter-rouge">public static void main</code> yang tidak perlu dites. Bila tidak dikecualikan, akan mengurangi nilai coveragenya.</p>
<p>Jacoco akan menghasilkan file <code class="language-plaintext highlighter-rouge">jacoco.exec</code> yang merupakan hasil coverage testing dan juga file <code class="language-plaintext highlighter-rouge">target/site/jacoco/jacoco.xml</code>. File ini kemudian akan dikonversi menjadi file JSON yang dipahami oleh Coveralls dan dikirim melalui REST API. Konversi dan pengiriman ini dilakukan oleh plugin Maven. Tambahkan baris berikut di bawah deklarasi plugin Jacoco.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.eluder.coveralls<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>coveralls-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>4.1.0<span class="nt"></version></span>
<span class="nt"></plugin></span>
</code></pre></div></div>
<p>Agar Maven menjalankan proses pembuatan report, kita harus mengeksekusi target <code class="language-plaintext highlighter-rouge">mvn jacoco:report coveralls:report</code>. Tambahkan baris berikut di konfigurasi <code class="language-plaintext highlighter-rouge">.travis.yml</code> agar Travis menjalankan target tersebut</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">after_success</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">mvn jacoco:report coveralls:report</span>
</code></pre></div></div>
<p>Selanjutnya, pada saat kita melakukan <code class="language-plaintext highlighter-rouge">git push</code>, Travis akan bekerja dan menjalankan target tersebut. Kita dapat melihat history perkembangan nilai coverage test</p>
<p><a href="https://lh3.googleusercontent.com/xpBSv6CxRsTD4nUfgsnpTnQzyApd8Dzc9oPlG2zks7nGIx6bkmXxRdTMkmHToysRfmOb9GrvwXiE=w1280-no"><img src="https://lh3.googleusercontent.com/xpBSv6CxRsTD4nUfgsnpTnQzyApd8Dzc9oPlG2zks7nGIx6bkmXxRdTMkmHToysRfmOb9GrvwXiE=w1280-no" alt="Coverage History" /></a></p>
<p>pada halaman di atas, kita juga bisa mengkonfigurasi notifikasi error apabila nilai coverage turun di bawah sekian persen, atau selisih penurunannya sebesar sekian persen. Saya atur apabila turun di bawah 80%, Coveralls akan mengirim pesan error. Dan bila penurunannya sebesar 5% (misalnya dari 100% turun menjadi 90%), Coveralls juga akan mengirim notifikasi.</p>
<p>Kita juga bisa lihat detail coverage per file source code</p>
<p><a href="https://lh3.googleusercontent.com/hFfcykMnmN23Bk31pJltj0UA6NB-Qg8r6r_ckH0NMuECuEioS99DtftmWpLP2nish9ZDeJrdGD2F=w1280-no"><img src="https://lh3.googleusercontent.com/hFfcykMnmN23Bk31pJltj0UA6NB-Qg8r6r_ckH0NMuECuEioS99DtftmWpLP2nish9ZDeJrdGD2F=w1280-no" alt="Coverage Project" /></a></p>
<p>dan bahkan kalau kita klik, kita bisa lihat baris per baris apakah sudah dijalankan oleh test.</p>
<p><a href="https://lh3.googleusercontent.com/Sjl1xIQnokrCTqYPqlWR8t37zrb5LuGnY1qZ5BeVRJI_FoMdSGzKBIOyJygFCLlrl8MfTcW_lBaM=w1280-no"><img src="https://lh3.googleusercontent.com/Sjl1xIQnokrCTqYPqlWR8t37zrb5LuGnY1qZ5BeVRJI_FoMdSGzKBIOyJygFCLlrl8MfTcW_lBaM=w1280-no" alt="Line Coverage" /></a></p>
<h2 id="build-badge">Build Badge</h2>
<p>Jaman sekarang, sangat penting bahwa kita eksis di dunia maya. Demikian juga dalam urusan project. Travis dan Coveralls memahami ini dan menyediakan badge untuk kita pasang di halaman project kita. Cukup tambahkan baris berikut pada file <code class="language-plaintext highlighter-rouge">README.md</code></p>
<div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nv">[![Build Status</span><span class="p">](</span><span class="sx">https://travis-ci.org/endymuhardin/belajar-ci.svg?branch=master</span><span class="p">)</span>](https://travis-ci.org/endymuhardin/belajar-ci)](https://travis-ci.org/endymuhardin/belajar-ci)
<span class="p">[</span><span class="nv">[![Coverage Status</span><span class="p">](</span><span class="sx">https://coveralls.io/repos/github/endymuhardin/belajar-ci/badge.svg?branch=master</span><span class="p">)</span>](https://coveralls.io/github/endymuhardin/belajar-ci?branch=master)](https://coveralls.io/github/endymuhardin/belajar-ci?branch=master)
</code></pre></div></div>
<p>untuk menampilkan badge seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/Q5nXA4u7k-2TN_JVVq8rzAIG-6BT-mnT4rLqr8CIyZOEsJ8mY78I4Ua14VJrPSYWkQdN6wYN8oSo=w1280-no"><img src="https://lh3.googleusercontent.com/Q5nXA4u7k-2TN_JVVq8rzAIG-6BT-mnT4rLqr8CIyZOEsJ8mY78I4Ua14VJrPSYWkQdN6wYN8oSo=w1280-no" alt="Build Badge" /></a></p>
<h2 id="penutup">Penutup</h2>
<p>Untuk project open source, Travis dan Coveralls dapat digunakan secara gratis. Tapi untuk project private, kita harus bayar. Pada saat artikel ini ditulis, harganya $129/bulan/project untuk Travis dan $5/bulan/project untuk Coveralls. Cukup mahal juga.</p>
<p>Bila kita ingin murah, maka kita bisa install sendiri tools CI yang tersedia gratis, misalnya:</p>
<ul>
<li>Jenkins</li>
<li>Gitlab CI</li>
</ul>
<p>Kita bisa install tools tersebut di hosting gratisan seperti Heroku atau Openshift, atau yang berbayar seperti <a href="https://m.do.co/c/910ad80271f7">DigitalOcean</a> atau Linode.</p>
<p>Setelah project kita bisa dibuild otomatis, artikel berikutnya akan membahas tentang <a href="http://software.endy.muhardin.com/java/project-bootstrap-03/">deployment ke cloud provider populer</a>. Stay tuned ……</p>
Setup Project Baru2016-02-22T07:00:00+07:00https://software.endy.muhardin.com/java/project-bootstrap-01<p>Bila kita ingin memulai pembuatan suatu aplikasi, tentu ada hal-hal yang harus dipersiapkan dulu agar tim bisa bekerja dengan baik. Berikut adalah checklist hal-hal yang biasa saya siapkan sebelum project dimulai.</p>
<ul>
<li>Membuat Repository Git</li>
<li>Membuat struktur file dan folder project</li>
<li>Membuat satu flow utuh dari database sampai ke tampilan</li>
<li>Membuat contoh automated test lengkap dengan sample datanya</li>
<li>Setup continuous integration agar aplikasi dites secara otomatis dan berkala</li>
<li>Setup continuous deployment agar setelah lulus tes otomatis, aplikasi langsung dideploy dan siap dites oleh tester</li>
</ul>
<p>Karena langkahnya cukup banyak, maka artikel ini kita bagi menjadi beberapa bagian:</p>
<ul>
<li>Setup Project</li>
<li><a href="http://software.endy.muhardin.com/java/project-bootstrap-02/">Setup Continuous Integration</a></li>
<li><a href="http://software.endy.muhardin.com/java/project-bootstrap-03/">Setup Deployment ke PaaS</a></li>
<li><a href="http://software.endy.muhardin.com/java/project-bootstrap-04/">Setup Continuous Deployment</a></li>
</ul>
<p>Kita mulai dengan Setup Project</p>
<!--more-->
<p>Tujuan dari setup project ini adalah struktur awal project kita sudah berfungsi dengan baik dan tersedia di repository Git agar dapat diakses seluruh team member. Ada beberapa langkah yang harus kita lakukan:</p>
<h2 id="setup-repository-git">Setup Repository Git</h2>
<p>Untuk mudahnya, kita akan membuat project open source dan disimpan di Github. Untuk project private, kita bisa bayar Github mulai dari $7/bulan untuk 5 user, atau sewa VPS dan install sendiri <a href="https://about.gitlab.com/downloads/">Gitlab</a>.</p>
<p>Registrasi dan instalasi Git <a href="http://software.endy.muhardin.com/aplikasi/instalasi-git-di-windows/">sudah pernah dibahas pada artikel terdahulu</a>, sehingga sekarang kita langsung lanjut ke pembuatan repository. Login ke Github, kemudian klik New Repository</p>
<p><a href="https://lh3.googleusercontent.com/Y0AGdPncs5HEt2zWr2TAwNwfciv3MFCWR6lUAuhEEn3gmKpBrF-4KnJaMUvox_hG1LkfmBoxorbI=w1280-no"><img src="https://lh3.googleusercontent.com/Y0AGdPncs5HEt2zWr2TAwNwfciv3MFCWR6lUAuhEEn3gmKpBrF-4KnJaMUvox_hG1LkfmBoxorbI=w1280-no" alt="Setup Git Repo" /></a></p>
<p>Isikan nama project dan keterangan yang dibutuhkan. Setelah selesai, clone project tersebut ke komputer kita</p>
<p><a href="https://lh3.googleusercontent.com/cwGwMq-IQCfKB72UmPZfgkbsYfHdd0j6QxShB1zCbCT_cyNuwSqghp-57UTAxnpZIBnO8vnziOkj=w1280-no"><img src="https://lh3.googleusercontent.com/cwGwMq-IQCfKB72UmPZfgkbsYfHdd0j6QxShB1zCbCT_cyNuwSqghp-57UTAxnpZIBnO8vnziOkj=w1280-no" alt="Clone Git Repo" /></a></p>
<p>Repository git kita siap digunakan.</p>
<h2 id="setup-project">Setup Project</h2>
<p>Kita akan membuat project Java dengan Spring Boot. Spring sudah menyediakan halaman khusus untuk memudahkan setup project. Langsung buka <a href="http://start.spring.io">halaman tersebut</a>.</p>
<p><a href="https://lh3.googleusercontent.com/nw-1lSZAdHkJHcQmFIvs1LdAfgXFIaCQIzw3hWAocS7UyKXe4c8XQmalL1M72ODCqa1stFXmWZfR=w1280-no"><img src="https://lh3.googleusercontent.com/nw-1lSZAdHkJHcQmFIvs1LdAfgXFIaCQIzw3hWAocS7UyKXe4c8XQmalL1M72ODCqa1stFXmWZfR=w1280-no" alt="Create Starter Project Spring" /></a></p>
<p>Isikan nama package, nama project, dan modul-modul Spring yang akan kita gunakan. Biasanya yang selalu saya gunakan:</p>
<ul>
<li>Web</li>
<li>Data JPA</li>
<li>MySQL</li>
</ul>
<p>Ada juga modul Security, tapi biasanya tidak saya tambahkan pada awal pembuatan.</p>
<p>Begitu kita klik Create Project, browser akan mendownload file <code class="language-plaintext highlighter-rouge">zip</code> yang berisi file-file project. Extract file tersebut, dan masukkan isinya ke folder hasil clone kita. Setelah itu, commit dan push.</p>
<p><a href="https://lh3.googleusercontent.com/_ePewNHtEsOPC-roG_atpHiOLX4o8Ol9kw8tBiCFGzTjdI8fiHWTuSMNob02PyFD7kw6dnB6OGQ9=w1280-no"><img src="https://lh3.googleusercontent.com/_ePewNHtEsOPC-roG_atpHiOLX4o8Ol9kw8tBiCFGzTjdI8fiHWTuSMNob02PyFD7kw6dnB6OGQ9=w1280-no" alt="Commit n Push" /></a></p>
<h2 id="setup-database">Setup Database</h2>
<p>Di project pembuatan aplikasi, biasanya saya men-standarisasi environment development. Nama database, username dan password database, sudah ditentukan dan disamakan di semua komputer programmer. Untuk project ini, kita asumsikan kita akan gunakan:</p>
<ul>
<li>nama database : belajar</li>
<li>username : belajar</li>
<li>password : java</li>
<li>jenis database : MySQL</li>
</ul>
<p>Untuk mempersiapkannya, login ke MySQL dengan user <code class="language-plaintext highlighter-rouge">root</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql -u root -p
Enter Password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.11 Homebrew
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
</code></pre></div></div>
<p>Selanjutnya, kita buat user dan password untuk mengakses database</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grant all on belajar.* to belajar@localhost identified by 'java';
</code></pre></div></div>
<p>Kemudian, kita buat databasenya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create database belajar;
</code></pre></div></div>
<p>Persiapan database selesai. Nilai ini bisa kita masukkan di file <code class="language-plaintext highlighter-rouge">application.properties</code> yang ada dalam starter project kita, yaitu di folder <code class="language-plaintext highlighter-rouge">src/main/resources/</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spring.datasource.url=jdbc:mysql://localhost/belajar
spring.datasource.username=belajar
spring.datasource.password=java
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
</code></pre></div></div>
<p>Berikutnya, kita siapkan script migrasi database, yaitu script untuk membuat skema database sesuai versi aplikasi kita. Ada dua tools yang tersedia, <a href="">Liquibase</a> dan <a href="">Flyway</a>. Kita akan gunakan Flyway. Tambahkan dependensinya di <code class="language-plaintext highlighter-rouge">pom.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.flywaydb<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flyway-core<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Script migrasi ada di folder <code class="language-plaintext highlighter-rouge">src/main/resources/db/migration</code>. Format penamaannya adalah <code class="language-plaintext highlighter-rouge">V<No Versi>__<Keterangan File>.sql</code>. Karena ini masih versi development, filenya saya beri nama <code class="language-plaintext highlighter-rouge">V0.0.1.20160222__Skema Awal.sql</code>. Isikan saja satu tabel sebagai contoh. Berikut contoh isi file saya</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- tabel Product --</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">product</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">32</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span>
<span class="n">code</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span> <span class="k">unique</span><span class="p">,</span>
<span class="n">name</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">price</span> <span class="nb">decimal</span><span class="p">(</span><span class="mi">19</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span>
<span class="p">)</span> <span class="n">Engine</span><span class="o">=</span><span class="n">InnoDB</span><span class="p">;</span>
</code></pre></div></div>
<p>Kemudian coba jalankan proses build untuk mengetes apakah konfigurasi database dan script migrasi sudah terkonfigurasi dengan benar.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn clean package
</code></pre></div></div>
<p>Bila semuanya terkonfigurasi dengan benar, tabel akan terbentuk di database sesuai script migrasi. Kita bisa periksa langsung ke database menggunakan aplikasi database client. Saya lebih suka yang berbasis command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql> show create table product \G
*************************** 1. row ***************************
Table: product
Create Table: CREATE TABLE `product` (
`id` varchar(32) NOT NULL,
`code` varchar(10) NOT NULL,
`name` varchar(255) NOT NULL,
`price` decimal(19,2) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)
</code></pre></div></div>
<h2 id="entity-class-dan-dao">Entity Class dan DAO</h2>
<p>Setelah database terkonfigurasi dengan baik, kita lanjutkan dengan membuat Entity class JPA dan DAO menggunakan Spring Data JPA. Buat class sesuai struktur tabel sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span>
<span class="nd">@Table</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"product"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Product</span> <span class="o">{</span>
<span class="nd">@Id</span>
<span class="nd">@GeneratedValue</span><span class="o">(</span><span class="n">generator</span> <span class="o">=</span> <span class="s">"uuid"</span><span class="o">)</span>
<span class="nd">@GenericGenerator</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"uuid"</span><span class="o">,</span> <span class="n">strategy</span> <span class="o">=</span> <span class="s">"uuid2"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@NotNull</span> <span class="nd">@NotEmpty</span> <span class="nd">@Size</span><span class="o">(</span><span class="n">min</span> <span class="o">=</span> <span class="mi">3</span><span class="o">,</span> <span class="n">max</span> <span class="o">=</span> <span class="mi">10</span><span class="o">)</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">,</span> <span class="n">unique</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">code</span><span class="o">;</span>
<span class="nd">@NotNull</span> <span class="nd">@NotEmpty</span> <span class="nd">@Size</span><span class="o">(</span><span class="n">min</span> <span class="o">=</span> <span class="mi">3</span><span class="o">,</span> <span class="n">max</span> <span class="o">=</span> <span class="mi">255</span><span class="o">)</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="nd">@NotNull</span> <span class="nd">@Min</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">price</span><span class="o">;</span>
<span class="c1">//getter setter tidak ditunjukkan</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kita buatkan <code class="language-plaintext highlighter-rouge">DAO</code>nya. Menggunakan Spring Data JPA, cukup satu baris saja</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ProductDao</span> <span class="kd">extends</span> <span class="nc">PagingAndSortingRepository</span><span class="o"><</span><span class="nc">Product</span><span class="o">,</span> <span class="nc">String</span><span class="o">>{</span> <span class="o">}</span>
</code></pre></div></div>
<h2 id="test-akses-database">Test Akses Database</h2>
<p>Untuk mengetes apakah entity kita sudah dimapping dengan benar, kita buatkan JUnit testnya. Kita buat class di folder <code class="language-plaintext highlighter-rouge">src/test/java</code> dengan package yang sama dengan DAO yang mau ditest</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RunWith</span><span class="o">(</span><span class="nc">SpringJUnit4ClassRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@SpringApplicationConfiguration</span><span class="o">(</span><span class="n">classes</span> <span class="o">=</span> <span class="nc">BelajarCiApplication</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@Transactional</span>
<span class="nd">@Sql</span><span class="o">(</span><span class="n">scripts</span> <span class="o">=</span> <span class="o">{</span><span class="s">"/mysql/delete-data.sql"</span><span class="o">,</span> <span class="s">"/mysql/sample-product.sql"</span><span class="o">})</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductDaoTests</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">ProductDao</span> <span class="n">pd</span><span class="o">;</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testSave</span><span class="o">(){</span>
<span class="nc">Product</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setCode</span><span class="o">(</span><span class="s">"T-001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Test Product 001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"100000.01"</span><span class="o">));</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertNull</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="n">pd</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertNotNull</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testFindById</span><span class="o">(){</span>
<span class="nc">Product</span> <span class="n">p</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="na">findOne</span><span class="o">(</span><span class="s">"abc123"</span><span class="o">);</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertNotNull</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="s">"P-001"</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getCode</span><span class="o">());</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="s">"Product 001"</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">101000.01</span><span class="o">),</span> <span class="n">p</span><span class="o">.</span><span class="na">getPrice</span><span class="o">());</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertNull</span><span class="o">(</span><span class="n">pd</span><span class="o">.</span><span class="na">findOne</span><span class="o">(</span><span class="s">"notexist"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Agar test bisa berjalan dengan baik, kita perlu menyediakan dua script untuk mengisi sampel data dan menghapusnya lagi. Script sampel data ini disimpan di <code class="language-plaintext highlighter-rouge">src/test/resources/mysql</code>. Berikut isinya</p>
<p>File <code class="language-plaintext highlighter-rouge">delete-data.sql</code></p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">delete</span> <span class="k">from</span> <span class="n">product</span><span class="p">;</span>
</code></pre></div></div>
<p>File <code class="language-plaintext highlighter-rouge">sample-product.sql</code></p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">product</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">code</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">price</span><span class="p">)</span>
<span class="k">values</span> <span class="p">(</span><span class="s1">'abc123'</span><span class="p">,</span> <span class="s1">'P-001'</span><span class="p">,</span> <span class="s1">'Product 001'</span><span class="p">,</span> <span class="mi">101000</span><span class="p">.</span><span class="mi">01</span><span class="p">);</span>
</code></pre></div></div>
<p>Mapping entity dan fungsionalitas DAO bisa dites dengan menjalankan perintah <code class="language-plaintext highlighter-rouge">mvn clean package</code>. Hasilnya harusnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ belajar-ci ---
[INFO] Building jar: /Users/endymuhardin/workspace/belajar/belajar-ci/target/belajar-ci-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.3.2.RELEASE:repackage (default) @ belajar-ci ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.539 s
[INFO] Finished at: 2016-02-22T11:03:02+07:00
[INFO] Final Memory: 29M/219M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<p>Coba jalankan beberapa kali untuk memastikan automated testnya repeatable, artinya bisa dijalankan berulang-ulang dengan konsisten.</p>
<h2 id="rest-controller">REST Controller</h2>
<p>Berikutnya, kita akan membuat data kita dalam database bisa diakses melalui HTTP. Untuk itu, kita buatkan class controller untuk fungsi <code class="language-plaintext highlighter-rouge">save</code>, <code class="language-plaintext highlighter-rouge">update</code>, <code class="language-plaintext highlighter-rouge">findAll</code>, dan <code class="language-plaintext highlighter-rouge">findById</code>. Kita akan buat sekaligus dengan automated testnya. Untuk melakukan automated test pada controller, kita gunakan library <a href="https://github.com/jayway/rest-assured/wiki/Usage">Rest Assured</a></p>
<h3 id="kerangka-class">Kerangka Class</h3>
<p>Pertama, mari kita lihat dulu deklarasi class <code class="language-plaintext highlighter-rouge">ProductController</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/api/product"</span><span class="o">)</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductController</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">ProductDao</span> <span class="n">productDao</span><span class="o">;</span>
<span class="c1">// nanti methodnya di sini</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dan ini deklarasi class untuk mengetesnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RunWith</span><span class="o">(</span><span class="nc">SpringJUnit4ClassRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@SpringApplicationConfiguration</span><span class="o">(</span><span class="n">classes</span> <span class="o">=</span> <span class="nc">BelajarCiApplication</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@Sql</span><span class="o">(</span><span class="n">scripts</span> <span class="o">=</span> <span class="o">{</span><span class="s">"/mysql/delete-data.sql"</span><span class="o">,</span> <span class="s">"/mysql/sample-product.sql"</span><span class="o">})</span>
<span class="nd">@WebIntegrationTest</span><span class="o">(</span><span class="n">randomPort</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductControllerTests</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">BASE_URL</span> <span class="o">=</span> <span class="s">"/api/product"</span><span class="o">;</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${local.server.port}"</span><span class="o">)</span>
<span class="kt">int</span> <span class="n">serverPort</span><span class="o">;</span>
<span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setup</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">RestAssured</span><span class="o">.</span><span class="na">port</span> <span class="o">=</span> <span class="n">serverPort</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// method test nanti diisi di sini</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Berikutnya, kita implementasi fungsi save</p>
<h3 id="insert-record-baru">Insert Record Baru</h3>
<p>Insert record ditangani dengan HTTP method POST. Berikut isi method <code class="language-plaintext highlighter-rouge">create</code> di dalam class <code class="language-plaintext highlighter-rouge">ProductController</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/"</span><span class="o">,</span> <span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">)</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o"><</span><span class="nc">Void</span><span class="o">></span> <span class="nf">create</span><span class="o">(</span><span class="nd">@RequestBody</span> <span class="nd">@Valid</span> <span class="nc">Product</span> <span class="n">p</span><span class="o">,</span> <span class="nc">UriComponentsBuilder</span> <span class="n">uriBuilder</span><span class="o">)</span> <span class="o">{</span>
<span class="n">productDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="no">URI</span> <span class="n">location</span> <span class="o">=</span> <span class="n">uriBuilder</span><span class="o">.</span><span class="na">path</span><span class="o">(</span><span class="s">"/api/product/{id}"</span><span class="o">)</span>
<span class="o">.</span><span class="na">buildAndExpand</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">()).</span><span class="na">toUri</span><span class="o">();</span>
<span class="nc">HttpHeaders</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpHeaders</span><span class="o">();</span>
<span class="n">headers</span><span class="o">.</span><span class="na">setLocation</span><span class="o">(</span><span class="n">location</span><span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">ResponseEntity</span><span class="o"><>(</span><span class="n">headers</span><span class="o">,</span> <span class="nc">HttpStatus</span><span class="o">.</span><span class="na">CREATED</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>dan ini method testnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testSave</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">Product</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setCode</span><span class="o">(</span><span class="s">"PT-001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Product Test 001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">102000.02</span><span class="o">));</span>
<span class="n">given</span><span class="o">()</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="n">p</span><span class="o">)</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">ContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">)</span>
<span class="o">.</span><span class="na">when</span><span class="o">()</span>
<span class="o">.</span><span class="na">post</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">201</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"Location"</span><span class="o">,</span> <span class="n">containsString</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/"</span><span class="o">))</span>
<span class="o">.</span><span class="na">log</span><span class="o">().</span><span class="na">headers</span><span class="o">();</span>
<span class="c1">// nama tidak diisi</span>
<span class="nc">Product</span> <span class="n">px</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="o">();</span>
<span class="n">px</span><span class="o">.</span><span class="na">setCode</span><span class="o">(</span><span class="s">"PT-001"</span><span class="o">);</span>
<span class="n">given</span><span class="o">()</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="n">px</span><span class="o">)</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">ContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">)</span>
<span class="o">.</span><span class="na">when</span><span class="o">()</span>
<span class="o">.</span><span class="na">post</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">400</span><span class="o">);</span>
<span class="c1">// kode kurang dari 3 huruf</span>
<span class="nc">Product</span> <span class="n">px1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="o">();</span>
<span class="n">px1</span><span class="o">.</span><span class="na">setCode</span><span class="o">(</span><span class="s">"PT"</span><span class="o">);</span>
<span class="n">px1</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Product Test"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mi">100</span><span class="o">));</span>
<span class="n">given</span><span class="o">()</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="n">px1</span><span class="o">)</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">ContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">)</span>
<span class="o">.</span><span class="na">when</span><span class="o">()</span>
<span class="o">.</span><span class="na">post</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">400</span><span class="o">);</span>
<span class="c1">// Harga negatif</span>
<span class="nc">Product</span> <span class="n">px2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="o">();</span>
<span class="n">px2</span><span class="o">.</span><span class="na">setCode</span><span class="o">(</span><span class="s">"PT-009"</span><span class="o">);</span>
<span class="n">px2</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Product Test"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(-</span><span class="mi">100</span><span class="o">));</span>
<span class="n">given</span><span class="o">()</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="n">px1</span><span class="o">)</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">ContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">)</span>
<span class="o">.</span><span class="na">when</span><span class="o">()</span>
<span class="o">.</span><span class="na">post</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">400</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="tampilkan-semua-record">Tampilkan Semua Record</h3>
<p>Berikut kode program untuk menampilkannya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/"</span><span class="o">,</span> <span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Page</span><span class="o"><</span><span class="nc">Product</span><span class="o">></span> <span class="nf">findAll</span><span class="o">(</span><span class="nc">Pageable</span> <span class="n">page</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">productDao</span><span class="o">.</span><span class="na">findAll</span><span class="o">(</span><span class="n">page</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dan berikut kode program untuk mengetesnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testFindAll</span><span class="o">()</span> <span class="o">{</span>
<span class="n">get</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="s">"totalElements"</span><span class="o">,</span> <span class="n">equalTo</span><span class="o">(</span><span class="mi">1</span><span class="o">))</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="s">"content.id"</span><span class="o">,</span> <span class="n">hasItems</span><span class="o">(</span><span class="s">"abc123"</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="cari-record-berdasarkan-id">Cari Record berdasarkan ID</h3>
<p>Berikut kode program untuk mencari dan menampilkannya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/{id}"</span><span class="o">,</span> <span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Product</span> <span class="nf">findById</span><span class="o">(</span><span class="nd">@PathVariable</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">Product</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">p</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">DataNotFoundException</span><span class="o">(</span><span class="s">"No data with the specified id"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">p</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>dan berikut kode program untuk mengetesnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testFindById</span><span class="o">()</span> <span class="o">{</span>
<span class="n">get</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/abc123"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">200</span><span class="o">)</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="s">"id"</span><span class="o">,</span> <span class="n">equalTo</span><span class="o">(</span><span class="s">"abc123"</span><span class="o">))</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="s">"code"</span><span class="o">,</span> <span class="n">equalTo</span><span class="o">(</span><span class="s">"P-001"</span><span class="o">));</span>
<span class="n">get</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/990"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">404</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="update-record">Update Record</h3>
<p>Berikut kode program untuk mengupdate record yang sudah ada dalam database</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/{id}"</span><span class="o">,</span> <span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">PUT</span><span class="o">)</span>
<span class="nd">@ResponseStatus</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">)</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">update</span><span class="o">(</span><span class="nd">@PathVariable</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">id</span><span class="o">,</span> <span class="nd">@RequestBody</span> <span class="nd">@Valid</span> <span class="nc">Product</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">productDao</span><span class="o">.</span><span class="na">exists</span><span class="o">(</span><span class="n">id</span><span class="o">))</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">DataNotFoundException</span><span class="o">(</span><span class="s">"No data with the specified id"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
<span class="n">productDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>dan berikut kode program untuk mengetesnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testUpdate</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Product</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setCode</span><span class="o">(</span><span class="s">"PX-009"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Product 909"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mi">2000</span><span class="o">));</span>
<span class="n">given</span><span class="o">()</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="n">p</span><span class="o">)</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">ContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">)</span>
<span class="o">.</span><span class="na">when</span><span class="o">()</span>
<span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/abc123"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">200</span><span class="o">);</span>
<span class="n">get</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/abc123"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">200</span><span class="o">)</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="s">"id"</span><span class="o">,</span> <span class="n">equalTo</span><span class="o">(</span><span class="s">"abc123"</span><span class="o">))</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="s">"code"</span><span class="o">,</span> <span class="n">equalTo</span><span class="o">(</span><span class="s">"PX-009"</span><span class="o">))</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="s">"name"</span><span class="o">,</span> <span class="n">equalTo</span><span class="o">(</span><span class="s">"Product 909"</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="delete-record">Delete Record</h3>
<p>Berikut kode program untuk menghapus record dengan <code class="language-plaintext highlighter-rouge">id</code> tertentu</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/{id}"</span><span class="o">,</span> <span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">DELETE</span><span class="o">)</span>
<span class="nd">@ResponseStatus</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">)</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span><span class="o">(</span><span class="nd">@PathVariable</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">productDao</span><span class="o">.</span><span class="na">exists</span><span class="o">(</span><span class="n">id</span><span class="o">))</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">DataNotFoundException</span><span class="o">(</span><span class="s">"No data with the specified id"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">productDao</span><span class="o">.</span><span class="na">delete</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dan berikut kode program untuk mengetesnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testDelete</span><span class="o">()</span> <span class="o">{</span>
<span class="n">delete</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/abc123"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">200</span><span class="o">);</span>
<span class="n">get</span><span class="o">(</span><span class="no">BASE_URL</span><span class="o">+</span><span class="s">"/abc123"</span><span class="o">)</span>
<span class="o">.</span><span class="na">then</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="mi">404</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="test">Test</h3>
<p>Bila sudah selesai semua, kita bisa pastikan semuanya berjalan baik dengan cara menjalankan <code class="language-plaintext highlighter-rouge">mvn clean package</code>. Seharusnya keluar output berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Results :
Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ belajar-ci ---
[INFO] Building jar: /Users/endymuhardin/workspace/belajar/belajar-ci/target/belajar-ci-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.3.2.RELEASE:repackage (default) @ belajar-ci ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 23.373 s
[INFO] Finished at: 2016-02-22T14:24:06+07:00
[INFO] Final Memory: 31M/215M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah project kita sudah selesai disetup. Selanjutnya semua anggota tim bisa mulai ikut terlibat dengan cara clone repositorynya dan mulai menambahkan kode program sesuai fitur yang akan dibuat. Kode program selengkapnya bisa didapatkan <a href="https://github.com/endymuhardin/belajar-ci">di Github</a></p>
<p>Pada <a href="http://software.endy.muhardin.com/java/project-bootstrap-02/">bagian selanjutnya</a>, kita akan mengotomasi proses pengetesan ini dengan menggunakan berbagai tools Continuous Integration. Stay tuned …</p>
Persiapan Coding AngularJS 22016-02-02T20:37:00+07:00https://software.endy.muhardin.com/javascript/persiapan-coding-angularjs2<blockquote>
<p>Artikel ini sudah kadaluarsa, mengingat perkembangan Angular yang luar biasa cepat. Silahkan nonton <a href="https://www.youtube.com/watch?v=iI_6RVpjCtw&list=PL9oC_cq7OYbyXj9NPqM2iedlHQMeChGJp">video tutorialnya</a> untuk mendapatkan versi yang lebih up to date.</p>
</blockquote>
<p>Saat ini, framework front end yang kami gunakan di ArtiVisi, AngularJS, <a href="https://angular.io/">sudah merilis versi 2</a>. Walaupun saat ini masih berstatus Beta, tapi menurut developernya <a href="http://angularjs.blogspot.co.id/2015/12/angular-2-beta.html">sudah bisa digunakan untuk aplikasi berskala besar</a>.</p>
<p>Menurut kabar berita, AngularJS versi 2 ini berbeda 180 derajat dengan versi 1 yang kami gunakan. Sekilas lihat, ternyata memang benar. AngularJS versi 2 ini menggunakan bahasa TypeScript, bukan JavaScript. Kode programnya malah lebih mirip Java daripada JavaScript.</p>
<p>Mumpung perbedaannya sangat signifikan, momen ini bisa digunakan untuk mengevaluasi alternatif lain. Setidaknya ada beberapa kandidat yang sebanding kelasnya (full framework, bukan library):</p>
<ul>
<li><a href="http://emberjs.com/">Ember JS</a></li>
<li><a href="http://aurelia.io/">Aurelia</a></li>
<li><a href="https://facebook.github.io/react/">React JS</a></li>
</ul>
<p>Bagaimana perbandingannya?</p>
<!--more-->
<p>Tentunya urusan membandingkan framework ini tidak ada habisnya diperdebatkan orang. Oleh karena itu saya juga tidak berpanjang lebar membahasnya. Silahkan baca beberapa artikel ini untuk lebih jelasnya:</p>
<ul>
<li><a href="https://www.quora.com/What-are-the-pros-and-cons-of-Angular-2-0-and-Ember-2-0">Angular 2 vs Ember JS</a></li>
<li><a href="http://www.creativebloq.com/web-design/react-goes-head-head-emberjs-31514361">Ember JS vs React</a></li>
<li><a href="https://medium.freecodecamp.com/angular-2-versus-react-there-will-be-blood-66595faafd51">Angular 2 vs React</a></li>
<li><a href="http://smashingboxes.com/blog/choosing-a-front-end-framework-angular-ember-react">Membandingkan ketiganya</a></li>
</ul>
<p>Pada akhirnya, saya berkesimpulan Angular 2 masih merupakan pilihan paling optimal dengan dua pertimbangan berikut:</p>
<ul>
<li>migrasi dari Angular 1 ke React justru lebih sulit daripada Angular 1 ke Angular 2</li>
<li>Ember JS dan React masih menggunakan syntax dan model pemrograman ES5. Jadi masih ada kemungkinan dia akan mengalami perubahan signifikan untuk mengadopsi fitur-fitur baru di ES6 dan selanjutnya.</li>
</ul>
<p>Sebagai contoh, berikut kode program Ember JS (diambil dari websitenya)</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">App</span><span class="p">.</span><span class="nx">GravatarImageComponent</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Component</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="na">size</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
<span class="na">email</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
<span class="na">gravatarUrl</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">computed</span><span class="p">(</span><span class="dl">'</span><span class="s1">email</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">size</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">email</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">email</span><span class="dl">'</span><span class="p">).</span><span class="nx">toLowerCase</span><span class="p">(),</span>
<span class="nx">size</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">size</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return</span> <span class="dl">'</span><span class="s1">http://www.gravatar.com/avatar/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">md5</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">?s=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">size</span><span class="p">;</span>
<span class="p">})</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Penggunaan inheritance masih menggunakan function, bukan keyword <code class="language-plaintext highlighter-rouge">extends</code>. Bila menggunakan model baru maka kira-kira akan seperti ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">Component</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">Ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">GravatarImageComponent</span> <span class="kd">extends</span> <span class="nx">Component</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Dan berikut contoh kode program React yang juga diambil dari websitenya. Sama saja bukan?</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">HelloMessage</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createClass</span><span class="p">({</span>
<span class="na">render</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="nx">Hello</span> <span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="o"><</span><span class="sr">/div></span><span class="err">;
</span> <span class="p">}</span>
<span class="p">});</span>
<span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="o"><</span><span class="nx">HelloMessage</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">John</span><span class="dl">"</span> <span class="o">/></span><span class="p">,</span> <span class="nx">mountNode</span><span class="p">);</span>
</code></pre></div></div>
<p>Nah, tinggal tunggu waktu saja perubahan ini terjadi. Maka lebih baik kita adopsi Angular 2 yang sudah mengadopsi syntax baru.</p>
<p>Selanjutnya, mari kita bahas cara menyiapkan project AngularJS 2. Kita asumsikan NodeJS dan <code class="language-plaintext highlighter-rouge">npm</code> sudah terinstal. Kita juga akan menggunakan package manager <a href="http://jspm.io/">JSPM</a>. Kita tidak menggunakan Grunt, Gulp, dan sejenisnya karena terlalu kompleks. Setup yang akan saya jelaskan di sini sejauh ini sudah yang paling sederhana.</p>
<h2 id="instalasi-nodejs">Instalasi NodeJS</h2>
<p>Kita membutuhkan aplikasi <code class="language-plaintext highlighter-rouge">npm</code> (Node Package Manager) yang ada dalam NodeJS. Instalasi NodeJS bisa dilakukan dengan perintah berikut (MacOSX)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install node
</code></pre></div></div>
<p>Untuk Ubuntu, berikut perintah instalasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs
</code></pre></div></div>
<h2 id="instalasi-jspm">Instalasi JSPM</h2>
<p>JSPM digunakan untuk mengelola library yang kita gunakan dalam project Angular 2. Kira-kira mirip dengan <a href="http://maven.apache.org">Maven di Java</a>, <a href="https://rubygems.org/">Gem di Ruby</a>, atau <a href="https://getcomposer.org/">Composer di PHP</a>.</p>
<p>Berikut perintah untuk menginstalnya. Kita instal secara global dengan opsi <code class="language-plaintext highlighter-rouge">-g</code> supaya bisa dijalankan dari command line.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install -g jspm
</code></pre></div></div>
<h2 id="membuat-struktur-folder-project">Membuat Struktur Folder Project</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir angular2-jspm
cd angular2-jspm
touch index.html
mkdir -p aplikasi/halo
touch aplikasi/boot.ts
touch aplikasi/halo/halo.component.{html,ts}
</code></pre></div></div>
<p>Perintah di atas akan menghasilkan struktur folder seperti ini</p>
<p><a href="https://github.com/endymuhardin/belajar-angular2/raw/master/img/struktur-folder-angular2-jspm.png"><img src="https://github.com/endymuhardin/belajar-angular2/raw/master/img/struktur-folder-angular2-jspm.png" alt="Struktur folder project AngularJS 2" /></a></p>
<h2 id="inisialisasi-project-dengan-jspm">Inisialisasi Project dengan JSPM</h2>
<p>Jalankan perintah inisialisasi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jspm init
</code></pre></div></div>
<p>Kemudian jawab pertanyaan yang diajukan JSPM. Ikuti default kecuali opsi terakhir dimana kita memilih <code class="language-plaintext highlighter-rouge">typescript</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Package.json file does not exist, create it? [yes]:
Would you like jspm to prefix the jspm package.json properties under jspm? [yes]:
Enter server baseURL (public folder path) [./]:
Enter jspm packages folder [./jspm_packages]:
Enter config file path [./config.js]:
Configuration file config.js doesn't exist, create it? [yes]:
Enter client baseURL (public folder URL) [/]:
Do you wish to use a transpiler? [yes]:
Which ES6 transpiler would you like to use, Babel, TypeScript or Traceur? [babel]:typescript
ok Verified package.json at package.json
Verified config file at config.js
Looking up loader files...
system.js
system-csp-production.js
system.js.map
system.src.js
system-csp-production.js.map
system-polyfills.js
system-polyfills.src.js
system-polyfills.js.map
system-csp-production.src.js
Using loader versions:
systemjs@0.19.17
Looking up npm:typescript
Updating registry cache...
ok Installed typescript as npm:typescript@^1.6.2 (1.8.0)
ok Loader files downloaded successfully
</code></pre></div></div>
<p>Kita juga perlu menginstal JSPM secara lokal dalam project kita, supaya lebih reliable untuk dijalankan di komputer lain.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install --save-dev jspm
</code></pre></div></div>
<h2 id="instalasi-angularjs-2">Instalasi AngularJS 2</h2>
<p>AngularJS perlu diinstal dua kali, karena integrasi antara TypeScript dengan JSPM belum sempurna. Penjelasannya ada di <a href="https://github.com/Microsoft/TypeScript/issues/6012">Issue TypeScript dalam Github mengenai Module Resolution</a>. Kalau issue ini sudah beres, maka nanti akan kita lihat kembali bagaimana solusinya. Sementara ini, AngularJS dan teman-temannya harus diinstal di JSPM (agar bisa dibundle untuk production) dan juga di NPM (supaya autocomplete di editor berjalan dengan baik). Instalasi ini tidak saling berhubungan, terserah mau menjalankan yang mana duluan.</p>
<h3 id="instalasi-angularjs-di-npm">Instalasi AngularJS di NPM</h3>
<p>Karena AngularJS masih beta, maka versinya berubah-ubah tergantung kapan artikel ini dibaca. Demikian juga teman-temannya. Untuk mendapatkan versi terbaru, kita perlu trial and error.</p>
<p>Pertama coba install dulu paket angular2 untuk mendapatkan nomer versi terbaru</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install --save angular2
</code></pre></div></div>
<p>Dia akan mengeluarkan pesan error karena teman-temannya tidak ada</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── angular2@2.0.0-beta.2 extraneous
├── UNMET PEER DEPENDENCY es6-promise@^3.0.2
├── UNMET PEER DEPENDENCY es6-shim@^0.33.3
├── UNMET PEER DEPENDENCY reflect-metadata@0.1.2
├── UNMET PEER DEPENDENCY rxjs@5.0.0-beta.0
└── UNMET PEER DEPENDENCY zone.js@0.5.10
npm WARN angular2@2.0.0-beta.2 requires a peer of es6-promise@^3.0.2 but none was installed.
npm WARN angular2@2.0.0-beta.2 requires a peer of es6-shim@^0.33.3 but none was installed.
npm WARN angular2@2.0.0-beta.2 requires a peer of reflect-metadata@0.1.2 but none was installed.
npm WARN angular2@2.0.0-beta.2 requires a peer of rxjs@5.0.0-beta.0 but none was installed.
npm WARN angular2@2.0.0-beta.2 requires a peer of zone.js@0.5.10 but none was installed.
npm WARN angular2-jspm No description
npm WARN angular2-jspm No repository field.
npm WARN angular2-jspm No license field.
</code></pre></div></div>
<p>Dari sini kita bisa mengetahui nomer versi yang dibutuhkan. Lakukan instalasi sekali lagi dengan lebih lengkap</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install --save angular2 es6-promise@^3.0.2 es6-shim@^0.33.3 reflect-metadata@0.1.2 rxjs@5.0.0-beta.0 zone.js@0.5.10
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── es6-promise@3.0.2
├── es6-shim@0.33.13
├── reflect-metadata@0.1.2
├── rxjs@5.0.0-beta.0
└── zone.js@0.5.10
npm WARN angular2-jspm No description
npm WARN angular2-jspm No repository field.
npm WARN angular2-jspm No license field.
</code></pre></div></div>
<h3 id="instalasi-angularjs-di-jspm">Instalasi AngularJS di JSPM</h3>
<p>Dependensi manajemen dalam paket AngularJS 2 sepertinya belum sempurna, sehingga kita harus juga menyebutkan nama teman-temannya. Padahal seharusnya ini sudah ditangani oleh JSPM.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jspm install angular2 zone.js reflect-metadata
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Updating registry cache...
Looking up npm:angular2
Looking up npm:es6-promise
Looking up npm:es6-shim
Looking up npm:reflect-metadata
Looking up npm:rxjs
Looking up npm:zone.js
ok Installed npm:es6-promise@^3.0.2 (3.0.2)
ok Installed npm:es6-shim@^0.33.3 (0.33.13)
ok Installed npm:zone.js@0.5.10 (0.5.10)
ok Installed npm:reflect-metadata@0.1.2 (0.1.2)
ok Installed npm:rxjs@5.0.0-beta.0 (5.0.0-beta.0)
Looking up github:jspm/nodelibs-process
Looking up github:jspm/nodelibs-crypto
Looking up github:jspm/nodelibs-buffer
Looking up github:jspm/nodelibs-assert
Looking up npm:process
ok Installed github:jspm/nodelibs-process@^0.1.0 (0.1.2)
Looking up npm:crypto-browserify
ok Installed github:jspm/nodelibs-crypto@^0.1.0 (0.1.0)
ok Installed npm:process@^0.11.0 (0.11.2)
Looking up npm:buffer
ok Installed github:jspm/nodelibs-buffer@^0.1.0 (0.1.0)
Looking up npm:assert
ok Installed github:jspm/nodelibs-assert@^0.1.0 (0.1.0)
Looking up npm:base64-js
Looking up npm:ieee754
Looking up npm:isarray
ok Installed npm:buffer@^3.0.1 (3.6.0)
Looking up npm:browserify-cipher
Looking up npm:browserify-sign
Looking up npm:create-ecdh
Looking up npm:create-hash
Looking up npm:create-hmac
Looking up npm:diffie-hellman
Looking up npm:inherits
Looking up npm:pbkdf2
Looking up npm:public-encrypt
Looking up npm:randombytes
ok Installed npm:crypto-browserify@^3.7.2 (3.11.0)
Looking up npm:util
ok Installed npm:assert@^1.3.0 (1.3.0)
ok Installed npm:isarray@^1.0.0 (1.0.0)
ok Installed npm:ieee754@^1.1.4 (1.1.6)
ok Installed npm:base64-js@0.0.8 (0.0.8)
ok Installed npm:inherits@^2.0.1 (2.0.1)
ok Installed npm:util@0.10.3 (0.10.3)
Looking up npm:bn.js
Looking up npm:browserify-rsa
Looking up npm:parse-asn1
Looking up npm:elliptic
Looking up npm:browserify-aes
Looking up npm:browserify-des
Looking up npm:evp_bytestokey
ok Installed npm:public-encrypt@^4.0.0 (4.0.0)
Looking up npm:cipher-base
Looking up npm:ripemd160
Looking up npm:sha.js
ok Installed npm:browserify-cipher@^1.0.0 (1.0.0)
ok Installed npm:browserify-sign@^4.0.0 (4.0.0)
ok Installed npm:create-ecdh@^4.0.0 (4.0.0)
ok Installed npm:randombytes@^2.0.0 (2.0.2)
ok Installed npm:create-hash@^1.1.0 (1.1.2)
ok Installed npm:create-hmac@^1.1.0 (1.1.4)
ok Installed npm:pbkdf2@^3.0.3 (3.0.4)
Looking up npm:buffer-xor
Looking up npm:brorand
Looking up npm:hash.js
Looking up npm:asn1.js
Looking up npm:des.js
ok Installed npm:browserify-aes@^1.0.4 (1.0.6)
Looking up github:jspm/nodelibs-child_process
ok Installed npm:bn.js@^4.1.0 (4.10.0)
ok Installed npm:elliptic@^6.0.0 (6.2.3)
ok Installed npm:browserify-des@^1.0.0 (1.0.0)
ok Installed npm:evp_bytestokey@^1.0.0 (1.0.0)
ok Installed npm:parse-asn1@^5.0.0 (5.0.0)
Looking up github:jspm/nodelibs-fs
ok Installed npm:cipher-base@^1.0.1 (1.0.2)
ok Installed npm:sha.js@^2.3.6 (2.4.4)
ok Installed npm:ripemd160@^1.0.0 (1.0.1)
Looking up npm:miller-rabin
ok Installed npm:diffie-hellman@^5.0.0 (5.0.2)
Looking up npm:minimalistic-assert
Looking up github:jspm/nodelibs-util
ok Installed npm:buffer-xor@^1.0.2 (1.0.3)
ok Installed npm:des.js@^1.0.0 (1.0.0)
ok Installed npm:hash.js@^1.0.0 (1.0.3)
ok Installed npm:asn1.js@^4.0.0 (4.3.1)
Looking up github:jspm/nodelibs-stream
Looking up github:jspm/nodelibs-path
Looking up github:systemjs/plugin-json
ok Installed npm:browserify-rsa@^4.0.0 (4.0.0)
ok Installed npm:miller-rabin@^4.0.0 (4.0.0)
ok Installed npm:brorand@^1.0.1 (1.0.5)
ok Installed npm:minimalistic-assert@^1.0.0 (1.0.0)
Looking up github:jspm/nodelibs-string_decoder
Looking up github:jspm/nodelibs-vm
Looking up github:jspm/nodelibs-constants
ok Installed github:jspm/nodelibs-child_process@^0.1.0 (0.1.0)
ok Installed github:jspm/nodelibs-fs@^0.1.0 (0.1.2)
ok Installed github:jspm/nodelibs-util@^0.1.0 (0.1.0)
Looking up npm:path-browserify
ok Installed github:jspm/nodelibs-path@^0.1.0 (0.1.0)
Looking up npm:stream-browserify
ok Installed github:jspm/nodelibs-stream@^0.1.0 (0.1.0)
ok Installed github:systemjs/plugin-json@^0.1.0 (0.1.0)
ok Installed npm:path-browserify@0.0.0 (0.0.0)
Looking up npm:readable-stream
ok Installed npm:stream-browserify@^1.0.0 (1.0.0)
Looking up npm:core-util-is
Looking up npm:string_decoder
ok Installed npm:readable-stream@^1.0.27-1 (1.1.13)
ok Installed npm:isarray@0.0.1 (0.0.1)
ok Installed npm:string_decoder@~0.10.0 (0.10.31)
ok Installed npm:core-util-is@~1.0.0 (1.0.2)
ok Installed github:jspm/nodelibs-string_decoder@^0.1.0 (0.1.0)
Looking up npm:vm-browserify
ok Installed github:jspm/nodelibs-vm@^0.1.0 (0.1.0)
Looking up npm:constants-browserify
ok Installed github:jspm/nodelibs-constants@^0.1.0 (0.1.0)
Looking up github:jspm/nodelibs-events
Looking up npm:indexof
ok Installed npm:vm-browserify@0.0.4 (0.0.4)
ok Installed npm:constants-browserify@0.0.1 (0.0.1)
ok Installed npm:indexof@0.0.1 (0.0.1)
Looking up npm:events
ok Installed github:jspm/nodelibs-events@^0.1.1 (0.1.1)
ok Installed npm:events@1.0.2 (1.0.2)
ok Installed angular2 as npm:angular2@^2.0.0-beta.2 (2.0.0-beta.2)
Installed Forks
npm:isarray 0.0.1 1.0.0
To inspect individual package constraints, use jspm inspect registry:name.
Looking up loader files...
system.js
system.js.map
system-csp-production.js
system.src.js
system-csp-production.js.map
system-polyfills.src.js
system-csp-production.src.js
system-polyfills.js.map
system-polyfills.js
Using loader versions:
systemjs@0.19.18
ok Loader files downloaded successfully
ok Install complete.
</code></pre></div></div>
<p>Kita butuh plugin <code class="language-plaintext highlighter-rouge">ts</code> supaya browser bisa langsung menjalankan file TypeScript tanpa kompilasi. Konfigurasi ini berbeda dengan workflow pada <a href="../halo-angular2">project halo-angular2</a> dimana file <code class="language-plaintext highlighter-rouge">*.ts</code> dikompilasi menjadi <code class="language-plaintext highlighter-rouge">*.js</code> baru dijalankan di browser. Pada workflow JSPM ini, file <code class="language-plaintext highlighter-rouge">*.ts</code> langsung dijalankan menggunakan plugin <code class="language-plaintext highlighter-rouge">ts</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jspm install ts
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Updating registry cache...
Looking up github:frankwallis/plugin-typescript
Looking up npm:typescript
ok Installed npm:typescript@1.7.5 (1.7.5)
ok Installed ts as github:frankwallis/plugin-typescript@^2.5.9 (2.5.9)
Clearing configuration for npm:typescript@1.8.0
Removing package files for npm:typescript@1.8.0
The following existing package versions were altered by install deduping:
typescript 1.8.0 -> 1.7.5
To keep existing dependencies locked during install, use the --lock option.
Installed Forks
npm:isarray 0.0.1 1.0.0
To inspect individual package constraints, use jspm inspect registry:name.
ok Installed typescript as npm:typescript@^1.6.2 (1.7.5)
ok Install complete.
</code></pre></div></div>
<p>Selanjutnya, kita edit <code class="language-plaintext highlighter-rouge">config.js</code> untuk mengkonfigurasi project kita dan mengaktifkan plugin <code class="language-plaintext highlighter-rouge">ts</code> tadi. Tambahkan baris berikut di bawah <code class="language-plaintext highlighter-rouge">transpiler: "typescript"</code></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">typescriptOptions</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">tsconfig</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">},</span>
<span class="nx">packages</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">aplikasi</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">main</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">boot</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">defaultExtension</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ts</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">meta</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">*.ts</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">loader</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ts</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span>
</code></pre></div></div>
<p>File di atas berisi:</p>
<ul>
<li>gunakan konfigurasi yang tertulis di <code class="language-plaintext highlighter-rouge">tsconfig</code></li>
<li>konfigurasi aplikasi yang akan diload, terdiri dari satu modul bernama <code class="language-plaintext highlighter-rouge">aplikasi</code> dengan titik awal file <code class="language-plaintext highlighter-rouge">boot.ts</code>. Modul ini akan diload menggunakan plugin <code class="language-plaintext highlighter-rouge">ts</code></li>
</ul>
<h2 id="konfigurasi-typescript-compiler">Konfigurasi TypeScript Compiler</h2>
<p>TypeScript compiler dikonfigurasi dengan file <code class="language-plaintext highlighter-rouge">tsconfig.json</code>. File ini akan dibaca oleh compiler dan digunakan oleh editor untuk menyediakan autocomplete dan error-checking. Berikut isi filenya</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="dl">"</span><span class="s2">compilerOptions</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">target</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">es5</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">module</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">system</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">moduleResolution</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">node</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">sourceMap</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">emitDecoratorMetadata</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">experimentalDecorators</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">removeComments</span><span class="dl">"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">noImplicitAny</span><span class="dl">"</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">exclude</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span>
<span class="dl">"</span><span class="s2">node_modules</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">jspm_packages</span><span class="dl">"</span>
<span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Berikut adalah hal-hal yang dikonfigurasi dalam file di atas:</p>
<ul>
<li>kode program yang ditulis dalam bahasa TypeScript akan dikompilasi menjadi kode program JavaScript dengan versi ES5</li>
<li>sistem modul yang digunakan adalah formatnya <code class="language-plaintext highlighter-rouge">SystemJS</code>. Lihat <a href="http://software.endy.muhardin.com/javascript/javascript-2015/">artikel sebelumnya</a> mengenai berbagai format modul</li>
<li>buat source map, supaya bila ada error bisa ditelusuri ke baris kode program yang menyebabkannya</li>
<li>proses decorator <code class="language-plaintext highlighter-rouge">@Component</code>, <code class="language-plaintext highlighter-rouge">@Injectable</code>, dan decorator Angular lainnya</li>
<li>jangan hilangkan komentar dalam source code</li>
<li>secara default, bila kita tidak menyebutkan tipe data, maka akan diset menjadi tipe data <code class="language-plaintext highlighter-rouge">any</code></li>
<li>jangan lakukan kompilasi (exclude) di folder <code class="language-plaintext highlighter-rouge">node_modules</code> dan <code class="language-plaintext highlighter-rouge">jspm_packages</code></li>
</ul>
<h2 id="membuat-aplikasi">Membuat Aplikasi</h2>
<p>Aplikasi kita akan terdiri dari beberapa file:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">index.html</code> : halaman HTML yang memuat Single Page Application kita</li>
<li><code class="language-plaintext highlighter-rouge">aplikasi/boot.ts</code> : titik awal aplikasi</li>
<li><code class="language-plaintext highlighter-rouge">aplikasi/halo/halo.component.html</code> : template untuk komponen AngularJS 2</li>
<li><code class="language-plaintext highlighter-rouge">aplikasi/halo/halo.component.ts</code> : kode program TypeScript untuk komponen AngularJS 2</li>
</ul>
<p>Berikut isi file <code class="language-plaintext highlighter-rouge">index.html</code></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!doctype html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Halo Angular 2<span class="nt"></title></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"jspm_packages/system.js"</span> <span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"config.js"</span> <span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Loading apps</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">System</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="dl">'</span><span class="s1">aplikasi/boot</span><span class="dl">'</span><span class="p">);</span>
<span class="nt"></script></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><h1></span>Halo Angular 2<span class="nt"></h1></span>
<span class="nt"><halo></halo></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>Ada beberapa hal yang kita lakukan pada file di atas</p>
<ul>
<li>Loading library SystemJS</li>
<li>Loading konfigurasi JSPM dalam file <code class="language-plaintext highlighter-rouge">config.js</code></li>
<li>Menjalankan titik awal aplikasi AngularJS yang berada di file <code class="language-plaintext highlighter-rouge">aplikasi/boot.ts</code></li>
<li>Menampilkan komponen halo dalam <code class="language-plaintext highlighter-rouge"><halo></halo></code></li>
</ul>
<p>Selanjutnya, kita lihat file <code class="language-plaintext highlighter-rouge">aplikasi/boot.ts</code></p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="dl">'</span><span class="s1">reflect-metadata</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">bootstrap</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">angular2/platform/browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">HaloComponent</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./halo/halo.component</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Bootstrapping Angular 2</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">bootstrap</span><span class="p">(</span><span class="nx">HaloComponent</span><span class="p">);</span>
</code></pre></div></div>
<p>Ada beberapa hal yang kita lakukan pada file di atas:</p>
<ul>
<li>import library <code class="language-plaintext highlighter-rouge">reflect-metadata</code>. Bila tidak dilakukan, ini akan menimbulkan error <code class="language-plaintext highlighter-rouge">Uncaught reflect-metadata shim is required when using class decorators</code>.</li>
<li>import function bootstrap milik Angular</li>
<li>import class component yang kita buat</li>
<li>jalankan komponen kita dengan function bootstrap</li>
</ul>
<p>Berikutnya, kita lihat kode program <code class="language-plaintext highlighter-rouge">HaloComponent</code></p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">Component</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">angular2/core</span><span class="dl">"</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">halo</span><span class="dl">'</span><span class="p">,</span>
<span class="na">templateUrl</span><span class="p">:</span> <span class="dl">"</span><span class="s2">./aplikasi/halo/halo.component.html</span><span class="dl">"</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">HaloComponent</span><span class="p">{</span>
<span class="nl">nama</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="kd">constructor</span><span class="p">(){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">nama</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Endy</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Halo </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">nama</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Beberapa hal yang dilakukan dalam kode program tersebut</p>
<ul>
<li>import annotation <code class="language-plaintext highlighter-rouge">@Component</code></li>
<li>tentukan tempat komponen ini ditampilkan (selector), yaitu dalam <code class="language-plaintext highlighter-rouge"><halo></halo></code></li>
<li>template HTML untuk komponen ini, yaitu dalam file <code class="language-plaintext highlighter-rouge">halo.component.html</code></li>
<li>instance variable bertipe <code class="language-plaintext highlighter-rouge">string</code> untuk ditampilkan dalam template</li>
<li>constructor untuk menginisialisasi objek yang akan dibuat. Isinya sederhana saja, yaitu menulis ke log dan mengisi variabel <code class="language-plaintext highlighter-rouge">nama</code></li>
</ul>
<p>Komponen di atas membutuhkan template HTML. Berikut isinya</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><h2></span>Halo {{nama}}<span class="nt"></h2></span>
</code></pre></div></div>
<p>Dalam file HTML di atas, kita menampilkan isi variabel <code class="language-plaintext highlighter-rouge">nama</code> di dalam elemen <code class="language-plaintext highlighter-rouge"><h2></code>.</p>
<h2 id="menjalankan-aplikasi">Menjalankan Aplikasi</h2>
<p>Untuk menjalankan aplikasi di atas, kita membutuhkan web server. Sebetulnya web server apapun bisa, boleh pakai Nginx, Lighttpd, Apache HTTPD, Apache Tomcat, Jetty, terserahlah mau pakai apa. Tapi untuk memudahkan, kita bisa pakai webserver berbasis JavaScript dengan nama paket <code class="language-plaintext highlighter-rouge">http-server</code>. Mari kita install dulu</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install -g http-server
</code></pre></div></div>
<p>Setelah selesai, kita jalankan webservernya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http-server
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Starting up http-server, serving ./
Available on:
http:127.0.0.1:8080
http:192.168.1.193:8080
Hit CTRL-C to stop the server
</code></pre></div></div>
<p>Kita bisa lihat hasilnya dengan membuka <code class="language-plaintext highlighter-rouge">http://localhost:8080</code> di browser. Jangan lupa tampilkan Developer Tools agar terlihat output dari <code class="language-plaintext highlighter-rouge">console.log</code></p>
<p><a href="https://github.com/endymuhardin/belajar-angular2/raw/master/img/angular2-jspm-output.png"><img src="https://github.com/endymuhardin/belajar-angular2/raw/master/img/angular2-jspm-output.png" alt="Browser Output" /></a></p>
<h2 id="menggunakan-twitter-bootstrap">Menggunakan Twitter Bootstrap</h2>
<p>JSPM juga memiliki kemampuan untuk menambahkan paket-paket populer dalam aplikasi kita. Sebagai contoh, bila kita ingin menggunakan framework CSS <a href="http://getbootstrap.com/">Twitter Bootstrap</a>, kita cari dulu nama paketnya di <a href="https://github.com/jspm/registry/blob/master/registry.json">registry JSPM</a>. Dari situ kita bisa mendapatkan bahwa nama paketnya adalah <code class="language-plaintext highlighter-rouge">bootstrap</code>.</p>
<p>Selanjutnya, kita install paketnya dalam konfigurasi dependensi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jspm install bootstrap
</code></pre></div></div>
<p>Jangan lupa install juga plugin SystemJS agar dia bisa memproses file CSS</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jspm install css
</code></pre></div></div>
<p>Selanjutnya, kita tinggal import saja menggunakan sintaks modul ES6 di <code class="language-plaintext highlighter-rouge">boot.ts</code></p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="dl">'</span><span class="s1">bootstrap/css/bootstrap.css!</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div>
<p>Kita menggunakan tanda seru <code class="language-plaintext highlighter-rouge">!</code> di akhir import untuk menunjukkan bahwa import tersebut harus diproses menggunakan plugin. Nama plugin yang menanganinya diambil dari ekstensi filenya (yaitu <code class="language-plaintext highlighter-rouge">css</code>).</p>
<p>Selain CSS yang sudah jadi seperti Bootstrap, kita juga bisa import file CSS kita sendiri sesuai modul yang membutuhkannya. Caranya sama, misalnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import './halo/halo.css!';
</code></pre></div></div>
<p>Dengan demikian, kita bisa membuat file CSS yang modular, diload sesuai modul/komponen yang membutuhkannya.</p>
<h2 id="production-build">Production Build</h2>
<p>Kalau kita lihat di Network Tab pada Developer Tools, kita akan melihat banyak sekali file yang diload. Untuk production deployment, ini kurang optimal karena banyak terjadi bolak-balik request ke server. Solusinya, kita perlu menggabungkan semua kode program kita dan library yang digunakan menjadi satu file.</p>
<p><a href="https://github.com/endymuhardin/belajar-angular2/raw/master/img/200an-request.png"><img src="https://github.com/endymuhardin/belajar-angular2/raw/master/img/200an-request.png" alt="200an request" /></a></p>
<p>JSPM sudah menyediakan <a href="http://jspm.io/docs/production-workflows.html">prosedur untuk membuat production deployment</a>. Jalankan perintah berikut untuk menjalankannya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jspm bundle-sfx --minify aplikasi/boot.ts aplikasi.js
</code></pre></div></div>
<p>Selanjutnya, kita ubah loading script dalam <code class="language-plaintext highlighter-rouge">index.html</code>. Hapus baris berikut</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script </span><span class="na">src=</span><span class="s">"jspm_packages/system.js"</span> <span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"config.js"</span> <span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Loading apps</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">System</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="dl">'</span><span class="s1">aplikasi/boot</span><span class="dl">'</span><span class="p">);</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>dan ganti dengan ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script src="aplikasi.js" type="text/javascript"></script>
</code></pre></div></div>
<p>Hasilnya, 200an request tadi menjadi 3 request saja</p>
<p><a href="https://github.com/endymuhardin/belajar-angular2/raw/master/img/3-request.png"><img src="https://github.com/endymuhardin/belajar-angular2/raw/master/img/3-request.png" alt="Production build dan minify" /></a></p>
<p>Khusus untuk file CSS, kita membutuhkan plugin tambahan, yaitu <code class="language-plaintext highlighter-rouge">clean-css</code>. Tanpa plugin ini, maka kita akan mendapatkan error</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Building the single-file sfx bundle for aplikasi/boot.ts...
err Error: Install Clean CSS via jspm install npm:clean-css --dev for CSS build support. Set System.buildCSS = false to skip CSS builds.
at file:///Users/endymuhardin/workspace/belajar/belajar-angular2/angular2-jspm/jspm_packages/github/systemjs/plugin-css@0.1.20/css-builder.js:95:13
</code></pre></div></div>
<p>Untuk itu, kita install dulu plugin <code class="language-plaintext highlighter-rouge">clean-css</code> sesuai instruksi di atas</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jspm install npm:clean-css --dev
</code></pre></div></div>
<p>Setelah plugin tersebut terinstall, maka proses build bisa berjalan dengan lancar.</p>
<p>Kita juga bisa menambahkan konfigurasi agar file CSS dibuat terpisah dengan file JS, dengan cara menambahkan baris berikut pada <code class="language-plaintext highlighter-rouge">config.js</code></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">separateCSS</span><span class="p">:</span> <span class="kc">true</span>
</code></pre></div></div>
<p>Berikut posisi baris tersebut dalam file <code class="language-plaintext highlighter-rouge">config.js</code></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">System</span><span class="p">.</span><span class="nx">config</span><span class="p">({</span>
<span class="na">baseURL</span><span class="p">:</span> <span class="dl">"</span><span class="s2">/</span><span class="dl">"</span><span class="p">,</span>
<span class="na">defaultJSExtensions</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">transpiler</span><span class="p">:</span> <span class="dl">"</span><span class="s2">typescript</span><span class="dl">"</span><span class="p">,</span>
<span class="na">typescriptOptions</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">tsconfig</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">},</span>
<span class="na">paths</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">npm:*</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">jspm_packages/npm/*</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">github:*</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">jspm_packages/github/*</span><span class="dl">"</span>
<span class="p">},</span>
<span class="na">separateCSS</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">packages</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">aplikasi</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">main</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">boot</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">defaultExtension</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ts</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">meta</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">*.ts</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">loader</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ts</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">map</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">// isi map hasil generate JSPM</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Dengan konfigurasi tersebut, kode program CSS akan dibuat dalam file <code class="language-plaintext highlighter-rouge">aplikasi.css</code> dan <code class="language-plaintext highlighter-rouge">aplikasi.css.map</code>. Kita tinggal memasangnya di <code class="language-plaintext highlighter-rouge">index.html</code> seperti biasa</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><link</span> <span class="na">href=</span><span class="s">"aplikasi.css"</span> <span class="na">rel=</span><span class="s">"stylesheet"</span><span class="nt">></span>
</code></pre></div></div>
<h2 id="portability-project">Portability Project</h2>
<p>Portability artinya project yang jalan di komputer kita bisa juga jalan di komputer orang lain. Jalan di sini maksudnya aplikasi bisa dibuka di editor, bisa menggunakan autocomplete, error checking, bisa dikompilasi, dan juga bisa dijalankan.</p>
<p>Dengan JSPM dan NPM, project kita sudah memiliki portability. Cukup jalankan perintah berikut setelah mengunduh kode program atau melakukan git clone</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install
jspm install
</code></pre></div></div>
<p>Setelah itu, bisa langsung menjalankan aplikasi dengan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http-server
</code></pre></div></div>
<p>ataupun membukanya di editor.</p>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah persiapan untuk memulai project AngularJS versi 2. Kode program selengkapnya ada <a href="https://github.com/endymuhardin/belajar-angular2">di Github</a>.</p>
JavaScript 20152016-01-05T13:37:00+07:00https://software.endy.muhardin.com/javascript/javascript-2015<p>Beberapa minggu yang lalu, tim AngularJS merilis versi Beta dari AngularJS versi 2. Masih belum rilis Stable, tapi mereka menjanjikan bahwa versi ini cukup stabil untuk dipakai di production.</p>
<p>Seperti pada <a href="http://software.endy.muhardin.com/life/otodidak/">tulisan saya sebelumnya</a>, seorang programmer harus selalu siap mempelajari teknologi baru. Setelah melihat tutorial Angular 2, kembali tulisan saya tadi terbukti. Banyak sekali teknologi baru yang belum saya pahami sejak terakhir <a href="http://software.endy.muhardin.com/java/development-stack-2014/">eksplorasi teknologi di tahun 2014</a>, padahal baru 2 tahun berlalu.</p>
<blockquote>
<p>Apa saja teknologi baru tersebut?</p>
</blockquote>
<p>Pada artikel ini, kita hanya akan bahas di sisi front-end saja, yaitu client-side JavaScript. Pada lain waktu kita akan bahas di sisi server-side.</p>
<p>Beberapa teknologi yang baru saya pelajari dengan rilisnya Angular 2 Beta ini adalah:</p>
<ul>
<li>Berbagai varian JavaScript : ES5, ES6, CoffeeScript, TypeScript</li>
<li>Berbagai sistem modul untuk JavaScript, misalnya : CommonJS, RequireJS, AMD, dan ESM (fitur standar dari ES6)</li>
<li>Berbagai library untuk melakukan loading modul, misalnya: browserify, webpack, SystemJS</li>
<li>Berbagai framework untuk membuat aplikasi, misalnya: AngularJS, EmberJS, React, Flux</li>
<li>Berbagai tools untuk melakukan test, misalnya: Karma, Jasmine, PhantomJS</li>
<li>Berbagai tools untuk melakukan proses build dan otomasi, misalnya: Gulp, Grunt, Brunch, Broccoli, Jake, dsb</li>
</ul>
<p><a href="https://lh3.googleusercontent.com/XwoAQ_6PsevoJ6Q8SqV6PzQTTg0toHYgPkjvv4aQXh8Cjb3WzkCPf6kbwJCoFhREpNgt0Zo2iZPJ=w1280-no"><img src="https://lh3.googleusercontent.com/XwoAQ_6PsevoJ6Q8SqV6PzQTTg0toHYgPkjvv4aQXh8Cjb3WzkCPf6kbwJCoFhREpNgt0Zo2iZPJ=w1280-no" alt="JavaScript Stack 2015" /></a></p>
<blockquote>
<p>Wah banyak sekali?</p>
</blockquote>
<p>Jangan khawatir, pada tulisan ini, kita akan kupas tuntas garis besarnya.</p>
<blockquote>
<p>Kenapa judul artikelnya 2015? Kan sekarang sudah 2016.</p>
</blockquote>
<p>Semua yang dibahas di sini adalah kondisi existing di tahun 2015. Di tahun 2016 ini, tentunya akan banyak perkembangan yang lebih canggih lagi.</p>
<!--more-->
<h2 id="varian-bahasa-javascript">Varian bahasa JavaScript</h2>
<p>JavaScript di jaman sekarang pada umumnya dibedakan menjadi dua jenis : server-side dan client-side. Server-side maksudnya yang dijalankan tanpa browser, mirip dengan bahasa pemrograman lain seperti Ruby, Python, PHP, Java, dan sebagainya. Sedangkan client-side maksudnya yang berjalan di dalam browser pada saat kita membuka halaman web.</p>
<p>Karena dia berjalan di browser, maka apa saja fitur dari bahasa pemrograman tersebut haruslah disepakati oleh para pembuat browser. Jadi, programmer Chrome, Firefox, Opera, Edge, dan lainnya, membentuk <a href="http://www.ecma-international.org/memento/index.html">suatu perkumpulan yang disebut ECMA International</a> yang membahas fitur apa yang akan didukung oleh browser. Hasilnya disebut dengan spesifikasi ECMA Script. Pada saat artikel ini ditulis, versi terbaru yang sudah dirilis oleh mereka disebut dengan <a href="http://www.ecma-international.org/publications/standards/Ecma-262.htm">ES6 atau ES2015</a>, dirilis pada tanggal 17 Juni 2015.</p>
<p>Akan tetapi, namanya perkumpulan dengan banyak anggota, sulit untuk mencapai keputusan, apalagi menjalankannya. ES2015 saja yang sudah dirilis, belum juga tayang di semua browser. Daftar lengkap kompatibilitas browser bisa dilihat <a href="https://kangax.github.io/compat-table/es6/">di sini</a>. Bentuknya kira-kira seperti gambar berikut</p>
<p><a href="https://lh3.googleusercontent.com/PrREqYij3dnzI5UijLBeuqqFNN4WbkVO-znIbtKrwuHN7VvWC4pXjxmeYyRbsy4WUOZ8VuunK07Y=w1280-no"><img src="https://lh3.googleusercontent.com/PrREqYij3dnzI5UijLBeuqqFNN4WbkVO-znIbtKrwuHN7VvWC4pXjxmeYyRbsy4WUOZ8VuunK07Y=w1280-no" alt="Dukungan ES6 di berbagai browser" /></a></p>
<p>Jadi, yang sekarang bisa dijalankan di semua browser adalah ECMA Script versi 5 (ES5).</p>
<p>Karena lambatnya kemajuan di ECMA, akhirnya para programmer tidak sabaran dan membuat bahasa sendiri-sendiri dengan fitur suka-suka sendiri. Agar bisa dijalankan di browser, mereka lalu membuat <em>transpiler</em> untuk mengkonversi kode programnya menjadi ES5.</p>
<p>Ini juga berlaku buat mereka yang tidak sabar ingin menggunakan ES6, diantaranya Google. Mereka membuat transpiler dari ES6 menjadi ES5 yang disebut dengan <a href="https://github.com/google/traceur-compiler">traceur</a>. Selain <code class="language-plaintext highlighter-rouge">traceur</code>, ada juga transpiler lain buatan komunitas yang bernama <a href="https://babeljs.io/">babel</a>.</p>
<p>Beberapa bahasa yang berusaha menambahkan fungsi JavaScript diantaranya:</p>
<ul>
<li><a href="http://coffeescript.org/">CoffeeScript</a></li>
<li><a href="https://www.dartlang.org/">Dart</a> : saat ini tidak lagi direkomendasikan karena fitur-fiturnya sudah diakomodasi di ES6.</li>
<li><a href="https://en.wikipedia.org/wiki/AtScript">AtScript</a> : pengembangan dari JavaScript yang dibuat oleh Google. Saat ini tidak dilanjutkan lagi, karena digabungkan dengan TypeScript</li>
<li><a href="http://www.typescriptlang.org/">TypeScript</a> : pengembangan JavaScript yang dibuat oleh Anders Hejlsberg (pembuat Turbo Pascal dan Delphi) yang sekarang bekerja di Microsoft dan didukung Google.</li>
<li>Selain itu, ada <a href="https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS">250an lagi bahasa pemrograman yang bisa dikonversi jadi JavaScript</a> ;)</li>
</ul>
<p>Perbedaan ES6 dengan ES5 bisa <a href="https://babeljs.io/docs/learn-es2015/">dibaca di sini</a>.</p>
<p>Yang saat ini sedang naik daun adalah TypeScript, karena dia digunakan untuk mengembangkan AngularJS versi 2 dan <a href="http://ionicframework.com/">Ionic Framework</a>. Perbandingannya dengan ES6 dan CoffeeScript bisa dilihat pada <a href="http://www.slideshare.net/NeilGreen1/type-script-vs-coffeescript-vs-es6">slide presentasi ini</a> atau <a href="https://www.youtube.com/watch?v=Ae4h9GC9cCg">video presentasi ini</a>.</p>
<p>Inti perbedaan antara ES6 dan TypeScript pada dasarnya adalah sistem tipe data. Contohnya, bila kita membuat class Produk, kita buat dalam ES6 seperti ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Produk</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">kode</span><span class="p">,</span> <span class="nx">nama</span><span class="p">,</span> <span class="nx">harga</span><span class="p">){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">kode</span> <span class="o">=</span> <span class="nx">kode</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">nama</span> <span class="o">=</span> <span class="nx">nama</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">harga</span> <span class="o">=</span> <span class="nx">harga</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">getKode</span><span class="p">(){</span>
<span class="k">return</span> <span class="nx">kode</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">setHarga</span><span class="p">(</span><span class="nx">harga</span><span class="p">){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">harga</span> <span class="o">=</span> <span class="nx">harga</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// getter setter lainnya</span>
<span class="p">}</span>
</code></pre></div></div>
<p>dan kita buat dalam TypeScript seperti ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Produk</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">kode</span><span class="p">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">nama</span><span class="p">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">harga</span><span class="p">:</span> <span class="nx">number</span><span class="p">){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">kode</span> <span class="o">=</span> <span class="nx">kode</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">nama</span> <span class="o">=</span> <span class="nx">nama</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">harga</span> <span class="o">=</span> <span class="nx">harga</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">getKode</span><span class="p">()</span> <span class="p">:</span> <span class="nx">string</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">kode</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">setKode</span><span class="p">(</span><span class="nx">kode</span><span class="p">:</span> <span class="nx">string</span><span class="p">){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">kode</span> <span class="o">=</span> <span class="nx">kode</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// getter dan setter lainnya</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Seperti kita lihat pada contoh kode program di atas, perbedaannya hanyalah pada deklarasi tipe data yang ada di TypeScript.</p>
<h2 id="sistem-modul">Sistem Modul</h2>
<p>Bila suatu bahasa pemrograman ingin digunakan untuk membuat aplikasi besar, maka bahasa tersebut harus memiliki sistem modul. Aplikasi besar terdiri dari ratusan ribu bahkan jutaan baris kode program. ribuan function, ratusan class dan file. Tanpa sistem modul yang mumpuni, kita akan mengalami kesulitan untuk menggunakan (reuse) fungsi-fungsi yang sudah pernah dibuat, baik oleh kita sendiri maupun oleh orang lain (library). Selain itu, kita juga akan mengalami kesulitan untuk memberikan nama untuk class dan function, karena besar kemungkinan terjadi kesamaan nama dengan kode program dari library orang lain. Sebagai contoh, nama-nama class yang pasaran seperti User, Application, Role, dan sejenisnya pastilah sudah ada yang menggunakannya.</p>
<p>Sayangnya JavaScript sampai ke versi ES5 belum memiliki sistem modul. Oleh karena itu, orang-orang kemudian membuat sistem modul sendiri. Ada beberapa sistem modul yang populer, diantaranya:</p>
<ul>
<li><a href="http://www.commonjs.org/">CommonJS</a> : ini adalah sistem modul yang digunakan oleh NodeJS. Biasanya ini jarang digunakan di aplikasi client-side, karena dia cuma bisa melakukan loading secara synchronous (blocking sampai modul berhasil diload). Ini kurang cocok digunakan di browser yang umumnya melakukan loading secara asynchronous (loading beberapa modul secara berbarengan).</li>
<li><a href="https://github.com/amdjs/amdjs-api/blob/master/AMD.md">AMD</a> : singkatan dari Asynchronous Module Definition, ini merupakan spesifikasi cara membuat dan menggunakan modul di JavaScript.</li>
<li>ES6 Module (ESM) : ini adalah sistem modul yang telah disediakan pada ECMA Script versi 6. Walaupun sudah rilis, tapi masih harus menunggu sampai diadopsi oleh seluruh browser.</li>
</ul>
<p>Pembahasan lengkap mengenai perbandingan sistem modul tersebut bisa dibaca di <a href="https://addyosmani.com/writing-modular-js/">artikel Addy Osmani</a>. Saat ini, tentu yang paling memungkinkan untuk kita adopsi adalah ESM, karena di masa depan dia akan didukung secara native oleh browser tanpa kita perlu menambah library lagi.</p>
<p>Untuk aplikasi yang terlanjur dibuat dengan CommonJS atau AMD, tidak perlu khawatir karena berbagai loader yang ada saat ini mendukung semua format populer di atas.</p>
<h2 id="module-loader">Module Loader</h2>
<p>Setelah kita mempartisi kode program kita menjadi modul-modul, tentunya harus ada suatu mekanisme supaya modul tersebut bisa bekerja sama dengan baik. Mekanisme ini disebut dengan istilah Module Loader. Bila kita menggunakan format modul CommonJS, kita bisa menggunakan implementasi loader <a href="http://webpack.github.io/">Webpack</a> dan <a href="http://browserify.org/">Browserify</a>. Sedangkan bila kita menggunakan format modul AMD, kita bisa memprosesnya dengan loader <a href="http://requirejs.org/">RequireJS</a> atau <a href="https://github.com/jrburke/almond">Almond</a>.</p>
<p>Ada juga loader yang bisa memproses berbagai format, baik CommonJS, AMD, ataupun EMS. Loader semacam ini contohnya <a href="https://github.com/systemjs/systemjs">SystemJS</a>, <a href="https://github.com/umdjs/umd">UMD</a>, dan <a href="http://urequire.org/">uRequire</a>.</p>
<p>Penjelasan dan contoh yang lebih detail dari module loader ini dapat dibaca di <a href="https://www.airpair.com/javascript/posts/the-mind-boggling-universe-of-javascript-modules">tulisan Tiago Romero Garcia</a>.</p>
<h2 id="framework">Framework</h2>
<p>Sekarang, mari kita bahas framework. Bila kita ingin membuat aplikasi yang full JavaScript, atau lebih dikenal dengan Single Page Application, maka kita harus menggunakan framework. Tanpa framework, kita akan mengalami kesulitan dan membuang waktu percuma untuk:</p>
<ul>
<li>memikirkan dan mengimplementasikan struktur / arsitektur aplikasi</li>
<li>memikirkan cara terbaik untuk memodifikasi elemen-elemen HTML / DOM</li>
<li>mengatur lalu lintas event</li>
<li>mengatur scope variabel</li>
<li>mengatur navigasi aplikasi (routing)</li>
</ul>
<p>Saat ini, framework di dunia JavaScript client-side sudah cukup mature dan stabil. <a href="http://angularjs.org/">AngularJS versi 1</a> banyak digunakan orang di aplikasi production berskala besar. Segala kesalahan dan kekurangan yang bersifat fundamental di AngularJS versi 1 sudah diperbaiki di <a href="http://angular.io/">AngularJS versi 2</a>. AngularJS versi 2 ini, walaupun masih berstatus Beta, tapi sudah digunakan di aplikasi besar oleh tim internal Google. AngularJS versi 2 ini dibuat dengan bahasa pemrograman TypeScript yang dikembangkan Microsoft.</p>
<p>Kompetitor utama AngularJS, yaitu <a href="http://emberjs.com/">EmberJS</a> saat ini juga sudah mencapai versi 2.2.0. Dia sudah digunakan di aplikasi besar seperti Discourse (forum diskusi), Groupon (diskon), LivingSocial (sharing pengalaman di suatu kota). Dengan track record seperti itu, kita tidak perlu ragu lagi dengan keandalannya.</p>
<p>Tidak mau kalah dengan kolaborasi Google dan Microsoft, Facebook juga merilis frameworknya menjadi opensource sehingga bisa kita pakai. Namanya <a href="https://facebook.github.io/react/">ReactJS</a>. Agak berbeda dengan AngularJS dan EmberJS yang merupakan framework komplit, ReactJS hanya mengurus masalah tampilan (View dalam MVC). Untuk bisa membuat aplikasi yang utuh, kita perlu tambahkan dengan framework <a href="https://facebook.github.io/flux/">Flux</a>.</p>
<h2 id="testing-tools">Testing Tools</h2>
<p>Ada dua tools yang umum digunakan untuk melakukan pengetesan:</p>
<ul>
<li><a href="http://jasmine.github.io/">Jasmine</a></li>
<li><a href="">Karma</a></li>
</ul>
<p>Jasmine adalah framework untuk menulis kode program test. Untuk menjalankannya di browser, kita menggunakan Karma. Karma ini bisa mengaktifkan berbagai browser seperti misalnya Chrome atau Firefox. Dia juga bisa menjalankan browser yang headless (tanpa tampilan) seperti PhantomJS.</p>
<h2 id="build-tools">Build Tools</h2>
<p>Build tools di dunia JavaScript berlimpah ruah, sampai kita pusing mau pilih yang mana. Trend popularitasnya juga naik turun. Sebagai ilustrasi, dulu Grunt sangat populer. Kemudian setelah banyak orang yang pakai, mulai banyak yang mengeluhkan buildfile yang terlalu panjang (kadang bisa ratusan baris). Kemudian orang pindah ke Gulp yang dianggap lebih baik. Ini juga ternyata banyak masalahnya. Bila ingin mengikuti perkembangannya, silahkan baca beberapa artikel berikut:</p>
<ul>
<li><a href="http://walkercoderanger.com/blog/2015/06/state-of-js-build-tools-2015/">Perbandingan berbagai build tools</a></li>
<li><a href="http://jamesknelson.com/which-build-system-should-i-use-for-my-javascript-app/">Memilih build tools</a></li>
</ul>
<p>Dan akhirnya orang memutuskan <a href="http://blog.keithcirkel.co.uk/why-we-should-stop-using-grunt/">tidak pakai satupun</a> :laughing:</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Demikianlah dinamika yang terjadi di dunia pemrograman JavaScript. Ini kita baru bicara di sisi client, yaitu yang berjalan di browser. Belum lagi kita bicarakan di sisi server. Sebagai programmer, sudah resiko profesi untuk selalu belajar hal yang baru. Apapun pengetahuan teknis yang kita kuasai sekarang, akan kadaluarsa dalam tiga sampai lima tahun ke depan. Jadi, jangan pelit ilmu. Buat apa merahasiakan sesuatu yang akan basi tahun depan ???</p>
Menjadi Dosen Hi-Tech2015-12-07T13:37:00+07:00https://software.endy.muhardin.com/life/dosen-hitech<p>Pada <a href="http://software.endy.muhardin.com/life/menjadi-mahasiswa-hi-tech/">posting
sebelumnya</a>, saya sudah menjelaskan tentang menjadi mahasiswa yang hi-tech. Yaitu dengan cara memanfaatkan teknologi dalam mengikuti perkuliahan sehingga hasilnya lebih maksimal. Tentunya tidak adil kalau peningkatan di sisi mahasiswa tidak diimbangi oleh dosennya.</p>
<p>Untuk itu, pada artikel kali ini, kita akan membahas bagaimana menjadi
dosen yang lihai memanfaatkan teknologi terbaru, agar impact yang dihasilkan
dari perkuliahannya menjadi maksimal.</p>
<!--more-->
<p>Menjadi mahasiswa sekaligus juga dosen, memungkinkan saya untuk mengoptimasi
metode belajar saya pribadi. Dari metode belajar ini, saya posisikan diri
sebagai pengajar, sehingga menghasilkan metode mengajar yang mudah-mudahan
tidak membebani siswa, tapi menghasilkan dampak yang maksimal. Tentu saja,
metode belajar tiap orang berbeda-beda, dan cara ini belum tentu efektif untuk
orang lain.</p>
<p>First of all, menjadi mahasiswa pasca tentu berbeda dengan mahasiswa S1/D3 yang
kuliah full time. Pada jaman saya kuliah S1 dulu, waktu saya 100% untuk kuliah.
Tidak disambi kerja. Sedangkan sekarang, selain kuliah saya juga kerja dan
mengajar. Alokasinya tentu jauh dari 100%.</p>
<p>Untuk mengatasi keterbatasan waktu, saya belajar kapanpun ada waktu idle.
Sumber waktu idle yang paling besar adalah <strong>waktu transportasi</strong>. Setiap hari
warga Jakarta menghabiskan waktu minimal 2-4 jam setiap hari di jalan. Berarti
dalam seminggu ada 10 - 20 jam yang terbuang percuma. Nah, kita bisa manfaatkan
waktu ini untuk belajar.</p>
<p>Metode belajar bisa macam-macam. Bisa dengan</p>
<ul>
<li>membaca buku</li>
<li>browsing internet</li>
<li>mendengarkan rekaman kuliah</li>
<li>menonton penjelasan</li>
</ul>
<p>Dengan berbagai metode di atas, saya sudah berhasil mempelajari:</p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PL56F4A88CC5F8ACC9">Ekonometrika</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLAEA5E9ACA1508F92">Ekonomi Mikro dan Makro</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLBddeLsLuADmKmqSkAILgbFptmRvNCSkO">Ushl Fiqh</a></li>
<li><a href="http://www.radiorodja.com/download/kajian/ustadz-erwandi-tarmizi">Fiqh Muamalah</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL23ZvcdS3XPLNdRYB_QyomQsShx59tpc-">Ruby on Rails</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLSjxaWF5Kn6RWG0JOVOmbVOcWJX1XzYeZ">Laravel</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL7DE50CFC19370404">Fotografi</a></li>
<li><a href="https://www.youtube.com/channel/UCzQ1L-wzA_1qmLf49ey9iTQ">Membuat Video dan Film</a></li>
<li><a href="https://www.youtube.com/channel/UCzQrtnJLZaH09xyZ1oKNq7Q">Membuat prakarya</a></li>
<li><a href="https://www.youtube.com/user/OrigamiHowTo1">Seni melipat kertas</a></li>
</ul>
<p>Belajarnya selama di kereta commuter line dan di motor antara rumah dan stasiun :D</p>
<p>Nah, berangkat dari pengalaman tersebut. Tentu kita sangat berterima kasih pada
guru-guru kita dan tim publikasinya yang telah memberikan pelajaran,
merekamnya, dan kemudian mengunggahnya ke Youtube agar dapat kita gunakan
bersama. Selanjutnya, sebagai dosen/guru/pengajar/instruktur, kita juga bisa
melakukan hal yang sama. Rekam semua materi yang kita ajarkan di kelas, edit,
kemudian upload ke Youtube.</p>
<p>Butuh waktu agak lama sampai saya mempublish artikel ini. Terutama karena saya
ingin <em>walk the talk</em>, <em>practice what you preach</em>, menjalankan apa yang kita
anjurkan. Sekarang saya sudah bisa menulis tentang ini, karena <a href="http://www.youtube.com/user/artivisi">channel Youtube
ArtiVisi</a> sudah berjalan dengan konsisten.</p>
<p>Dari sisi ROI (return on investment), publikasi di Youtube ini akan sangat
menguntungkan. Materi yang seharusnya cuma bisa didengarkan segelintir orang
dalam kelas, jadi bisa dikonsumsi orang seluruh dunia (karena materi saya
berbahasa Indonesia). Bayangkan pahala sedekah jariyah yang bisa kita dapatkan.
Para motivator menyebutnya passive income :D</p>
<h2 id="peralatan">Peralatan</h2>
<blockquote>
<p>Berapa modalnya?</p>
</blockquote>
<p>Bisa mahal, bisa murah. Kalau mau minimalis, untuk menghasilkan video tutorial
yang memadai, kita cukup punya tiga perangkat :</p>
<ul>
<li>Smartphone berkamera (Android atau iPhone)</li>
<li>Penyangga smartphone</li>
<li>Microphone</li>
</ul>
<p>Mengenai smartphone, tentu tidak perlu dijelaskan lagi. Sekarang ini semua
orang sudah punya akun Instagram dan Facebook. Tentu cara mengoperasikan kamera
video sudah tidak perlu kita bahas lagi di sini.</p>
<p>Untuk penyangga, tidak perlu yang mahal-mahal. Kita bisa bikin sendiri seperti
pada tutorial berikut.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/91auY89dylM" frameborder="0" allowfullscreen=""></iframe>
<p>Banyak orang bertanya, kan smartphone saya juga bisa menangkap suara.</p>
<blockquote>
<p>Kenapa masih harus beli mic?</p>
</blockquote>
<p>Ya karena kalau tidak pakai mic, suaranya jelek. Ini karena jarak antara sumber
suara dan microphone handphone terlalu jauh. Sehingga dia menangkap juga
suara-suara dalam ruangan. Bila kita pasang mic dekat mulut kita, maka suara
kita akan mengalahkan suara-suara lain dalam ruangan, sehingga terdengar lebih
jelas.</p>
<p>Microphone yang cukup baik harganya tidak mahal. Saya menggunakan mic lavalier
(mic yang ditempel/dijepit di baju) merek Audio Technica ATR3350IS seharga 500
ribu rupiah.</p>
<p><a href="https://lh3.googleusercontent.com/ugrsJcUScaOyqgfQsZuCJGu_fc3HQuS-IwBz3jnrvXEWSxEtCxM=s680-no"><img src="https://lh3.googleusercontent.com/ugrsJcUScaOyqgfQsZuCJGu_fc3HQuS-IwBz3jnrvXEWSxEtCxM=s680-no" alt="Audio Technica ATR3350IS" /></a></p>
<p>gambar diambil dari <a href="http://www.audio-technica.com/cms/wired_mics/55540ea6a5fbc94d/index.html">websitenya Audio Technica</a></p>
<p>Microphone ini sudah dilengkapi dengan adapter supaya bisa dipasang di
smartphone.</p>
<p>Bila mau lebih serius, silahkan beli kamera handycam, DSLR, dan jangan lupa
tripodnya sekalian.</p>
<h2 id="cara-pembuatan">Cara Pembuatan</h2>
<blockquote>
<p>Sulitkah membuatnya?</p>
</blockquote>
<p>Sama sekali tidak. Saya sudah menjelaskan cara
pembuatannya pada video berikut</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/P1XLsLdJm90" frameborder="0" allowfullscreen=""></iframe>
<p>Secara garis besar, membuat video tutorial terdiri dari tiga langkah:</p>
<ul>
<li>syuting</li>
<li>edit</li>
<li>upload</li>
</ul>
<h3 id="syuting">Syuting</h3>
<p>Tidak ada yang perlu dijelaskan mengenai syuting. Cukup letakkan smartphone
pada penyangga, pasang mic, mulai merekam.</p>
<p>Sedikit tips, bila pada saat merekam ada gangguan, misalnya:</p>
<ul>
<li>lupa apa yang mau dijelaskan</li>
<li>ada suara latar yang mengganggu (motor lewat pakai knalpot racing, dipanggil istri, dsb)</li>
</ul>
<p>Berhenti saja sebentar tanpa perlu mematikan rekaman. Begitu sudah tenang, bisa kita lanjut lagi. Bagian yang berisik tadi bisa dibuang di fase pengeditan.</p>
<p>Untuk materi pemrograman, saya biasanya juga merekam layar komputer saya selama
membuat aplikasi. Berikut beberapa aplikasi gratis yang bisa digunakan:</p>
<ul>
<li>Simple Screen Recorder (Ubuntu)</li>
<li>gtkRecordMyDesktop (Ubuntu)</li>
<li>Kazaam (Ubuntu)</li>
<li>QuickTime Player (MacOSX) –ya dia juga bisa merekam lho–</li>
<li>Camstudio (Windows), bukan Camtasia (berbayar)</li>
<li>Open Broadcaster Studio / OBS (Windows)</li>
</ul>
<h3 id="edit">Edit</h3>
<p>Setelah disyuting, kita lakukan pengeditan. Biasanya yang saya lakukan pada
saat mengedit antara lain:</p>
<ul>
<li>membuang bagian-bagian yang mengganggu / tidak perlu</li>
<li>menambahkan ilustrasi berupa tulisan atau slide presentasi</li>
<li>menambahkan bumper intro dan closing</li>
<li>konversi ke format file dan resolusi yang sesuai</li>
</ul>
<p>Aplikasi pengeditan tidak perlu canggih-canggih. Berikut beberapa aplikasi
gratis yang bisa digunakan:</p>
<ul>
<li>Openshot (Ubuntu)</li>
<li>Pitivi (Ubuntu)</li>
<li>iMovie (MacOS)</li>
<li>Windows Movie Maker (Windows)</li>
</ul>
<p>Aplikasi gratis ini sudah cukup untuk melakukan pengeditan seperti yang
dijelaskan di atas.</p>
<h3 id="unggah">Unggah</h3>
<p>Tahap terakhir, kita login ke Youtube atau Vimeo, lalu upload video kita tadi.
Jangan lupa aktifkan fitur <em>Monetization</em> supaya yutub memasang iklan di video
kita. Lumayan bonus buat upgrade peralatan ;)</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Sekarang sudah jaman multimedia. Sebagai dosen, kita juga harus up to date
dengan perkembangan teknologi. Dengan energi yang sama, (okelah tambah dikit
untuk edit dan unggah), manfaat yang kita berikan bagi orang lain akan jauh
bertambah.</p>
<p>Bila ingin lihat contoh hasilnya, silahkan berkunjung ke <a href="https://www.youtube.com/user/artivisi">channel
ArtiVisi</a>.</p>
<p>Kita ada materi pelajaran:</p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbx58s-eCBwP-5NxKnG8SHIr">Java Fundamental</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbzcM4YZkTncDgfak0xKUQdf">Java Desktop</a></li>
<li><a href="https://www.youtube.com/watch?v=CVeaOkHrZ70">Java Web tanpa framework</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLAx_QRVWQ-CyjYbvLWnYGEsmoWJyAIuvU">Java Web dengan Spring Boot</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbwBK-VWbCHsr9kiDJ5Eo0_o">OAuth 2</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbyLn02xpU5xLips7l4nUeyo">Laravel</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYby8FmTWxX0jx_3VOCFV7_r3">Ruby Fundamental</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbzexUhUswOhM9XP1FfSCs75">Ruby on Rails</a></li>
<li>dan masih banyak lagi</li>
</ul>
<p>Yuk upload video tutorial. Jangan mau kalah sama Sinta-Jojo ;)</p>
Backup dengan Duplicity2015-10-19T13:37:00+07:00https://software.endy.muhardin.com/linux/backup-duplicity<p>Backup itu penting, semua orang tentu setuju. Akan tetapi, berapa orang yang benar-benar melakukannya? Secara rutin? Dengan cara yang benar?</p>
<p>Pada artikel ini, kita akan membahas tentang seluk beluk backup. Apa yang harus diperhatikan, bagaimana cara melakukannya, seberapa sering, dan apa aplikasi yang digunakan.</p>
<p><a href="https://lh3.googleusercontent.com/QL9x5aS1BuhPkM7xGBHY0QPZMf8noCaYDJ1ahL7QUGem5z0dVHQ=w1280-no"><img src="https://lh3.googleusercontent.com/QL9x5aS1BuhPkM7xGBHY0QPZMf8noCaYDJ1ahL7QUGem5z0dVHQ=w1280-no" alt="Backup Station" /></a></p>
<p>Foto di atas menunjukkan meja makan yang sedang dikudeta untuk menjalankan prosedur backup ;)</p>
<p>Oh iya, saya membackup harddisk external yang biasa saya bawa-bawa (karena hardisk laptopnya SSD 128GB, sehingga cuma muat buat sistem operasi saja) ke harddisk external yang besar (ditinggal di rumah).</p>
<p>Daftar perangkat:</p>
<ul>
<li>Harddisk external yang akan dibackup : <a href="http://www.seagate.com/as/en/products/laptop-mobile-storage/laptop-external-drives/backup-plus-slim/">Seagate Backup Slim Plus 2TB</a></li>
<li>Harddisk external tujuan backup : Western Digital 1TB</li>
<li>Docking Station : <a href="http://www.thermaltake.com/products-model.aspx?id=C_00001756">ThermalTake BlacX Duet 5G</a></li>
<li>USB Hub : <a href="http://www.transcend-info.com/Products/No-402">Transcend USB 3.0 Hub</a></li>
</ul>
<blockquote>
<p>Peringatan : artikel ini diperuntukkan buat mereka yang memiliki latar belakang teknis yang cukup memadai, khususnya Linux, Mac, dan penggunaan terminal (command prompt).</p>
</blockquote>
<p>Bagi mereka yang levelnya user biasa, cukuplah dengan beberapa alternatif berikut:</p>
<ul>
<li>Pengguna Mac : <a href="http://osxdaily.com/2013/08/09/time-machine-multiple-drives-mac/">setup Time Machine untuk membuat backup ke external harddisk</a></li>
<li>Pengguna Linux : gunakan Deja Dup untuk membuat backup ke <a href="https://www.maketecheasier.com/deja-dup-makes-backup-a-simple-task-linux/">external harddisk</a>, Dropbox, Google Drive, atau Amazon S3</li>
<li>Lain-lain : gunakan layanan seperti CrashPlan, Backblaze, atau IDrive. Mereka sudah membuatkan aplikasi untuk komputer Anda. Coba baca <a href="http://thewirecutter.com/reviews/best-online-backup-service/">ulasan The Wirecutter</a> untuk lebih detailnya.</li>
</ul>
<p>Nah, untuk advanced users, silahkan meneruskan membaca artikel ini ;)</p>
<ul id="markdown-toc">
<li><a href="#kenapa-perlu-backup" id="markdown-toc-kenapa-perlu-backup">Kenapa perlu backup</a></li>
<li><a href="#cara-backup-yang-benar" id="markdown-toc-cara-backup-yang-benar">Cara backup yang benar</a></li>
<li><a href="#lokasi-penyimpanan" id="markdown-toc-lokasi-penyimpanan">Lokasi Penyimpanan</a></li>
<li><a href="#aplikasi-backup" id="markdown-toc-aplikasi-backup">Aplikasi Backup</a></li>
<li><a href="#backup-on-the-cloud" id="markdown-toc-backup-on-the-cloud">Backup on the Cloud</a></li>
<li><a href="#menggunakan-duplicity" id="markdown-toc-menggunakan-duplicity">Menggunakan Duplicity</a> <ul>
<li><a href="#instalasi-duplicity" id="markdown-toc-instalasi-duplicity">Instalasi Duplicity</a> <ul>
<li><a href="#macosx" id="markdown-toc-macosx">MacOSX</a></li>
<li><a href="#ubuntu" id="markdown-toc-ubuntu">Ubuntu</a></li>
</ul>
</li>
<li><a href="#persiapan-gpg-key" id="markdown-toc-persiapan-gpg-key">Persiapan GPG Key</a></li>
</ul>
</li>
<li><a href="#backup-script" id="markdown-toc-backup-script">Backup Script</a> <ul>
<li><a href="#konfigurasi-backup" id="markdown-toc-konfigurasi-backup">Konfigurasi Backup</a></li>
<li><a href="#backup-ke-external-harddisk" id="markdown-toc-backup-ke-external-harddisk">Backup ke External Harddisk</a></li>
<li><a href="#backup-ke-amazon-s3-dan-glacier" id="markdown-toc-backup-ke-amazon-s3-dan-glacier">Backup ke Amazon S3 dan Glacier</a></li>
<li><a href="#solusi-fakir-bandwidth" id="markdown-toc-solusi-fakir-bandwidth">Solusi Fakir Bandwidth</a></li>
</ul>
</li>
<li><a href="#kesimpulan" id="markdown-toc-kesimpulan">Kesimpulan</a></li>
<li><a href="#referensi" id="markdown-toc-referensi">Referensi</a></li>
</ul>
<!--more-->
<h2 id="kenapa-perlu-backup">Kenapa perlu backup</h2>
<p>Banyak hal yang bisa terjadi pada data kita, misalnya:</p>
<ul>
<li>kena virus (Windows only).</li>
<li>harddisk rusak. Baik karena faktor usia, jatuh, kena kopi, kehujanan, kecebur di bak mandi, dan seribu satu alasan lainnya.</li>
<li>hilang kecurian</li>
<li>kebakaran</li>
<li>dan lain sebagainya</li>
</ul>
<p>Laptop kecurian bisa dibeli lagi (walaupun nangis dulu, kemudian lembur). Komputer rusak juga bisa kanibal atau nunggu warisan. Tapi data? Tidak akan bisa kembali.</p>
<p>Data pekerjaan, foto keluarga, foto anak-anak sejak bayi, rekaman anak-anak belajar Al-Fatihah, tidak ternilai dan tak tergantikan.</p>
<h2 id="cara-backup-yang-benar">Cara backup yang benar</h2>
<p>Ada beberapa faktor yang harus diperhatikan supaya data kita benar-benar aman. Sebenarnya urusan backup ini, saking pentingnya, sudah dibuatkan orang prosedur dan ketentuannya. Kita tinggal ikuti saja. Contohnya <a href="http://www.hanselman.com/blog/TheComputerBackupRuleOfThree.aspx">prosedur 3-2-1</a> dan <a href="http://www.hanselman.com/blog/OnLosingDataAndAFamilyBackupStrategy.aspx">prosedur backup untuk keluarga</a>.</p>
<p>Berikut beberapa kriteria yang harus kita penuhi agar data kita aman:</p>
<ul>
<li>datanya terduplikasi di lebih dari satu tempat. Selain di laptop, ada juga di external harddisk, dan ada juga di server atau layanan cloud.</li>
<li>tempat penyimpanan terpisah satu dengan lainnya secara geografis lebih dari 30 kilometer. Ini untuk mengantisipasi apabila terjadi kejadian seperti gempa bumi, kerusuhan, atau kebakaran.</li>
<li>data sensitif disimpan dalam bentuk terenkripsi. Kita tentu tidak mau arsip foto keluarga beredar secara bebas di internet. Demikian juga dokumen-dokumen pribadi seperti scan identitas, surat kendaraan, dan informasi rahasia lainnya.</li>
<li>data harus bisa <em>dimundurkan</em> ke periode waktu tertentu. Misalnya komputer kita terkena virus, kemudian kita jalankan prosedur backup. Tentu saja si virus ini akan ikut tersimpan dalam backup. Bila ini terjadi, kita ingin mengambil data <strong>pada waktu belum terkena virus</strong>.</li>
</ul>
<p>Dan juga ada beberapa kriteria tambahan yang sifatnya tidak wajib, tapi akan membuat hidup kita jauh lebih mudah</p>
<ul>
<li>prosedurnya bisa diotomasi, sehingga cukup sekali setup, dia akan berjalan sendiri secara otomatis</li>
<li>incremental backup : hanya membackup perubahan sejak backup sebelumnya. Ini akan menghemat space harddisk dan bandwidth (bila backupnya diupload ke tempat lain)</li>
<li>dukungan untuk berbagai layanan cloud (FTP, Dropbox, Google Drive, Amazon S3, Amazon Glacier)</li>
<li>kompresi data. Perlu diketahui juga bahwa tidak semua file bisa dikompresi. File-file seperti PDF, JPG, MP3, MP4, dan file multimedia pada umumnya sudah berada dalam kondisi terkompres. Sehingga bila dikompres lagi, tidak akan mengurangi ukuran filenya.</li>
</ul>
<h2 id="lokasi-penyimpanan">Lokasi Penyimpanan</h2>
<p>Ada beberapa pilihan yang bisa kita gunakan untuk menyimpan backup kita, diantaranya:</p>
<ul>
<li>external harddisk. Bedakan antara harddisk yang kita bawa-bawa (karena harddisk laptopnya SSD, sehingga kapasitasnya terbatas) dengan harddisk untuk backup (idealnya ditinggal di rumah untuk mengurangi resiko bad sector dan supaya lokasinya terpisah)</li>
<li>server kita sendiri yang bisa diakses dari internet. Kita bisa pasang FTP, SSH, Owncloud, dan aplikasi lain yang bisa menerima data kita.</li>
<li>layanan penyimpanan berbasis cloud. Jaman sekarang, pilihannya sudah banyak, murah, dan mudah digunakan. Contohnya: Dropbox, Google Drive, Amazon S3, Amazon Glacier, dan lain sebagainya.</li>
</ul>
<h2 id="aplikasi-backup">Aplikasi Backup</h2>
<p>Untuk memudahkan pekerjaan kita, orang sudah banyak membuatkan aplikasi untuk backup, diantaranya:</p>
<ul>
<li><a href="https://support.apple.com/en-ap/HT201250">Time Machine</a> (MacOS)</li>
<li><a href="http://duplicity.nongnu.org/">Duplicity</a> (Linux, MacOS, Windows)</li>
<li>Windows Backup and Restore</li>
<li>Deja Dup (front-end duplicity)</li>
<li>Duply (wrapper duplicity)</li>
</ul>
<p>Sebetulnya masih banyak aplikasi lain. Apalagi kalau Anda menggunakan Linux. Mau model seperti <a href="http://www.techrepublic.com/blog/10-things/10-outstanding-linux-backup-utilities/">apapun</a> <a href="http://www.linuxlinks.com/article/20090105114152803/Backup.html">ada</a>.</p>
<p>Saya sudah melihat-lihat berbagai aplikasi tersebut, dan akhirnya yang terbaik pada saat artikel ini ditulis adalah Duplicity.</p>
<blockquote>
<p>Kenapa harus pakai aplikasi segala? Upload ke Dropbox kan beres?</p>
</blockquote>
<p>Sebetulnya bisa saja. Gampang dan mudah. Akan tetapi kurang aman, karena data kita tersimpan di server orang lain (Amazon, Google, dsb) dalam kondisi apa adanya (plain). Siapapun bisa buka, asal <a href="https://www.jwz.org/blog/2011/04/dropbox-doesnt-actually-encrypt-your-files/">punya akses ke server</a>. Siapa yang tahu kalau di kemudian hari mereka diakuisisi perusahaan lain, atau <a href="http://dougbelshaw.com/blog/2013/08/28/why-im-saying-goodbye-to-dropbox-and-hello-to-spideroak-hive/">servernya disita NSA</a>.</p>
<p>Boleh saja menggunakan Dropbox atau Google Drive, asal kita enkripsi dulu.</p>
<p>Ada beberapa fitur penting yang disediakan aplikasi yang disebutkan di atas ini, diantaranya:</p>
<ul>
<li>enkripsi</li>
<li>kompresi</li>
<li>full dan incremental backup</li>
<li>kemampuan mundur ke waktu tertentu</li>
</ul>
<h2 id="backup-on-the-cloud">Backup on the Cloud</h2>
<p>Backup di hardisk lain cukup mudah kita lakukan. Apalagi dengan menggunakan aplikasi, tinggal kita tancapkan harddisknya, kemudian jalankan aplikasinya. Di bawah nanti kita akan bahas cara melakukannya.</p>
<p>Walaupun demikian, external harddisk masih belum memenuhi kriteria backup yang harus terpisah lebih dari 30 km. Untuk memenuhi syarat ini, kita perlu punya replika data di luar rumah/kantor. Cara paling mudah adalah dengan menggunakan layanan cloud.</p>
<p>Ada berbagai layanan cloud untuk menyimpan data, diantaranya:</p>
<ul>
<li>Dropbox</li>
<li>Google Drive</li>
<li>Amazon S3</li>
<li>Amazon Glacier</li>
<li>Backblaze</li>
<li>Crashplan</li>
<li>IDrive</li>
<li>Acronis</li>
<li>Carbonite</li>
</ul>
<p>Semua layanan ini sebetulnya bisa dikelompokkan menjadi dua jenis : online storage dan online backup. Online backup maksudnya adalah dia menyediakan layanan manajemen file ditambah dengan layanan penyimpanan file. Sedangkan online storage hanya menyediakan layanan penyimpanan.</p>
<p>Layanan manajemen yang dimaksud memiliki beberapa fitur, sama seperti fitur aplikasi backup yang telah dijelaskan di atas.</p>
<p>Walaupun demikian, saya pribadi tidak terlalu menghiraukan fitur manajemen file ini, karena saya lebih suka melakukannya sendiri. Berikut pertimbangannya:</p>
<ul>
<li>pemilihan file : akan lebih mudah dan fleksibel kalau kita konfigurasi sendiri</li>
<li>enkripsi : saya lebih suka mengenkripsi sendiri, dengan algoritma yang dipilih sendiri, dengan key yang dibuat dan disimpan sendiri. Bila algoritma dipilihkan mereka, key mereka yang buat, mereka yang simpan, sama saja data kita tersimpan tanpa enkripsi. Dengan surat perintah pengadilan, perusahaan cloud services ini bisa dipaksa menyerahkan semua data mereka (termasuk key enkripsi). Untuk mengatasi masalah ini, beberapa layanan backup membolehkan kita menggunakan key kita sendiri.</li>
<li>kemampuan mundur ke tanggal tertentu : juga bisa ditangani oleh aplikasi kita sendiri.</li>
</ul>
<p>Karena alasan tersebut, jadi saya samakan saja antara layanan online backup dan online storage. Kriteria pemilihannya jadi lebih sederhana, cuma dua saja yaitu:</p>
<ul>
<li>keandalan : apakah data kita tersimpan aman? Apakah mereka cuma punya satu datacenter, atau ada replikasi ke datacenter lain? Apakah perusahaan dan layanan tersebut akan terus ada 5-10 tahun ke depan?</li>
<li>tarif</li>
</ul>
<p>Dari sisi keandalan, maka kita bisa mempercayai nama-nama besar berikut:</p>
<ul>
<li>Amazon</li>
<li>Google</li>
<li>dan juga Dropbox, karena dia sebetulnya menitipkan data di Amazon ;)</li>
</ul>
<p>Dari sisi tarif, pada waktu artikel ini ditulis, paling murah adalah Amazon Glacier. Ini adalah layanan yang khusus untuk keperluan <em>archival</em>. Yaitu penyimpanan untuk data yang harus disimpan dalam jangka waktu lama (rekam medis, data transaksi keuangan) tapi tidak butuh diakses sewaktu-waktu. Oleh karena itu, walaupun tarif penyimpanannya murah, untuk mengambil data di Glacier dikenakan biaya yang mahal dan prosesnya tidak instan. Ada waktu tunggu beberapa jam sampai data kita siap didownload.</p>
<p>Berikut tarif penyimpanan data</p>
<p><a href="https://lh3.googleusercontent.com/J4JxEIBvpagQV3b5cM5qx69Vj6RjSwZlbOFauoWCNbSdL7MBQ7M=w1280-no"><img src="https://lh3.googleusercontent.com/J4JxEIBvpagQV3b5cM5qx69Vj6RjSwZlbOFauoWCNbSdL7MBQ7M=w1280-no" alt="Tarif Penyimpanan Glacier " /></a></p>
<p>dan tarif untuk mengambil data</p>
<p><a href="https://lh3.googleusercontent.com/6OCYS73HU9iqROLLpFYYeS3xtIpl-FwGtGBTf9iskUEhVXX55Gk=w1280-no"><img src="https://lh3.googleusercontent.com/6OCYS73HU9iqROLLpFYYeS3xtIpl-FwGtGBTf9iskUEhVXX55Gk=w1280-no" alt="Tarif Mengambil Data dari Glacier" /></a></p>
<p>Sengaja saya tidak pasang tarif di sini, karena harganya bisa berubah sewaktu-waktu. Tapi mari kita ambil contoh saja, saat ini harga Amazon Glacier $0.007/GB/bulan. Sebagai ilustrasi, seluruh koleksi foto keluarga saya besarnya hampir 200GB. Dengan demikian, kalau disimpan di Amazon Glacier, tarifnya <em>hanya</em> $1.4/bulan. Kalikan Rp. 15.000, hasilnya Rp. 21.000 per bulan. Kalikan 12, Rp. 252.000 setahun. Coba pertimbangkan, apakah koleksi kenangan indah masa lalu kita senilai harga tersebut?</p>
<h2 id="menggunakan-duplicity">Menggunakan Duplicity</h2>
<p>Dari sekian banyak aplikasi backup, saya memilih <a href="http://duplicity.nongnu.org">Duplicity</a> untuk beberapa alasan:</p>
<ul>
<li>cross platform, berjalan di Linux, Mac, dan Windows</li>
<li>bisa full dan incremental backup</li>
<li>bisa enkripsi dengan <a href="http://software.endy.muhardin.com/linux/menggunakan-gpg">menggunakan GPG</a></li>
<li>
<p>mendukung berbagai macam backend storage, misalnya:</p>
<ul>
<li>external harddisk</li>
<li>Amazon S3 dan Glacier</li>
<li>Dropbox</li>
<li>Google Drive</li>
<li>dan masih banyak lagi. Silahkan lihat sendiri di dokumentasinya</li>
</ul>
</li>
<li>berbasis command line, sehingga bisa juga digunakan untuk membackup server.</li>
</ul>
<p>Baiklah, mari kita install dulu dia.</p>
<h3 id="instalasi-duplicity">Instalasi Duplicity</h3>
<p>Instalasi di Windows dapat dibaca pada <a href="http://www.alexdimarco.ca/blog/15-duplicity-on-windows.html">artikel ini</a>.</p>
<h4 id="macosx">MacOSX</h4>
<p>Cek versinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew info duplicity
duplicity: stable 0.6.26 (bottled), devel 0.7.04
Bandwidth-efficient encrypted backup
http://www.nongnu.org/duplicity/
Not installed
From: https://github.com/Homebrew/homebrew/blob/master/Library/Formula/duplicity.rb
==> Dependencies
Required: librsync ✘, gnupg ✘
==> Options
--universal
Build a universal binary
--devel
Install development version 0.7.04
</code></pre></div></div>
<p>Install duplicity</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew install --devel duplicity
</code></pre></div></div>
<h4 id="ubuntu">Ubuntu</h4>
<p>Cukup jalankan perintah berikut di terminal</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install python-software-properties software-properties-common -y
sudo apt-add-repository ppa:duplicity-team/ppa
sudo add-apt-repository ppa:chris-lea/python-boto
sudo apt-get update
sudo apt-get install duplicity python-boto haveged
</code></pre></div></div>
<h3 id="persiapan-gpg-key">Persiapan GPG Key</h3>
<p>Duplicity mendukung enkripsi dengan symmetric key (semacam password) ataupun dengan GPG. Saya rekomendasikan menggunakan GPG supaya tekniknya juga bisa digunakan di server. Kalau menggunakan symmetric key, kita harus input password setiap kali menjalankan backup, ini tidak bisa dipakai untuk server ataupun backup otomatis terjadwal.</p>
<p>Persiapan GPG key bisa dibaca di <a href="http://software.endy.muhardin.com/linux/menggunakan-gpg">artikel sebelumnya</a>. Kita membutuhkan:</p>
<ul>
<li>GPG keypair yang sudah ter-import ke dalam keyring</li>
<li>Key ID</li>
<li>Passphrase untuk membuka private key (bila ada)</li>
</ul>
<p>Jalankan perintah berikut untuk mendapatkan Key ID</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --list-keys --keyid-format LONG
</code></pre></div></div>
<p>Berikut hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/Users/endymuhardin/.gnupg/pubring.gpg
--------------------------------------
pub 4096R/80D2744B0EB1FA47 2015-10-17 [expires: 2017-10-16]
uid Endy Muhardin <endy.muhardin+duplicity@gmail.com>
sub 4096R/2512CD4FD21E50A5 2015-10-17 [expires: 2017-10-16]
</code></pre></div></div>
<p>Key ID yang akan kita gunakan dalam penjelasan di bawah adalah <code class="language-plaintext highlighter-rouge">80D2744B0EB1FA47</code></p>
<h2 id="backup-script">Backup Script</h2>
<p>Sebetulnya duplicity bisa langsung dipanggil dari command line. Contohnya, bila kita sekedar ingin membackup folder <code class="language-plaintext highlighter-rouge">Pictures</code> ke external harddisk, perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>duplicity --encrypt-key=80D2744B0EB1FA47 Pictures file:///media/endy/EXTHD/folder-backup
</code></pre></div></div>
<p>Walaupun demikian, untuk backup yang bersifat jangka panjang, kita ingin menggunakan berbagai fitur dan opsi command line. Akan lebih mudah bila kita tulis dalam bentuk script agar tidak ada yang terlupakan.</p>
<h3 id="konfigurasi-backup">Konfigurasi Backup</h3>
<p>Dalam urusan backup, ada istilah <code class="language-plaintext highlighter-rouge">retention period</code>, artinya seberapa lama kita ingin menyimpan backup tersebut. Berikut adalah kebijakan retention period yang saya pakai:</p>
<ul>
<li>Simpan backup 6 bulan ke belakang. Maksimal periode yang bisa saya restore adalah 6 bulan terakhir. Artinya, kalau ada satu file yang dihapus 5 bulan yang lalu, masih bisa saya ambil. Sedangkan bila dihapus tahun lalu, tidak bisa diambil lagi.</li>
<li>Backup bisa dilakukan setiap saat, bentuknya berupa incremental backup (hanya selisih saja dari backup sebelumnya)</li>
<li>Lakukan full backup setiap bulan</li>
<li>Setelah full backup bulan terakhir dibuat, hapus incremental backup di bulan sebelumnya</li>
</ul>
<p>Nah, setelah kebijakan tersebut kita tetapkan, saatnya kita membuat backup script.</p>
<h3 id="backup-ke-external-harddisk">Backup ke External Harddisk</h3>
<p>Berikut adalah backup script yang saya gunakan untuk membackup beberapa folder ke external harddisk.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="c"># GPG Key ID + Private Key Passphrase</span>
<span class="nv">GPG_KEY</span><span class="o">=</span><span class="s1">'0x80D2744B0EB1FA47'</span>
<span class="nv">PASSPHRASE</span><span class="o">=</span><span class="s1">'masukkan passphrase anda di sini'</span>
<span class="nb">export </span>PASSPHRASE
<span class="c"># Masa Penyimpanan</span>
<span class="nv">FULL_BACKUP_FREQ</span><span class="o">=</span>1M <span class="c"># full backup setiap bulan</span>
<span class="nv">FULL_BACKUP_KEPT</span><span class="o">=</span>6 <span class="c"># simpan 6 full backup -> 6 bulan backup</span>
<span class="nv">FULL_BACKUP_CHAIN_KEPT</span><span class="o">=</span>1 <span class="c"># simpan 1 rangkaian full + inc</span>
<span class="c"># Log File</span>
<span class="nv">LOGFILE</span><span class="o">=</span><span class="nv">$HOME</span>/.duplicity/duplicity.log
<span class="k">function </span>do_backup <span class="o">{</span>
<span class="c"># Backup</span>
<span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">echo</span> <span class="s2">"========================================================"</span>
<span class="nb">echo</span> <span class="s2">" Backup folder </span><span class="nv">$1</span><span class="s2"> to </span><span class="nv">$2</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"========================================================"</span>
duplicity <span class="se">\</span>
<span class="nt">--allow-source-mismatch</span> <span class="se">\</span>
<span class="nt">--file-prefix-archive</span><span class="o">=</span><span class="s2">"data-"</span> <span class="se">\</span>
<span class="nt">--encrypt-key</span><span class="o">=</span><span class="s2">"</span><span class="nv">$GPG_KEY</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">--asynchronous-upload</span> <span class="se">\</span>
<span class="nt">--log-file</span> <span class="nv">$LOGFILE</span> <span class="se">\</span>
<span class="nt">--full-if-older-than</span> <span class="nv">$FULL_BACKUP_FREQ</span> <span class="se">\</span>
<span class="nv">$1</span> <span class="se">\</span>
file://<span class="nv">$2</span>
<span class="c"># Hapus full backup yang sudah melewati masa penyimpanan</span>
<span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">echo</span> <span class="s2">"========================================================"</span>
<span class="nb">echo</span> <span class="s2">" Removing old backup in </span><span class="nv">$2</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"========================================================"</span>
duplicity remove-all-but-n-full <span class="nv">$FULL_BACKUP_KEPT</span> <span class="se">\</span>
<span class="nt">--force</span> <span class="se">\</span>
file://<span class="nv">$2</span>
<span class="c"># Hapus incremental backup yang sudah melewati masa penyimpanan</span>
duplicity remove-all-inc-of-but-n-full <span class="nv">$FULL_BACKUP_CHAIN_KEPT</span> <span class="se">\</span>
<span class="nt">--force</span> <span class="se">\</span>
file://<span class="nv">$2</span>
<span class="c"># Hapus file-file yang tidak perlu</span>
<span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">echo</span> <span class="s2">"========================================================"</span>
<span class="nb">echo</span> <span class="s2">" Cleanup </span><span class="nv">$2</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"========================================================"</span>
duplicity cleanup <span class="se">\</span>
<span class="nt">--force</span> <span class="se">\</span>
<span class="nt">--extra-clean</span> <span class="se">\</span>
file://<span class="nv">$2</span>
<span class="o">}</span>
<span class="k">while </span><span class="nb">read </span>SRC DST<span class="p">;</span> <span class="k">do
</span>do_backup <span class="nv">$SRC</span> <span class="nv">$DST</span>
<span class="k">done</span> < daftar-backup-exthdd.txt
<span class="nb">unset </span>PASSPHRASE
</code></pre></div></div>
<p>Script tersebut akan mencari file <code class="language-plaintext highlighter-rouge">daftar-backup-exthdd.txt</code> yang berisi folder yang ingin dibackup dan folder tujuan backupnya. Berikut isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/home/endy/Pictures /media/endy/ENDYBACKUP/backup-foto
/home/endy/Documents /media/endy/ENDYBACKUP/backup-documents
</code></pre></div></div>
<p>Bila terjadi pesan error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Max open files of 256 is too low, should be >= 1024.
Use 'ulimit -n 1024' or higher to correct.
</code></pre></div></div>
<p>Maka kita bisa naikkan <code class="language-plaintext highlighter-rouge">ulimit</code> dengan menjalankan perintah berikut di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ulimit -n 1024
</code></pre></div></div>
<p>Setelah backup selesai, dia akan menampilkan hasil seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Local and Remote metadata are synchronized, no sync needed.
Last full backup date: none
Last full backup is too old, forcing full backup
Reuse configured PASSPHRASE as SIGN_PASSPHRASE
--------------[ Backup Statistics ]--------------
StartTime 1440036073.03 (Thu Aug 20 09:01:13 2015)
EndTime 1440036073.06 (Thu Aug 20 09:01:13 2015)
ElapsedTime 0.02 (0.02 seconds)
SourceFiles 3
SourceFileSize 104157 (102 KB)
NewFiles 3
NewFileSize 104157 (102 KB)
DeletedFiles 0
ChangedFiles 0
ChangedFileSize 0 (0 bytes)
ChangedDeltaSize 0 (0 bytes)
DeltaEntries 3
RawDeltaSize 100061 (97.7 KB)
TotalDestinationSizeChange 100239 (97.9 KB)
Errors 0
-------------------------------------------------
</code></pre></div></div>
<p>Bila kita buka di file explorer, hasilnya akan tampil seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/u3K2tQbik_dcMuQJ7XWsuSAOvhhEHciUROoMvPF1SmQkxD2kMpc=w1280-no"><img src="https://lh3.googleusercontent.com/u3K2tQbik_dcMuQJ7XWsuSAOvhhEHciUROoMvPF1SmQkxD2kMpc=w1280-no" alt="Hasil Backup" /></a></p>
<p>Kalau kita lihat di screenshot di atas, tidak ada file yang bisa kita lihat. Ini karena file dan foldernya dienkripsi. Untuk melihat isinya, kita harus mendekripsi dulu dan menampilkan isi folder sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>duplicity list-current-files file:///media/endy/ENDYBACKUP/backup-foto
</code></pre></div></div>
<p>Berikut potongan outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>duplicity list-current-files file:///media/endy/ENDYBACKUP/backup-foto
Local and Remote metadata are synchronized, no sync needed.
Last full backup date: Mon Oct 19 09:09:44 2015
Sun Oct 11 10:14:41 2015 .
Sun Oct 11 10:52:20 2015 2015/10/11
Sun Oct 11 08:40:24 2015 2015/10/11/DSC_2850.JPG
Sun Oct 11 08:40:50 2015 2015/10/11/DSC_2851.JPG
Sun Oct 11 08:41:20 2015 2015/10/11/DSC_2852.JPG
Sun Oct 11 08:42:42 2015 2015/10/11/DSC_2853.JPG
Sun Oct 11 08:44:32 2015 2015/10/11/DSC_2854.JPG
Sun Oct 11 08:44:40 2015 2015/10/11/DSC_2855.JPG
Sun Oct 11 08:45:08 2015 2015/10/11/DSC_2856.JPG
Sun Oct 11 10:39:28 2015 2015/10/11/DSC_2857-edit.JPG
Sun Oct 11 08:45:26 2015 2015/10/11/DSC_2857.JPG
Sun Oct 11 10:30:19 2015 2015/10/11/DSC_2858-crop.JPG
</code></pre></div></div>
<p>Bila kita ingin mengambil file <code class="language-plaintext highlighter-rouge">DSC_2858-crop.JPG</code>, perintahnya adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>duplicity --file-to-restore 2015/10/11/DSC_2858-crop.JPG file:///media/endy/ENDYBACKUP/backup-foto foto.jpg
</code></pre></div></div>
<p>Kita akan dimintai passphrase yang digunakan untuk mengenkripsi (bila pakai symmetric key) atau password private key (bila menggunakan GPG)</p>
<h3 id="backup-ke-amazon-s3-dan-glacier">Backup ke Amazon S3 dan Glacier</h3>
<p>Agar dapat mengupload file ke Amazon, kita membutuhkan <code class="language-plaintext highlighter-rouge">AWS_ACCESS_KEY_ID</code> berikut <code class="language-plaintext highlighter-rouge">AWS_SECRET_ACCESS_KEY</code>. Untuk mendapatkannya, kita login dulu ke web console Amazon. Kemudian masuk ke <a href="https://console.aws.amazon.com/iam/home#/users">bagian User Management di modul IAM</a>. Kemudian klik <code class="language-plaintext highlighter-rouge">Add User</code></p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2015/duplicity/01-add-user.png" alt="[Add User](https://software.endy.muhardin.com/images/uploads/2015/duplicity/01-add-user.png)" /></p>
<p>Isikan username yang akan kita buat, kemudian klik <code class="language-plaintext highlighter-rouge">Next</code> untuk mengatur permission.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2015/duplicity/02-add-permission.png" alt="[Add Permission](https://software.endy.muhardin.com/images/uploads/2015/duplicity/02-add-permission.png)" /></p>
<p>Selanjutnya, pilih tab <code class="language-plaintext highlighter-rouge">Attach Existing Policy</code> kemudian klik <code class="language-plaintext highlighter-rouge">Create Policy</code>.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2015/duplicity/03-create-policy.png" alt="[Create Policy](https://software.endy.muhardin.com/images/uploads/2015/duplicity/03-create-policy.png)" /></p>
<p>Isi policy seperti ini, yaitu memberikan akses buat mengunggah ke dalam bucket</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"s3:*"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"arn:aws:s3:::endy-backup"</span><span class="p">,</span><span class="w">
</span><span class="s2">"arn:aws:s3:::endy-backup/*"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Setelah itu, klik <code class="language-plaintext highlighter-rouge">Create Policy</code>. Kembali ke layar <code class="language-plaintext highlighter-rouge">Add User</code>, pasang policy tersebut ke user yang baru dibuat tadi</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2015/duplicity/04-add-policy.png" alt="[Add Policy](https://software.endy.muhardin.com/images/uploads/2015/duplicity/04-add-policy.png)" /></p>
<p>Klik <code class="language-plaintext highlighter-rouge">Finish</code> dan user kita telah terbentuk. Masuk ke record user tersebut, kemudian buka tab <code class="language-plaintext highlighter-rouge">Security Credential</code>. Klik <code class="language-plaintext highlighter-rouge">Add Access Key</code>.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2015/duplicity/06-access-key-created.png" alt="[Access Key](https://software.endy.muhardin.com/images/uploads/2015/duplicity/06-access-key-created.png)" /></p>
<p>Kita akan dibuatkan <code class="language-plaintext highlighter-rouge">Access Key</code> baru. Copy paste <code class="language-plaintext highlighter-rouge">Access Key ID</code> dan <code class="language-plaintext highlighter-rouge">Secret Key Access</code>. Kita akan membutuhkannya untuk dipasang di script di atas.</p>
<p><img src="https://software.endy.muhardin.com/images/uploads/2015/duplicity/05-create-access-key.png" alt="[Add Access Key](https://software.endy.muhardin.com/images/uploads/2015/duplicity/05-create-access-key.png)" /></p>
<p>Setelah <code class="language-plaintext highlighter-rouge">Access Key ID</code> dan <code class="language-plaintext highlighter-rouge">Secret Key Access</code> kita dapatkan, sekarang kita bisa lanjut membuat script uploadnya.</p>
<p>Untuk memasukkan file ke Glacier, kita harus melalui layanan S3 dulu. Di dalam konfigurasi S3, kita bisa membuat aturan (rule) untuk memindahkan file dari dalam S3 ke Glacier.</p>
<p>Berkaitan dengan duplicity, dia membutuhkan file <code class="language-plaintext highlighter-rouge">signature</code> dan <code class="language-plaintext highlighter-rouge">manifest</code> agar dia tahu posisi terakhir backup dan apa saja isi backup. Ukuran kedua file tersebut relatif tidak besar. Sedangkan datanya sendiri disimpan dalam file <code class="language-plaintext highlighter-rouge">difftar</code> yang hanya diperlukan pada waktu ingin restore. File <code class="language-plaintext highlighter-rouge">difftar</code> inilah yang ukurannya besar, sesuai dengan isi folder yang kita backup.</p>
<p>Dengan demikian, file <code class="language-plaintext highlighter-rouge">signature</code> dan <code class="language-plaintext highlighter-rouge">manifest</code> ini harus tetap ada di S3. File <code class="language-plaintext highlighter-rouge">difftar</code> bisa kita migrasikan ke Glacier supaya lebih murah biaya sewanya. Untuk keperluan tersebut, kita harus menambahkan prefix khusus di nama filenya supaya bisa diproses oleh rule S3.</p>
<p>Berikut contoh nama file standar duplicity</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls /media/endy/ENDYBACKUP/backup-foto/
duplicity-full-signatures.20151019T034820Z.sigtar.gpg duplicity-full.20151019T034820Z.vol22.difftar.gpg
duplicity-full.20151019T034820Z.manifest.gpg duplicity-full.20151019T034820Z.vol23.difftar.gpg
duplicity-full.20151019T034820Z.vol1.difftar.gpg duplicity-full.20151019T034820Z.vol24.difftar.gpg
duplicity-full.20151019T034820Z.vol10.difftar.gpg duplicity-full.20151019T034820Z.vol25.difftar.gpg
duplicity-full.20151019T034820Z.vol11.difftar.gpg duplicity-full.20151019T034820Z.vol26.difftar.gpg
duplicity-full.20151019T034820Z.vol12.difftar.gpg duplicity-full.20151019T034820Z.vol27.difftar.gpg
duplicity-full.20151019T034820Z.vol13.difftar.gpg duplicity-full.20151019T034820Z.vol28.difftar.gpg
duplicity-full.20151019T034820Z.vol14.difftar.gpg duplicity-full.20151019T034820Z.vol29.difftar.gpg
duplicity-full.20151019T034820Z.vol15.difftar.gpg duplicity-full.20151019T034820Z.vol3.difftar.gpg
duplicity-full.20151019T034820Z.vol16.difftar.gpg duplicity-full.20151019T034820Z.vol4.difftar.gpg
duplicity-full.20151019T034820Z.vol17.difftar.gpg duplicity-full.20151019T034820Z.vol5.difftar.gpg
duplicity-full.20151019T034820Z.vol18.difftar.gpg duplicity-full.20151019T034820Z.vol6.difftar.gpg
duplicity-full.20151019T034820Z.vol19.difftar.gpg duplicity-full.20151019T034820Z.vol7.difftar.gpg
duplicity-full.20151019T034820Z.vol2.difftar.gpg duplicity-full.20151019T034820Z.vol8.difftar.gpg
duplicity-full.20151019T034820Z.vol20.difftar.gpg duplicity-full.20151019T034820Z.vol9.difftar.gpg
duplicity-full.20151019T034820Z.vol21.difftar.gpg
</code></pre></div></div>
<p>Kita perlu menambahkan opsi <code class="language-plaintext highlighter-rouge">--file-prefix-archive</code> agar nama file difftar diberi prefix tertentu, misalnya <code class="language-plaintext highlighter-rouge">data-</code>. Hasilnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls /media/endy/ENDYBACKUP/backup-foto/
data-duplicity-full.20151019T042837Z.vol1.difftar.gpg data-duplicity-full.20151019T042837Z.vol24.difftar.gpg
data-duplicity-full.20151019T042837Z.vol10.difftar.gpg data-duplicity-full.20151019T042837Z.vol25.difftar.gpg
data-duplicity-full.20151019T042837Z.vol11.difftar.gpg data-duplicity-full.20151019T042837Z.vol26.difftar.gpg
data-duplicity-full.20151019T042837Z.vol12.difftar.gpg data-duplicity-full.20151019T042837Z.vol27.difftar.gpg
data-duplicity-full.20151019T042837Z.vol13.difftar.gpg data-duplicity-full.20151019T042837Z.vol28.difftar.gpg
data-duplicity-full.20151019T042837Z.vol14.difftar.gpg data-duplicity-full.20151019T042837Z.vol29.difftar.gpg
data-duplicity-full.20151019T042837Z.vol15.difftar.gpg data-duplicity-full.20151019T042837Z.vol3.difftar.gpg
data-duplicity-full.20151019T042837Z.vol16.difftar.gpg data-duplicity-full.20151019T042837Z.vol4.difftar.gpg
data-duplicity-full.20151019T042837Z.vol17.difftar.gpg data-duplicity-full.20151019T042837Z.vol5.difftar.gpg
data-duplicity-full.20151019T042837Z.vol18.difftar.gpg data-duplicity-full.20151019T042837Z.vol6.difftar.gpg
data-duplicity-full.20151019T042837Z.vol19.difftar.gpg data-duplicity-full.20151019T042837Z.vol7.difftar.gpg
data-duplicity-full.20151019T042837Z.vol2.difftar.gpg data-duplicity-full.20151019T042837Z.vol8.difftar.gpg
data-duplicity-full.20151019T042837Z.vol20.difftar.gpg data-duplicity-full.20151019T042837Z.vol9.difftar.gpg
data-duplicity-full.20151019T042837Z.vol21.difftar.gpg duplicity-full-signatures.20151019T042837Z.sigtar.gpg
data-duplicity-full.20151019T042837Z.vol22.difftar.gpg duplicity-full.20151019T042837Z.manifest.gpg
data-duplicity-full.20151019T042837Z.vol23.difftar.gpg
</code></pre></div></div>
<p>Berikut adalah backup scriptnya</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="c"># GPG Key ID + Private Key Passphrase</span>
<span class="nv">GPG_KEY</span><span class="o">=</span><span class="s1">'0x80D2744B0EB1FA47'</span>
<span class="nv">PASSPHRASE</span><span class="o">=</span><span class="s1">'RmQo32ta9j2JeCH9kXtMXjWP'</span>
<span class="nb">export </span>PASSPHRASE
<span class="c"># Amazon Credentials</span>
<span class="nb">source </span>amazon-auth.txt
<span class="nb">export </span>AWS_ACCESS_KEY_ID
<span class="nb">export </span>AWS_SECRET_ACCESS_KEY
<span class="c"># Masa Penyimpanan</span>
<span class="nv">FULL_BACKUP_FREQ</span><span class="o">=</span>1M <span class="c"># full backup setiap bulan</span>
<span class="c"># Log File</span>
<span class="nv">LOGFILE</span><span class="o">=</span><span class="nv">$HOME</span>/.duplicity/duplicity.log
<span class="k">function </span>do_backup <span class="o">{</span>
<span class="c"># Backup</span>
<span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">echo</span> <span class="s2">"========================================================"</span>
<span class="nb">echo</span> <span class="s2">" Backup folder </span><span class="nv">$1</span><span class="s2"> to Amazon S3 s3+http://</span><span class="nv">$2</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"========================================================"</span>
duplicity <span class="se">\</span>
<span class="nt">--s3-use-new-style</span> <span class="se">\</span>
<span class="nt">--s3-use-multiprocessing</span> <span class="se">\</span>
<span class="nt">--allow-source-mismatch</span> <span class="se">\</span>
<span class="nt">--file-prefix-archive</span><span class="o">=</span><span class="s2">"data-"</span> <span class="se">\</span>
<span class="nt">--encrypt-key</span><span class="o">=</span><span class="s2">"</span><span class="nv">$GPG_KEY</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">--asynchronous-upload</span> <span class="se">\</span>
<span class="nt">--log-file</span> <span class="nv">$LOGFILE</span> <span class="se">\</span>
<span class="nt">--full-if-older-than</span> <span class="nv">$FULL_BACKUP_FREQ</span> <span class="se">\</span>
<span class="nv">$1</span> <span class="se">\</span>
s3+http://<span class="nv">$2</span>
<span class="o">}</span>
<span class="k">while </span><span class="nb">read </span>SRC DST<span class="p">;</span> <span class="k">do
</span>do_backup <span class="nv">$SRC</span> <span class="nv">$DST</span>
<span class="k">done</span> < daftar-backup-s3.txt
<span class="nb">unset </span>PASSPHRASE
<span class="nb">unset </span>AWS_ACCESS_KEY_ID
<span class="nb">unset </span>AWS_SECRET_ACCESS_KEY
</code></pre></div></div>
<p>Script tersebut akan melihat konfigurasi login ke Amazon dalam file <code class="language-plaintext highlighter-rouge">amazon-auth.txt</code> yang isinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AWS_ACCESS_KEY_ID='MASUKKANACCESSKEYDISINI'
AWS_SECRET_ACCESS_KEY='awsSECRETaccessKEYanda'
</code></pre></div></div>
<p>Dia juga akan melihat daftar folder yang ingin dibackup berikut tujuannya dalam file <code class="language-plaintext highlighter-rouge">daftar-backup-s3.txt</code> yang isinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/home/endy/Pictures endy-backup/backup-foto
/home/endy/Documents endy-backup/backup-documents
</code></pre></div></div>
<p>Di kolom tujuan, formatnya adalah <code class="language-plaintext highlighter-rouge">namabucket/namafolder</code>. Sebelum dijalankan, pastikan kita sudah membuat bucket bernama <code class="language-plaintext highlighter-rouge">endy-backup</code> di Amazon S3.</p>
<p><a href="https://lh3.googleusercontent.com/0yX6Emtzj0ewaSpezMoQGJ9sfsNLEZDhrYJ9vSXYofMzNW02U7U=w1280-no"><img src="https://lh3.googleusercontent.com/0yX6Emtzj0ewaSpezMoQGJ9sfsNLEZDhrYJ9vSXYofMzNW02U7U=w1280-no" alt="Bucket kosong" /></a></p>
<p>Setelah script dijalankan, dia akan membuat folder dalam bucket</p>
<p><a href="https://lh3.googleusercontent.com/yoUO1yCyFjEFn0B3Bx2bcG7JLrjzdPUa0zFw4v51CI2lIpMYgVA=w1280-no"><img src="https://lh3.googleusercontent.com/yoUO1yCyFjEFn0B3Bx2bcG7JLrjzdPUa0zFw4v51CI2lIpMYgVA=w1280-no" alt="Bucket sudah terisi" /></a></p>
<p>Kita bisa melihat isi foldernya, mirip dengan yang kita hasilkan di external harddisk</p>
<p><a href="https://lh3.googleusercontent.com/-mcz-0Zlvn9klUBA7uMMR-R5uhGgMUBaWvSjWIi8aKsxv__AoSM=w1280-no"><img src="https://lh3.googleusercontent.com/-mcz-0Zlvn9klUBA7uMMR-R5uhGgMUBaWvSjWIi8aKsxv__AoSM=w1280-no" alt="Backup Content" /></a></p>
<p>Dari gambar di atas, kita ambil dua variabel : nama folder dan prefix untuk file difftar seperti sudah dijelaskan di atas. Nilainya adalah <code class="language-plaintext highlighter-rouge">backup-documents</code> dan <code class="language-plaintext highlighter-rouge">data-</code></p>
<p>Selanjutnya, kembali ke halaman bucket, buka tab Properties, dan klik tab Lifecycle</p>
<p><a href="https://lh3.googleusercontent.com/HMhpK8EXbf95SU9SCPtsEO3U-QDGMu1TzHXLMpEZ-pa0hZGcPUQ=w1280-no"><img src="https://lh3.googleusercontent.com/HMhpK8EXbf95SU9SCPtsEO3U-QDGMu1TzHXLMpEZ-pa0hZGcPUQ=w1280-no" alt="Konfigurasi Lifecycle" /></a></p>
<p>Tambahkan Rule untuk memfilter prefix <code class="language-plaintext highlighter-rouge">backup-documents/data-</code>. Sebagai catatan, prefix ini tidak mendukung regex. Jadi isilah apa adanya.</p>
<p><a href="https://lh3.googleusercontent.com/O7TPqC0wCP3WSBOEXeViw1vNNOrXHN80Encxr1q42WN6qO0KLvU=w1280-no"><img src="https://lh3.googleusercontent.com/O7TPqC0wCP3WSBOEXeViw1vNNOrXHN80Encxr1q42WN6qO0KLvU=w1280-no" alt="Rule Prefix" /></a></p>
<p>Selanjutnya, kita atur supaya file difftar langsung dipindah ke Glacier hari itu juga.</p>
<p><a href="https://lh3.googleusercontent.com/olF9mLlwiUQcLv0GKCKactqISXVgjdpYT1tpBefp1zoWEHd-12M=w1280-no"><img src="https://lh3.googleusercontent.com/olF9mLlwiUQcLv0GKCKactqISXVgjdpYT1tpBefp1zoWEHd-12M=w1280-no" alt="Langsung pindah ke Glacier" /></a></p>
<p>Berikan nama supaya jelas</p>
<p><a href="https://lh3.googleusercontent.com/ZLisCTGy1MRTX3QzZf7pKH2gvmga_wsk0Thy1_6KTiPy_c6G2WU=w1280-no"><img src="https://lh3.googleusercontent.com/ZLisCTGy1MRTX3QzZf7pKH2gvmga_wsk0Thy1_6KTiPy_c6G2WU=w1280-no" alt="Nama Rule" /></a></p>
<p>Ulangi hal yang sama untuk semua folder backup</p>
<p><a href="https://lh3.googleusercontent.com/W4Z7gZNNlj2OhuflZJK_iIuf11VwhhYYmo8x-GyWl8uA57taEFk=w1280-no"><img src="https://lh3.googleusercontent.com/W4Z7gZNNlj2OhuflZJK_iIuf11VwhhYYmo8x-GyWl8uA57taEFk=w1280-no" alt="Rule Glacier" /></a></p>
<p>Lihat lagi besoknya, dan file kita sudah dipindahkan dari S3 ke Glacier.</p>
<p><img src="https://lh3.googleusercontent.com/ghj7nOi-9LXzGnNePC2TDvfbxu4vIsuNi8s_g7pEL-Nu7j0PyfU=w1280-no" alt="Storage Class
Glacier" /></p>
<h3 id="solusi-fakir-bandwidth">Solusi Fakir Bandwidth</h3>
<p>Banyak di antara kita yang memiliki koneksi internet yang terbatas. Baik secara kecepatan maupun secara kuota. Agar kita tetap bisa melakukan backup ke Amazon Glacier, kita bisa mengakalinya dengan cara melakukan enkripsi dengan <code class="language-plaintext highlighter-rouge">Duplicity</code> secara local. Berikut perintah yang kita jalankan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>duplicity --encrypt-key 0x80D2744B0EB1FA47 --volsize 200 /lokasi/foto/yang/mau/dibackup file:///folder/tujuan/backup
</code></pre></div></div>
<p>Untuk bisa mengupload ke Amazon Glacier, kita perlu file konfigurasi credential yang berada di <code class="language-plaintext highlighter-rouge">.aws/credentials</code>. Isinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws_access_key_id = 'MASUKKANACCESSKEYDISINI'
aws_secret_access_key = 'awsSECRETaccessKEYanda'
</code></pre></div></div>
<p>Kita juga perlu menginstal aplikasi command line AWS, caranya bisa dibaca <a href="http://docs.aws.amazon.com/cli/latest/userguide/installing.html">di dokumentasi resminya</a>.</p>
<p>Setelah persiapan lengkap, kita bisa upload dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3 sync /folder/tujuan/backup s3://endy-backup/backup-foto
</code></pre></div></div>
<p>Hasil akhirnya akan sama saja dengan backup langsung ke Glacier.</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Backup itu penting. Dengan sudah semakin canggihnya teknologi di jaman sekarang, alternatif tempat penyimpanan semakin murah dan mudah digunakan. Saat ini kombinasi paling optimal yang saya gunakan adalah:</p>
<ul>
<li>Backup minimal seminggu sekali ke external harddisk</li>
<li>Setiap dapat koneksi internet yang bagus, upload ke Amazon S3</li>
<li>Setup rule di Amazon S3 untuk memindahkan data backup ke Glacier, supaya lebih murah</li>
</ul>
<h2 id="referensi">Referensi</h2>
<ul>
<li>http://cloudacademy.com/blog/amazon-s3-and-amazon-glacier-together-the-best-of-both-worlds-for-your-backup-strategy/</li>
<li>http://college.wfu.edu/itg/scott-claybrook/2014/10/21/dropbox-is-not-a-backup-service/</li>
<li>http://blog.epsilontik.de/?page_id=68</li>
<li>http://www.sleptlate.org/2013/04/15/incremental-backups-in-aws-glacier-using-duplicity/</li>
<li>https://theholyjava.wordpress.com/2015/04/03/backup-wd-mycloud-to-s3glacier-with-duplicity-build-instructions-included/</li>
<li>https://aws.amazon.com/blogs/aws/archive-s3-to-glacier/</li>
<li>https://storagemojo.com/2014/04/25/amazons-glacier-secret-bdxl/</li>
<li>http://www.businessinsider.co.id/best-cloud-storage-price-google-drive-dropbox-icloud-one-drive-2014-12/</li>
<li>https://blog.serverdensity.com/secure-encrypted-backup-using-duplicity-for-linux-and-mac/</li>
<li>https://blog.fite.cat/2014/05/full-backup-with-duplicity-and-dropbox/</li>
<li>https://www.digitalocean.com/community/tutorials/how-to-use-duplicity-with-gpg-to-securely-automate-backups-on-ubuntu</li>
<li>http://blog.yadutaf.fr/2012/09/08/lazy-man-backup-strategy-with-duplicity-part-1/</li>
<li>http://www.leehodgkinson.com/blog/using-glacier-to-lower-the-cost-of-backup-of-large-folders/</li>
<li>http://dougbelshaw.com/blog/2013/08/28/why-im-saying-goodbye-to-dropbox-and-hello-to-spideroak-hive/</li>
<li>https://www.jwz.org/blog/2011/04/dropbox-doesnt-actually-encrypt-your-files/</li>
</ul>
Menggunakan GPG2015-08-19T16:28:00+07:00https://software.endy.muhardin.com/linux/menggunakan-gpg<p>Pada artikel terdahulu, kita telah membahas asymmetric encryption menggunakan OpenSSL, dan aplikasinya dalam SSL. Teknologi SSL banyak digunakan dalam aplikasi client server, misalnya:</p>
<ul>
<li>Web Server (https)</li>
<li>Email Server (SMTP, POP3, IMAP)</li>
</ul>
<p>Dalam SSL, kita membuatkan keypair yang nantinya akan digunakan oleh aplikasi server (webserver, mailserver) untuk mengenkripsi lalu lintas data yang melalui server tersebut.</p>
<p>Tapi bagaimana kalau kita ingin melakukan enkripsi pada file, chat, atau email kita sendiri? Untuk keperluan personal, kita menggunakan aplikasi yang disebut dengan <a href="https://en.wikipedia.org/wiki/GNU_Privacy_Guard">GPG (Gnu Privacy Guard)</a>. GPG ini adalah implementasi dari standar OpenPGP, yang diawali dari aplikasi PGP karya Phil Zimmerman. Bagi yang tertarik dengan pelajaran sejarah, bisa baca <a href="https://en.wikipedia.org/wiki/Pretty_Good_Privacy">ceritanya di Wikipedia</a>.</p>
<p>Di artikel ini, kita akan membahas apa itu GPG, kapan kita menggunakannya, dan bagaimana cara menggunakannya.</p>
<!--more-->
<ul id="markdown-toc">
<li><a href="#apa-itu-gpg" id="markdown-toc-apa-itu-gpg">Apa itu GPG</a></li>
<li><a href="#manajemen-key" id="markdown-toc-manajemen-key">Manajemen Key</a> <ul>
<li><a href="#generate-keypair" id="markdown-toc-generate-keypair">Generate Keypair</a></li>
<li><a href="#melihat-isi-keyring" id="markdown-toc-melihat-isi-keyring">Melihat isi keyring</a></li>
<li><a href="#membuat-revocation-certificate" id="markdown-toc-membuat-revocation-certificate">Membuat Revocation Certificate</a></li>
<li><a href="#export-public-key" id="markdown-toc-export-public-key">Export Public Key</a></li>
<li><a href="#backup-keypair" id="markdown-toc-backup-keypair">Backup Keypair</a></li>
<li><a href="#import-key" id="markdown-toc-import-key">Import Key</a></li>
<li><a href="#menghapus-key" id="markdown-toc-menghapus-key">Menghapus Key</a></li>
<li><a href="#edit-key" id="markdown-toc-edit-key">Edit Key</a></li>
</ul>
</li>
<li><a href="#penggunaan-keypair" id="markdown-toc-penggunaan-keypair">Penggunaan Keypair</a> <ul>
<li><a href="#aplikasi-gpg-pada-file" id="markdown-toc-aplikasi-gpg-pada-file">Aplikasi GPG pada file</a></li>
<li><a href="#aplikasi-gpg-pada-email" id="markdown-toc-aplikasi-gpg-pada-email">Aplikasi GPG pada Email</a></li>
<li><a href="#aplikasi-gpg-untuk-backup" id="markdown-toc-aplikasi-gpg-untuk-backup">Aplikasi GPG untuk Backup</a></li>
</ul>
</li>
<li><a href="#referensi" id="markdown-toc-referensi">Referensi</a></li>
</ul>
<h2 id="apa-itu-gpg">Apa itu GPG</h2>
<p>Pada dasarnya, GPG menggunakan prinsip asymmetric encryption, yaitu enkripsi dan dekripsi menggunakan dua key yang berbeda. Walaupun demikian, dia juga punya opsi untuk melakukan symmetric encryption seperti akan kita praktekkan nanti.</p>
<p>Kita akan memiliki sepasang key (keypair), yaitu private key dan public key. Sesuai namanya, private key haruslah dirahasiakan, dan public key biasanya disebarluaskan ke seluruh dunia. Public key dan private key ini diciptakan bersamaan dan berpasangan.</p>
<p>Ada dua fungsi utama private key, yaitu:</p>
<ul>
<li>membubuhkan tandatangan (digital signature) pada message/file yang kita kirim. Karena private key ini hanya kita yang punya, maka kalau ada signature yang dibuat dengan private key tersebut, bisa dipastikan bahwa message/file tersebut berasal dari kita.</li>
<li>melakukan dekripsi terhadap pesan yang dienkripsi dengan pasangan public keynya. Semua orang bisa punya public key kita, sehingga semua orang bisa mengenkripsi pesan yang dia mau kirim ke kita. Karena hanya kita yang punya private key, maka cuma kita yang bisa membuka pesan terenkripsi tersebut.</li>
</ul>
<p>Demikian sebaliknya, ada dua fungsi utama public key, yaitu:</p>
<ul>
<li>melakukan verifikasi terhadap digital signature. Semua orang bisa mendapatkan public key, sehingga bila kita membuat pesan/pengumuman, lalu kita tandatangani menggunakan private key, masyarakat bisa memastikan bahwa pesan/pengumuman tersebut benar-benar berasal dari kita.</li>
<li>mengenkripsi pesan/file yang ditujukan untuk pemilik private key. Seperti penjelasan sebelumnya, hanya pemilik private key yang bisa membukanya.</li>
</ul>
<p>Selanjutnya, kita akan membahas penggunaan GPG yang umum dilakukan, diantaranya:</p>
<ul>
<li>Manajemen Key</li>
<li>Enkripsi dan Dekripsi</li>
<li>Digital Signature</li>
<li>Integrasi Email</li>
<li>Backup dan Restore Data Pribadi</li>
</ul>
<h2 id="manajemen-key">Manajemen Key</h2>
<p>Ada beberapa hal yang biasa kita lakukan berkaitan dengan keypair:</p>
<ul>
<li>Membuat (generate) keypair</li>
<li>Menyimpan keypair dalam bentuk text</li>
<li>Backup keypair</li>
<li>Import key ke dalam keyring</li>
<li>Mempublikasikan public key</li>
<li>Membatalkan (revoke) keypair</li>
</ul>
<h3 id="generate-keypair">Generate Keypair</h3>
<p>Sebelum bisa menggunakan GPG, terlebih dulu kita harus memiliki pasangan private dan public key. Kita bisa membuatnya dengan menggunakan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --full-generate-key
</code></pre></div></div>
<p>GPG akan mengajukan beberapa pertanyaan seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(14) Existing key from card
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 2y
Key expires at Wed Jan 18 13:23:40 2023 WIB
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name: Endy Muhardin
Email address: endy.muhardin@gmail.com
Comment:
You selected this USER-ID:
"Endy Muhardin <endy.muhardin@gmail.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?
</code></pre></div></div>
<p>Ada beberapa opsi yang kita harus isi:</p>
<ul>
<li>panjang key : pilih sepanjang mungkin, misalnya 4096 bit</li>
<li>tanggal kadaluarsa (expire) : biasanya saya gunakan 2 tahun. Jangan dibuat tak hingga, takutnya kita kehilangan private key dan tidak punya revocation certificate, sehingga public key kita tidak bisa dibatalkan. Ini sering terjadi pada pemula, termasuk saya :) Sampai sekarang public key saya atas nama <code class="language-plaintext highlighter-rouge">endy@artivisi.com</code> tidak bisa dibatalkan, dan juga tidak bisa saya gunakan karena private keynya sudah hilang.</li>
<li>nama lengkap : isi sesuai nama kita</li>
<li>email : isi sesuai email. Umumnya, GPG digunakan untuk enkripsi dan sign email. Jadi biasanya satu keypair berkaitan dengan satu email.</li>
</ul>
<p>Pada saat proses generate dilakukan, GPG akan membangkitkan bilangan acak (random number) supaya key kita tidak mudah ditebak orang. Semakin panjang key, semakin besar bilangan acak yang dibutuhkan (entropy). Untuk itu, pada waktu proses generate key, kita diminta untuk melakukan berbagai aktivitas yang membuat komputer kita sibuk. Bila entropy kurang, maka akan muncul pesan error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
Not enough random bytes available. Please do some other work to give
the OS a chance to collect more entropy! (Need 142 more bytes)
</code></pre></div></div>
<p>Beberapa hal yang biasa dilakukan untuk meningkatkan entropy diantaranya:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">ls -R /</code> : menampilkan daftar nama file di seluruh komputer</li>
<li><code class="language-plaintext highlighter-rouge">find / > /dev/null</code> : mencari file di seluruh komputer</li>
</ul>
<p>Kedua aktifitas tersebut akan membuat harddisk bekerja keras sehingga menimbulkan bilangan acak yang banyak jumlahnya. Bila masih kurang, tancapkan semua harddisk external yang bisa Anda dapatkan, pakai USB hub bila perlu.</p>
<p>Ada hal penting yang perlu diperhatikan : <strong>cara yang salah dalam membangkitkan entropy</strong>. Bila kita google dengan keyword <code class="language-plaintext highlighter-rouge">gpg not enough entropy</code>, maka akan muncul saran untuk melakukan perintah seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rngd -f -r /dev/urandom
</code></pre></div></div>
<p>Cara di atas <strong>sangat tidak aman</strong>, karena bilangan acak yang dihasilkan <code class="language-plaintext highlighter-rouge">urandom</code> ternyata tidak terlalu acak. Bahkan hal ini sudah diwanti-wanti <a href="http://manpages.ubuntu.com/manpages/precise/man4/random.4.html">dalam user manualnya</a> sebagai berikut</p>
<blockquote>
<p>A read from the /dev/urandom device will not block waiting for more
entropy. As a result, if there is not sufficient entropy in the
entropy pool, the returned values are theoretically vulnerable to a
cryptographic attack on the algorithms used by the driver. Knowledge
of how to do this is not available in the current unclassified
literature, but it is theoretically possible that such an attack may
exist. If this is a concern in your application, use /dev/random
instead.</p>
</blockquote>
<p>Walaupun ada juga yang menganggap itu <a href="http://www.2uo.de/myths-about-urandom/">mitos belaka</a>.</p>
<p>Setelah proses generate key selesai, keypair yang baru dibuat secara otomatis akan dimasukkan ke dalam <code class="language-plaintext highlighter-rouge">keyring</code>, yaitu database key yang kita miliki.</p>
<p>Kadangkala kita akan mendapatkan pesan error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg: can't connect to the agent: No such file or directory
gpg: agent_genkey failed: No agent running
Key generation failed: No agent running
</code></pre></div></div>
<p>Itu disebabkan karena <code class="language-plaintext highlighter-rouge">gpg-agent</code> belum aktif. Kita bisa aktifkan dulu dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/local/Cellar/gnupg/2.2.27/bin/gpg-agent -v --daemon
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg-agent[11577]: listening on socket '/Users/endymuhardin/.gnupg/S.gpg-agent'
gpg-agent[11577]: listening on socket '/Users/endymuhardin/.gnupg/S.gpg-agent.extra'
gpg-agent[11577]: listening on socket '/Users/endymuhardin/.gnupg/S.gpg-agent.browser'
gpg-agent[11577]: listening on socket '/Users/endymuhardin/.gnupg/S.gpg-agent.ssh'
gpg-agent[11578]: gpg-agent (GnuPG) 2.2.27 started
</code></pre></div></div>
<h3 id="melihat-isi-keyring">Melihat isi keyring</h3>
<p>Kita bisa melihat isi keyring dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --list-key
</code></pre></div></div>
<p>Outputnya akan tampil seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/home/endy/.gnupg/pubring.gpg
-----------------------------
pub 2048R/0x3D115775D2C19EB3 2011-07-20 [expires: 2017-08-16]
Key fingerprint = 9E3C 7469 2D78 8562 92A0 B0D3 3D11 5775 D2C1 9EB3
uid [ultimate] Endy Muhardin (endy) <endy.muhardin@gmail.com>
sub 2048R/0xBB31E545AF288E4C 2011-07-20 [expires: 2017-08-16]
</code></pre></div></div>
<p>Keyring kita berisi keypair kita sendiri, dan juga bisa diisi dengan public key orang lain. Public key orang lain akan berguna apabila:</p>
<ul>
<li>kita menerima email/pesan/file ber-signature. Kita butuh public key pengirim untuk melakukan verifikasi</li>
<li>kita ingin mengirim pesan/file rahasia ke orang tersebut. Kita butuh public key untuk melakukan enkripsi</li>
</ul>
<h3 id="membuat-revocation-certificate">Membuat Revocation Certificate</h3>
<p>Ada beberapa kasus dimana kita tidak lagi ingin memakai keypair, diantaranya:</p>
<ul>
<li>private key sudah bocor ke tangan orang lain</li>
<li>kita ingin mengganti key dengan yang lebih panjang</li>
<li>dan lainnya</li>
</ul>
<p>Untuk itu, kita membuat certificate pembatalan (revocation) dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --gen-revoke -a endy.muhardin@gmail.com > endymuhardin-revoke.asc
</code></pre></div></div>
<p>File ini perlu dijaga. Kalau sampai file ini jatuh ke tangan orang iseng, maka bisa digunakan untuk membatalkan public dan private key kita.</p>
<h3 id="export-public-key">Export Public Key</h3>
<p>Setelah kita memiliki keypair, selanjutnya kita ingin membagikan public key kepada masyarakat banyak. Ada banyak cara, diantara yang tradisional adalah memasangnya di website pribadi kita. Untuk itu, kita perlu export public key tersebut dalam bentuk text file. Demikian perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --export -a endy.muhardin@gmail.com > endymuhardin.asc
</code></pre></div></div>
<p>Penjelasan opsinya sebagai berikut :</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">--export</code> : lakukan export</li>
<li><code class="language-plaintext highlighter-rouge">-a</code> : output dalam bentuk text (ASCII)</li>
<li><code class="language-plaintext highlighter-rouge">endy.muhardin@gmail.com</code> : email yang digunakan pada public key</li>
</ul>
<p>Perintah di atas akan menghasilkan file <code class="language-plaintext highlighter-rouge">endymuhardin.asc</code> yang berisi public key. File ini bisa dibuka dengan text editor biasa.</p>
<h3 id="backup-keypair">Backup Keypair</h3>
<p>Keypair yang sudah kita buat, perlu kita backup, terutama private key. Sebab tanpa private key, kita tidak bisa lagi membuka pesan terenkripsi dan tidak lagi bisa memasang digital signature.</p>
<p>Adapun public key tidak perlu dibackup, karena bisa digenerate dari private key.</p>
<p>Proses backupnya sederhana, yaitu kita tinggal melakukan export agar private key kita muncul dalam bentuk text file. Perintahnya sederhana</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --export-secret-keys -a endy.muhardin@gmail.com > endymuhardin-plain.asc
</code></pre></div></div>
<p>Walaupun demikian, cara di atas belum cukup. File yang dihasilkan adalah private key yang terbuka (plain). Siapapun yang mendapatkannya bisa langsung menggunakannya untuk membuka file-file rahasia kita. Kita ingin mengenkripsi file private key ini supaya tidak bisa digunakan orang lain.</p>
<p>Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --export-secret-keys -a endy.muhardin@gmail.com | gpg --symmetric --cipher-algo AES256 -a > endymuhardin-encrypted.asc
</code></pre></div></div>
<p>Pada waktu dijalankan, perintah tersebut akan meminta password enkripsi. Pilihlah password yang panjang, misalnya</p>
<p><code class="language-plaintext highlighter-rouge">Ini password saya panjang sekali supaya susah ditebak orang lain. Mudah-mudahan saya sendiri tidak lupa</code></p>
<p>Perintah di atas menggabungkan perintah export key dengan perintah enkripsi dengan operator <code class="language-plaintext highlighter-rouge">|</code>. Dengan demikian, tidak ada file <em>temporary</em> yang dapat menimbulkan resiko keamanan.</p>
<p>Hasilnya adalah file text yang terenkripsi. Walaupun orang lain melihat dan mendapatkannya, tapi dia tidak bisa membukanya tanpa tahu passwordnya.</p>
<p>File private key ini bisa kita simpan di tempat yang aman. Bisa di safety deposit box, brankas, atau tempat lain yang dianggap aman. Silahkan baca diskusi di StackOverflow untuk mendapat ide tentang berbagai metode penyimpanan.</p>
<p>Kadangkala kita menemui error seperti ini pada waktu melakukan export/import</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg: problem with the agent: Inappropriate ioctl for device
gpg: error creating passphrase: Operation cancelled
gpg: symmetric encryption of '[stdin]' failed: Operation cancelled
gpg: [stdout]: write error: Broken pipe
gpg: filter_flush failed on close: Broken pipe
</code></pre></div></div>
<p>Solusinya mudah, cukup jalankan perintah berikut di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export GPG_TTY=$(tty)
</code></pre></div></div>
<p>Selanjutnya, kita bisa mengulangi perintah export tadi. Seharusnya sekarang sudah bisa berjalan dengan baik.</p>
<h3 id="import-key">Import Key</h3>
<p>Ada beberapa situasi dimana kita melakukan import, diantaranya:</p>
<ul>
<li>kita baru instal ulang komputer, ingin menggunakan private key yang sudah ada</li>
<li>kita ingin menggunakan public key orang lain</li>
</ul>
<p>Untuk import private key, bila dalam kondisi <em>plain</em> perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat endymuhardin-plain.asc | gpg --batch --import
</code></pre></div></div>
<p>Sedangkan bila private key dienkripsi seperti anjuran di atas, maka perlu didekripsi dulu. Seperti halnya pada waktu enkripsi, proses dekripsi juga kita lakukan dalam satu langkah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --decrypt -a --output - endymuhardin-encrypted.asc | gpg --batch --import
</code></pre></div></div>
<p>Setelah diimport, kita perlu membuat statusnya menjadi <code class="language-plaintext highlighter-rouge">trusted</code> supaya bisa digunakan untuk encrypt/decrypt maupun sign/verify.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --edit-key endy.muhardin@gmail.com
gpg> trust
</code></pre></div></div>
<p>Kita akan dihadapkan pada pertanyaan, mau trust level berapa. Karena ini adalah private key kita sendiri, maka jawab saja <code class="language-plaintext highlighter-rouge">5</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)
1 = I don't know or won't say
2 = I do NOT trust
3 = I trust marginally
4 = I trust fully
5 = I trust ultimately
m = back to the main menu
Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y
</code></pre></div></div>
<h3 id="menghapus-key">Menghapus Key</h3>
<p>Berikut perintah untuk menghapus public key dalam keyring</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --delete-key endy.muhardin@gmail.com
</code></pre></div></div>
<p>Bila kita ingin menghapus public key yang ada private keynya, maka private key harus terlebih dulu dihapus.</p>
<p>Untuk menghapus private key dalam keyring, berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --delete-secret-key endy.muhardin@gmail.com
</code></pre></div></div>
<h3 id="edit-key">Edit Key</h3>
<p>Kita bisa mengedit key yang tersimpan dalam keyring. Beberapa hal yang sering diedit antara lain:</p>
<ul>
<li>trust key yang baru saja kita import</li>
<li>password private key</li>
<li>masa kadaluarsa (expire) public key</li>
</ul>
<p>Untuk mengedit key, berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --edit-key endy.muhardin@gmail.com
</code></pre></div></div>
<p>Nanti kita akan mendapat prompt gpg sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg>
</code></pre></div></div>
<p>Selanjutnya, bila kita ingin mengganti password private key, ketikkan password, kemudian simpan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg> passwd
gpg> save
</code></pre></div></div>
<p>Demikian juga, untuk mengganti masa kadaluarsa, gunakan perintah <code class="language-plaintext highlighter-rouge">expiry</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --edit-key endy.muhardin@gmail.com
gpg> expire
Changing expiration time for the primary key.
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 1y
Key expires at Wed Dec 29 11:38:49 2021 WIB
Is this correct? (y/N) y
sec rsa2048/3D115775D2C19EB3
created: 2011-07-20 expires: 2021-12-29 usage: SC
trust: ultimate validity: unknown
ssb rsa2048/BB31E545AF288E4C
created: 2011-07-20 expired: 2017-08-16 usage: E
[ unknown] (1). Endy Muhardin (endy) <endy.muhardin@gmail.com>
gpg: WARNING: Your encryption subkey expires soon.
gpg: You may want to change its expiration date too.
gpg> save
</code></pre></div></div>
<p>Kita mendapatkan warning bahwa subkey kita juga expire. Sebaiknya kita update sekalian.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --edit-key endy.muhardin@gmail.com
gpg> list
sec rsa2048/3D115775D2C19EB3
created: 2011-07-20 expires: 2021-12-29 usage: SC
trust: ultimate validity: ultimate
ssb rsa2048/BB31E545AF288E4C
created: 2011-07-20 expired: 2017-08-16 usage: E
[ultimate] (1). Endy Muhardin (endy) <endy.muhardin@gmail.com>
gpg> key 1
sec rsa2048/3D115775D2C19EB3
created: 2011-07-20 expires: 2021-12-29 usage: SC
trust: ultimate validity: ultimate
ssb* rsa2048/BB31E545AF288E4C
created: 2011-07-20 expired: 2017-08-16 usage: E
[ultimate] (1). Endy Muhardin (endy) <endy.muhardin@gmail.com>
gpg> expire
Changing expiration time for a subkey.
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 1y
Key expires at Wed Dec 29 11:40:56 2021 WIB
Is this correct? (y/N) y
sec rsa2048/3D115775D2C19EB3
created: 2011-07-20 expires: 2021-12-29 usage: SC
trust: ultimate validity: ultimate
ssb* rsa2048/BB31E545AF288E4C
created: 2011-07-20 expires: 2021-12-29 usage: E
[ultimate] (1). Endy Muhardin (endy) <endy.muhardin@gmail.com>
gpg> save
</code></pre></div></div>
<h2 id="penggunaan-keypair">Penggunaan Keypair</h2>
<p>Setelah kita memiliki keypair, kita bisa gunakan untuk:</p>
<ul>
<li>enkripsi/dekripsi dan signature pada file</li>
<li>enkripsi/dekripsi dan signature pada email</li>
<li>enkripsi backup</li>
</ul>
<h3 id="aplikasi-gpg-pada-file">Aplikasi GPG pada file</h3>
<p>Berikut adalah perintah untuk enkripsi file</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg -a --encrypt -r 'endy.muhardin@gmail.com' namafile.txt
</code></pre></div></div>
<p>Perintah di atas akan membuat file dengan nama <code class="language-plaintext highlighter-rouge">namafile.txt.asc</code></p>
<p>Untuk membuka enkripsi (dekripsi), berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --decrypt namafile.txt.asc > hasil-decrypt.txt
</code></pre></div></div>
<p>GPG akan meminta password untuk membuka private key. Setelah itu, private key digunakan untuk mendekripsi file.</p>
<p>Selanjutnya kita akan membuat digital signature untuk file yang akan kita kirim. Perintahnya sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --sign namafile.txt
</code></pre></div></div>
<p>Akan terbentuk file <code class="language-plaintext highlighter-rouge">namafile.txt.gpg</code> yang berisi signature dari file <code class="language-plaintext highlighter-rouge">namafile.txt</code>.</p>
<p>Untuk memeriksanya, gunakan opsi <code class="language-plaintext highlighter-rouge">verify</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg --verify namafile.txt.gpg
</code></pre></div></div>
<p>Bila lancar, berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg: Signature made Rab 19 Agu 2015 01:46:52 WIB
gpg: using RSA key 0x3D115775D2C19EB3
gpg: Good signature from "Endy Muhardin (endy) <endy.muhardin@gmail.com>" [ultimate]
Primary key fingerprint: 9E3C 7469 2D78 8562 92A0 B0D3 3D11 5775 D2C1 9EB3
</code></pre></div></div>
<h3 id="aplikasi-gpg-pada-email">Aplikasi GPG pada Email</h3>
<p>Pada awalnya, penggunaan GPG yang paling populer adalah untuk membubuhkan signature pada email. Dengan adanya signature, penerima bisa merasa yakin bahwa email benar-benar dikirim oleh pengirim yang sah. Di jaman dahulu, orang biasa menggunakan aplikasi mail client seperti Thunderbird, Outlook, dan sejenisnya. Aplikasi desktop ini bisa dikonfigurasi untuk memuat private key.</p>
<p>Di tahun 2015 ini, sudah jarang orang yang pakai aplikasi mail client berbasis desktop. Mayoritas orang langsung mengakses aplikasi web yang disediakan GMail atau Yahoo. Dengan demikian, ada perubahan cara menggunakan private key. Kita tentu tidak mau mengupload private key kita ke server Google atau Yahoo. Nanti jadi tidak <em>private</em> lagi dong ;)</p>
<p>Solusinya, kita menginstal extension di browser. Extension ini dapat membaca private key, disimpan di komputer kita, dan menggunakannya untuk melakukan <em>sign</em> dan <em>encrypt/decrypt</em>. Dengan kecanggihan JavaScript di jaman sekarang dan penggunaan style CSS yang tepat, extension tersebut bisa mengubah tampilan seolah-olah menyatu dengan aplikasi webnya.</p>
<p>Untuk memakainya, pertama kita search dulu extension tersebut</p>
<p><a href="https://lh3.googleusercontent.com/6fTIWRai02DIsiM2QN4bxbCkqWOB9pA92YwnFU3zC0Y=w1280-no"><img src="https://lh3.googleusercontent.com/6fTIWRai02DIsiM2QN4bxbCkqWOB9pA92YwnFU3zC0Y=w1280-no" alt="Search GPG Plugin" /></a></p>
<p>Kita bisa lihat ada extension untuk Firefox dan Chrome. Mari kita coba install extension Chrome</p>
<p><a href="https://lh3.googleusercontent.com/MXe17kR1ml5SAf-0YQ0taBbakvp983qI1U6Dqke6Pe8=w1280-no"><img src="https://lh3.googleusercontent.com/MXe17kR1ml5SAf-0YQ0taBbakvp983qI1U6Dqke6Pe8=w1280-no" alt="Extension Mymail Crypt" /></a></p>
<p>Setelah terinstal, kita bisa lihat di menu Extension.</p>
<p><a href="https://lh3.googleusercontent.com/TzETAGFvtI2oW-HGFpPop2KceKDYgcGgwP3Squmtq1M=w1280-no"><img src="https://lh3.googleusercontent.com/TzETAGFvtI2oW-HGFpPop2KceKDYgcGgwP3Squmtq1M=w1280-no" alt="Extension MyMail Crypt sudah terinstall" /></a></p>
<p>Klik Option untuk mengisi private key.</p>
<p><a href="https://lh3.googleusercontent.com/Zl-PhaBB0s3IAXYRR6ZliyTpgZNduoZ7cPD9dmaVv5E=w1280-no"><img src="https://lh3.googleusercontent.com/Zl-PhaBB0s3IAXYRR6ZliyTpgZNduoZ7cPD9dmaVv5E=w1280-no" alt="Menu Option MyMail Crypt" /></a></p>
<p>Untuk mengisi private key, tekan My Keys, kemudian klik Insert Private Key. Selanjutnya kita akan diberikan text area untuk mengisi private key</p>
<p><a href="https://lh3.googleusercontent.com/6CuyuhC-j4kzGuns2z7eDwUsnyFEL0PlCyMYI6ZI5hA=w1280-no"><img src="https://lh3.googleusercontent.com/6CuyuhC-j4kzGuns2z7eDwUsnyFEL0PlCyMYI6ZI5hA=w1280-no" alt="Insert Private Key" /></a></p>
<p>Export private key kita sehingga menjadi format text seperti dibahas sebelumnya. Kemudian paste isinya ke dalam text input.</p>
<p><a href="https://lh3.googleusercontent.com/pvJ8yeni0h-NvqD9OGOcD7-LO9Eljb0qfe5shM3PE1M=w1280-no"><img src="https://lh3.googleusercontent.com/pvJ8yeni0h-NvqD9OGOcD7-LO9Eljb0qfe5shM3PE1M=w1280-no" alt="Private Key terdaftar" /></a></p>
<p>Hasilnya bisa kita lihat di atas, keypair kita sudah siap digunakan.</p>
<p>Selanjutnya, restart browser, kemudian coba buka Gmail. Klik salah satu email untuk menampilkan isinya.</p>
<p><a href="https://lh3.googleusercontent.com/zDW1Xu0rg0Y4YmUIj49ji3cElfcwyhK-bkxEEdz76Ro=w1280-no"><img src="https://lh3.googleusercontent.com/zDW1Xu0rg0Y4YmUIj49ji3cElfcwyhK-bkxEEdz76Ro=w1280-no" alt="View Email" /></a></p>
<p>Kita bisa lihat ada tambahan menu untuk mengecek signature (verify) dan melakukan dekripsi (bila emailnya terenkripsi). Di situ ada kolom password untuk memasukkan passphrase private key kita.</p>
<p>Fitur extension ini juga tersedia pada saat kita mau mengirim email. Kita bisa memberikan signature kita, atau melakukan enkripsi dengan public key orang lain. Langsung saja klik Compose seperti biasa.</p>
<p><a href="https://lh3.googleusercontent.com/T-FuRISAv3Zs9muNubuqyZSHPPH_hT7TeuISqSxLvhw=w1280-no"><img src="https://lh3.googleusercontent.com/T-FuRISAv3Zs9muNubuqyZSHPPH_hT7TeuISqSxLvhw=w1280-no" alt="Compose Mail" /></a></p>
<p>Di situ kita bisa lihat ada tambahan tombol baru Encrypt dan Sign.</p>
<h3 id="aplikasi-gpg-untuk-backup">Aplikasi GPG untuk Backup</h3>
<p>Di jaman modern sekarang ini, banyak sekali tersedia layanan penyimpanan data di awan (cloud storage) dengan harga yang sangat murah. Diantaranya:</p>
<ul>
<li>Dropbox</li>
<li>Google Drive</li>
<li>Microsoft OneDrive</li>
<li>Amazon S3</li>
<li>Amazon Glacier</li>
<li>Apple iCloud</li>
<li>dan sebagainya</li>
</ul>
<p>Bahkan Amazon Glacier menawarkan harga <strong>$0.01 per GB per bulan</strong> !!! Sebagai ilustrasi, koleksi foto dan video keluarga saya sejak tahun 2004 saat ini mencapai 200an GB.Untuk menyimpan semua data tersebut, biayanya hanya $2/bulan.</p>
<p>Tentunya untuk alasan keamanan, kita tidak bisa menyimpan data tersebut begitu saja. Kita harus enkripsi dulu file-filenya sebelum diupload. Ada aplikasi canggih bernama <a href="http://duplicity.nongnu.org/duplicity.1.html">duplicity</a> yang bisa mengenkripsi file kita, kemudian menguploadnya ke berbagai layanan storage di atas. Tidak cuma itu, bila data kita berubah, duplicity juga cukup cerdas untuk hanya mengupload perubahannya saja. Dengan demikian, kita bisa menghemat bandwidth untuk upload.</p>
<p>Penggunaan duplicity untuk melakukan backup akan kita bahas pada <a href="https://software.endy.muhardin.com/linux/backup-duplicity/">artikel selanjutnya</a>. Duplicity membutuhkan keypair untuk melakukan enkripsi. Jadi, silahkan berlatih GPG dulu agar nantinya mudah menggunakan Duplicity.</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li>http://irtfweb.ifa.hawaii.edu/~lockhart/gpg/gpg-cs.html</li>
<li>https://montemazuma.wordpress.com/2010/03/01/moving-a-gpg-key-privately/</li>
<li>https://blog.fite.cat/2014/05/full-backup-with-duplicity-and-dropbox/</li>
<li>http://www.thegeekstuff.com/2013/04/gnupg-digital-signatures/</li>
<li>http://duplicity.nongnu.org/duplicity.1.html</li>
</ul>
Mencari Bottleneck Sistem2015-02-24T05:28:00+07:00https://software.endy.muhardin.com/linux/mencari-bottleneck-sistem<p>Sudah sebulan ini laptop saya lemot.</p>
<blockquote>
<p>Apa maksudnya lemot? Seperti apa yang dibilang lemot?</p>
</blockquote>
<p>Pada waktu kuliah dulu, di tahun 1999, komputer saya spesifikasinya Pentium 233 MMX dengan RAM 32 MB (Mega, bukan Giga). Dia berjalan cukup responsif dengan sistem operasi Windows 98 SE. Di tahun 2000, Microsoft merilis Windows 2000 Professional. Saya instal di komputer saya tersebut, dan dia langsung lemot seperti bekicot.</p>
<p><a href="https://lh3.googleusercontent.com/-lYF4E2oWQvU/VOyUY1ZEmbI/AAAAAAAAH90/Tomn_Tcatw4/w640-h480-no/Gary_bored_again..png"><img src="https://lh3.googleusercontent.com/-lYF4E2oWQvU/VOyUY1ZEmbI/AAAAAAAAH90/Tomn_Tcatw4/w640-h480-no/Gary_bored_again..png" alt="Gary the Snail" /></a></p>
<p>Gambar diambil dari <a href="http://spongebob.wikia.com/wiki/Gary_the_Snail/gallery">sini</a></p>
<p>Laptop saya sekarang ini spesifikasinya cukup mumpuni, yaitu sebagai berikut:</p>
<ul>
<li>Processor Pentium Core i5</li>
<li>RAM 8 GB</li>
<li>Solid State Disk Corsair 128 GB</li>
</ul>
<p>Laptop tersebut diinstal Ubuntu 14.04 yang selalu up-to-date dengan partisi <code class="language-plaintext highlighter-rouge">/home</code> terpisah dan terenkripsi.</p>
<p>Dengan spesifikasi tersebut, normalnya laptop saya ini bekerja sekejap mata. Tekan tombol power, sekejap mata muncul halaman login. Isikan password, sekejap mata muncul desktopnya. Klik icon Chrome, dalam sekejap Gmail sudah tersaji. Nah, sudah sebulan ini dia lemot, seperti Pentium 233 MMX yang diinstal Windows 2000 Professional dulu.</p>
<p>Berikut langkah-langkah yang saya tempuh sampai akhirnya menemukan solusinya, sebulan kemudian ;)</p>
<!--more-->
<h2 id="mencurigai-aplikasi">Mencurigai Aplikasi</h2>
<p>Hal pertama yang saya curigai adalah aplikasi yang berjalan di background dan mungkin menghabiskan resource. Saya ingat-ingat lagi apa perubahan yang saya lakukan baru-baru ini. Teringat adanya launching Whatsapp yang bisa diakses melalui browser. Biasanya saya pakai browser Chromium, tapi karena ingin menggunakan Whatsapp di laptop, akhirnya saya install Chrome.</p>
<p>Tersangka pertama adalah Chrome. Saya coba uninstall Chrome, kembali ke Chromium. Tetap lemot. Uninstall Chromium, pakai Firefox, tidak ada pengaruh. Hmm … mungkin bukan di situ masalahnya. Saatnya kita melakukan langkah yang lebih drastis.</p>
<h2 id="mencurigai-sistem-operasi">Mencurigai Sistem Operasi</h2>
<p>Sebelumnya saya menggunakan Ubuntu 14.04. Usianya sudah hampir setahun. Yang sudah-sudah, saya selalu upgrade tiap 6 bulan, sehingga harusnya saya sudah menggunakan 14.10. Tapi kemarin itu karena malas reinstall, akhirnya saya biarkan. Mungkin ini masalahnya, mari kita coba.</p>
<p>Laptop saya format, diganti dengan Ubuntu 14.10 32bit. Instal ulang semua aplikasi, menghabiskan 2 GB kuota internet. Ternyata tetap lemot.</p>
<p>Format lagi, ganti dengan 14.10 64bit. Habiskan lagi kuota 2 GB untuk menginstal aplikasi, tetap lemot juga.</p>
<p>Sempat terpikir mencurigai partisi <code class="language-plaintext highlighter-rouge">/home</code> yang terenkripsi. Mungkin saja proses enkripsi ini memakan resource yang besar. Tapi tidak saya teruskan penelusuran ke arah sana, karena enkripsi ini sudah saya aktifkan sejak tahun lalu dan lancar-lancar saja.</p>
<p>Berarti jelas sudah, masalahnya bukan di sistem operasi. Saatnya kita gunakan bantuan, phone a friend.</p>
<h2 id="mencurigai-desktop-manager">Mencurigai Desktop Manager</h2>
<p>Karena buntu, saya putuskan untuk berkonsultasi dengan orang lain. Saya sudah menggunakan Linux sejak sebelum lulus, sekitar tahun 2000-2001. Mulai tahun 2002 saya tidak lagi menginstal Windows sama sekali di laptop. Bukan perkara mudah mencari teman diskusi yang memiliki jam terbang Linux lebih dari 12 tahun.</p>
<p>Untungnya orangnya ada, seleb pula :D. The one and only <a href="http://antonraharja.com">Anton Raharja</a>. Dia sudah pakai Linux sebelum saya punya komputer. Mudah-mudahan beliau punya solusi. Dan ternyata memang ada.</p>
<p>Dia sempat mengalami masalah lemot, dan <a href="http://linuxaria.com/howto/how-to-remove-zeitgeist-in-ubuntu-and-why">masalahnya ada di Zeitgeist</a>. Zeitgeist adalah service di Ubuntu yang bertugas untuk mencatat kegiatan user. Aplikasi yang sering dipakai, dokumen yang sering dibuka, bahkan dia seringkali menjelajahi harddisk kita untuk membuat index. Gunanya supaya kalau kita mencari sesuatu, Ubuntu bisa memberikan rekomendasi yang tepat. Solusinya adalah dengan <a href="http://antonraharja.com/2014/10/21/my-slow-slow-ubuntu-14-10/">mematikan si Zeitgeist ini</a>.</p>
<p>Baiklah saya coba …. dan tidak ngaruh juga :((</p>
<p>Oke, bukan Zeitgeist. Mungkin driver VGA card yang kurang sempurna sehingga lemot ketika dipakai merender efek visual desktop manager Unity yang digunakan Ubuntu. Sebetulnya kecurigaan ini kecil, karena VGA card saya Intel yang relatif aman di Linux. Berbeda dengan nVidia atau ATI yang sering bermasalah. Anyway, layak dicoba.</p>
<p>Saya ganti Unity dengan Cinnamon, desktop manager Linux Mint yang konon katanya sederhana, minimalis, dan ringan.</p>
<p>No dice …. -_-</p>
<h2 id="going-scientific">Going Scientific</h2>
<p>Hmmm, apa lagi yang bisa dicoba?</p>
<blockquote>
<p>Hei hei, dulu siapa yang menulis artikel <a href="http://software.endy.muhardin.com/programming/tuning-performance">Tuning Performance</a> ?</p>
</blockquote>
<p>Kan di situ sudah dijelaskan bahwa kita harus pasang monitor untuk mengukur penggunaan resource. Jangan main tebak-tebakan.</p>
<blockquote>
<p>Baiklah, mari kita ambil langkah kuantitatif.</p>
</blockquote>
<p>Pertama, mari kita amati penggunaan memori. Semua sistem operasi memiliki fenomena yang disebut <code class="language-plaintext highlighter-rouge">thrashing</code>, yaitu <a href="http://en.wikipedia.org/wiki/Thrashing_%28computer_science%29">kehabisan memori sehingga data yang ada ditulis ke harddisk</a> untuk membuat ruang buat aplikasi yang membutuhkannya. Karena akses ke harddisk jauh lebih lemot daripada akses ke memori, maka keseluruhan sistem akan menjadi tidak responsif.</p>
<p>Gunakan aplikasi <code class="language-plaintext highlighter-rouge">top</code> untuk memonitor pemakaian memori. Mumpung sedang buka <code class="language-plaintext highlighter-rouge">top</code>, sekalian saja amati pemakaian CPU.</p>
<blockquote>
<p>Ternyata memori dan CPU santai, saudara-saudara.</p>
</blockquote>
<p>Baiklah, mari kita cek tersangka berikut: I/O harddisk. Aktifitas baca tulis harddisk, bila tidak optimal, akan membuat sistem lambat. Bisa jadi harddisk saya error, sehingga proses I/O tidak berjalan lancar.</p>
<p>Untuk mengamati kegiatan I/O, kita install aplikasi <code class="language-plaintext highlighter-rouge">iotop</code>. Jalankan, amati pada saat dia sedang lemot, dan ternyata …. santai juga !!!</p>
<blockquote>
<p>Ada apa ini?? Kenapa dia lemot padahal semua resource tidak terpakai?</p>
</blockquote>
<h2 id="faktor-luck-dan-pantang-menyerah">Faktor Luck dan Pantang Menyerah</h2>
<p>Pada titik ini, tentunya Anda tidak perlu menyarankan saya untuk <a href="http://software.endy.muhardin.com/aplikasi/teknik-menggunakan-google">mencari di Google</a>. Berbagai keyword sudah saya coba, diantaranya yang saya ingat</p>
<ul>
<li>ubuntu slow</li>
<li>ubuntu sluggish</li>
<li>ubuntu chrome whatsapp slow</li>
<li>ubuntu compiz not responsive</li>
<li>ubuntu ssd slow</li>
<li>dan berbagai kombinasi keyword lainnya</li>
</ul>
<p>Akhirnya, titik terang ditemukan dengan keyword <code class="language-plaintext highlighter-rouge">ubuntu vga intel bug</code>. Salah satu link yang diberikan google mengarah ke <a href="http://askubuntu.com/questions/186387/laptop-slows-down-while-charging-battery">diskusi di AskUbuntu ini</a>.</p>
<p><a href="https://lh6.googleusercontent.com/-9w3o3-cb5uA/VOyR5GuGRUI/AAAAAAAAH9c/p1ncooIqOaA/w884-h552-no/01.%2BAskUbuntu%2BLaptop%2BSlow.png"><img src="https://lh6.googleusercontent.com/-9w3o3-cb5uA/VOyR5GuGRUI/AAAAAAAAH9c/p1ncooIqOaA/w884-h552-no/01.%2BAskUbuntu%2BLaptop%2BSlow.png" alt="Screenshot AskUbuntu" /></a></p>
<p>Dari artikel di atas, saya dapat keyword, yaitu <code class="language-plaintext highlighter-rouge">ubuntu slow when charging</code>.</p>
<blockquote>
<p>Masa sih, charger mempengaruhi performance?</p>
</blockquote>
<p>Saya coba. Kan gampang, tinggal cabut dan colok chargernya. Ternyata memang benar ini masalahnya. Begitu charger dicolok, berapapun kondisi baterai (10% ataupun 70%), langsung lemot. Copot charger, langsung ngebut.</p>
<blockquote>
<p>Kok bisa ?? Apa hubungannya charger dengan performance?</p>
</blockquote>
<p>Mari kita lanjut google.</p>
<p>Ketemu lagi <a href="https://bbs.archlinux.org/viewtopic.php?id=120892">artikel lain yang isinya senada</a>, yaitu gunakan opsi kernel</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>drm_kms_helper.poll=N
</code></pre></div></div>
<p>Sudah dicoba, tetap lemot. Pantang menyerah, google lagi masih dengan keyword <code class="language-plaintext highlighter-rouge">ubuntu slow when charging</code>. Kali ini dapat <a href="http://www.computing.net/answers/networking/internet-becomes-very-slow-when-laptop-charger-is-plugged/51640.html">artikel berjudul “Internet becomes very slow when laptop charger is plugged”</a>.</p>
<p>Penjelasan tentang pengaruh charger terhadap performance ada di komentar paling bawah</p>
<p><a href="https://lh6.googleusercontent.com/-qgsN3cgs2X4/VOyR5A7CztI/AAAAAAAAH9g/TkUVkKK8nxo/w698-h586-no/02.%2BSolusi%2B-%2BCharger%2Bsoak.png"><img src="https://lh6.googleusercontent.com/-qgsN3cgs2X4/VOyR5A7CztI/AAAAAAAAH9g/TkUVkKK8nxo/w698-h586-no/02.%2BSolusi%2B-%2BCharger%2Bsoak.png" alt="Screenshot solusi" /></a></p>
<p>Charger saya ternyata sudah lemah, sehingga dia mengeluarkan daya listrik yang kecil. Mendeteksi arus listrik yang tidak memadai, laptop saya menurunkan kemampuan sistem supaya tidak boros energi.</p>
<p>Setelah membaca komentar tadi, saya baru ingat bahwa memang butuh waktu lama untuk mengisi baterai laptop. Biasanya cuma 2 jam sudah penuh, sekarang butuh waktu 8-10 jam sampai dia penuh.</p>
<p>Test dengan charger laptop adik saya yang semerek, langsung mak joss !!!</p>
<p>Pada kasus yang dibahas di Ask Ubuntu, opsi kernel bisa dipakai karena penurunan kemampuan dilakukan oleh sistem operasi, sehingga bisa di-override melalui konfigurasi. Tapi di laptop saya, penurunan ini dilakukan di level hardware (sebelum sampai ke sistem operasi), sehingga tidak bisa diatasi dengan konfigurasi kernel saja.</p>
<p>Segera beli charger seharga 300 ribu di Mal Ambassador, dan dunia kembali terang benderang :D</p>
<p>Pada saat mulai menulis artikel ini, kondisi baterai ada di 10%. Sekarang, 2 jam kemudian sudah di angka 70%. Now we’re talking ;)</p>
<h2 id="pesan-moral">Pesan Moral</h2>
<p>Dari kejadian ini, ada beberapa hikmah yang bisa kita dapatkan, yaitu</p>
<ol>
<li>
<p>Pantang menyerah. Menulis artikel tentang <a href="http://software.endy.muhardin.com/aplikasi/teknik-menggunakan-google">teknik menggunakan google</a> tidak menjadi jaminan saya bisa menemukan solusi dengan cepat. Butuh waktu sebulan, mencoba berbagai keyword. Membuka artikel dan menemui jalan buntu, mendapatkan ide keyword dari artikel satu untuk digoogle lagi, dan seterusnya.</p>
</li>
<li>
<p>Dalam urusan tuning performance, 99% effort dihabiskan untuk mencari bottleneck (sebulan berpusing ria). Setelah ketemu, perbaikannya cuma 1 jam (pinjam charger untuk memastikan, kemudian beli charger baru ke Ambas).</p>
</li>
<li>
<p>Urusan tuning mirip dengan diagnosa penyakit di dokter. Kita cuma punya dugaan awal, yang akan kita konfirmasi melalui serangkaian tes. Coba lakukan ini, jalani treatment anu, kerjakan test tertentu. Seringkali hal yang kita lakukan bukanlah implementasi solusi, tapi hanya langkah perantara untuk mendapatkan informasi lebih lengkap.</p>
</li>
<li>
<p>Solusi masalah performance seringkali ada di tempat yang tidak kita duga. Oleh karena itu, pada waktu mencari bottleneck jangan terlalu yakin pada dugaan awal. Kalau kita terlalu yakin, pikiran kita akan terpaku ke satu hal tersebut sehingga mengabaikan kemungkinan-kemungkinan lain. Bersikaplah open-minded, inti masalah dan solusi bisa ada di mana saja.</p>
</li>
</ol>
<p>Demikian sedikit sharing tentang laptop lemot. Semoga bermanfaat bagi kita semua.</p>
Apache Maven2015-02-24T05:28:00+07:00https://software.endy.muhardin.com/java/apache-maven<p>Membuat kandang ayam berbeda dengan membuat rumah. Demikian pula membuat rumah berbeda dengan membangun mall. Jumlah pekerja yang terlibat jauh berbeda. Keragaman bahan baku dan peralatannya tidak sama. Kita cuma membutuhkan beberapa papan kayu, paku, dan palu dalam membuat kandang ayam. Tapi tentunya kita tidak bisa menggunakan bahan, alat, dan cara kerja yang sama dalam pembangunan gedung.</p>
<p>Hal yang sama berlaku di pembuatan software. Untuk tugas kuliah, cukup install Netbeans atau Eclipse, kita bisa kerjakan aplikasi sampai selesai. Akan tetapi, untuk membangun aplikasi besar, dibutuhkan persenjataan yang lebih lengkap, diantaranya:</p>
<ul>
<li>version control</li>
<li>build tools</li>
<li>automated testing</li>
<li>continuous integration</li>
<li>issue tracker</li>
<li>dan lain sebagainya</li>
</ul>
<p>Dalam artikel ini, kita akan membahas tentang apa itu build tools, mengapa kita gunakan, dan bagaimana cara menggunakannya.</p>
<!--more-->
<p>Pada dasarnya, build tools adalah aplikasi untuk melakukan proses <em>build</em>. Salah satu langkah dalam proses <em>build</em> yang paling kita kenal adalah kompilasi source code. Akan tetapi, bahasa pemrograman jaman sekarang tidak cukup hanya dicompile. Kita membutuhkan fitur lain, diantaranya:</p>
<ul>
<li>Dependency Management : menyediakan library, framework, tools yang dibuat orang lain</li>
<li>Compile : mengugbah source code menjadi executable</li>
<li>Test : menjaankan test secara otomatis</li>
<li>Run : menjalankan aplikasi</li>
<li>Package : membuat paket instalasi aplikasi</li>
</ul>
<p>Konsep build tools ini tidak hanya ada di dunia Java saja. Berikut beberapa aplikasi build tools di berbagai bahasa pemrograman</p>
<ul>
<li>
<p>Java</p>
<ul>
<li>Ant + Ivy</li>
<li>Maven</li>
<li>Gradle</li>
</ul>
</li>
<li>
<p>Ruby</p>
<ul>
<li>Rake</li>
</ul>
</li>
<li>
<p>JavaScript</p>
<ul>
<li>Grunt</li>
</ul>
</li>
</ul>
<h2 id="mengapa-menggunakan-maven">Mengapa menggunakan Maven</h2>
<p>Bila kita bekerja dalam tim, ada banyak hal yang harus kita seragamkan supaya masing-masing orang bisa bekerja dengan baik. Diantaranya adalah:</p>
<ul>
<li>struktur folder : Di mana meletakkan source code Java, HTML, CSS, JavaScript. Di mana meletakkan image icon dan logo.</li>
<li>penggunaan library : Di mana jar disimpan, bagaimana cara mengenalinya dari kode program kita, bagaimana bila upgrade versi</li>
<li>workflow : bagaimana menjalankan tes, bagaimana menginisialisasi database</li>
</ul>
<p>Semua keseragaman di atas harus kita tentukan dan berlakukan di semua project. Bila semua project seragam, maka programmer tidak akan kesulitan pada saat ditugaskan di project manapun, karena bentuk dan aturannya sama. Demikian juga bila sebagai programmer, kita berpindah kantor. Selama kantor baru juga menggunakan aturan yang sama, kita akan bisa cepat beradaptasi.</p>
<p>Dengan Maven, kita tidak perlu lagi membuat aturan kita sendiri. Kita cukup mempelajari dan mengikuti aturan yang sudah dia tetapkan. Selama kita mengikuti aturan Maven, apapun jenis aplikasi yang kita buat (desktop, web, mobile), strukturnya sama.</p>
<p>Maven merupakan tools yang populer dan banyak penggunanya. Dengan demikian, dia didukung oleh semua editor yang beredar di pasaran seperti Netbeans, Eclipse, IDEA, dan lainnya. Didukung di sini artinya mereka bisa mengenali struktur folder dan aturan-aturan dalam Maven.</p>
<p>Ada beberapa aturan dalam Maven yang perlu kita ketahui:</p>
<ul>
<li>konfigurasi project</li>
<li>struktur folder</li>
<li>cara menjalankan</li>
</ul>
<h2 id="konfigurasi-project">Konfigurasi Project</h2>
<p>Konfigurasi project dalam Maven ditulis dalam file <code class="language-plaintext highlighter-rouge">pom.xml</code>.</p>
<p><a href="https://lh3.googleusercontent.com/-JRfxohp8O6Y/Uxk_HhHV4sI/AAAAAAAAFZQ/EyqbJHiOaxE/w908-h466-no/02-isi-pom-xml.png"><img src="https://lh3.googleusercontent.com/-JRfxohp8O6Y/Uxk_HhHV4sI/AAAAAAAAFZQ/EyqbJHiOaxE/w908-h466-no/02-isi-pom-xml.png" alt="Isi pom.xml" /></a></p>
<p>Isi <code class="language-plaintext highlighter-rouge">pom.xml</code>:</p>
<ul>
<li>
<p>Identifier project</p>
<ul>
<li>groupId : nama organisasi / perusahaan pembuat</li>
<li>artifactId : nama modul / project</li>
<li>version : versi project</li>
</ul>
</li>
<li>
<p>Contoh identifier library mysql</p>
<ul>
<li>groupId : mysql</li>
<li>artifactId : mysql-connector-java</li>
<li>version : 5.1.25</li>
</ul>
</li>
<li>
<p>Dependensi project. Pilihan scope dependency:</p>
<ul>
<li>compile : digunakan (diimport) dalam main source dan akan di-include dalam produk akhir. Ini adalah pilihan default, kalau scope dikosongkan, artinya compile</li>
<li>runtime : tidak digunakan dalam source code (tidak diimport), tapi disertakan dalam produk akhir. Contoh: library MySQL.</li>
<li>test : digunakan (diimport) dalam test source, tapi tidak digunakan di main source. Tidak diinclude dalam produk akhir. Contoh: library JUnit.</li>
<li>provided : digunakan (diimport) dalam main source, tapi tidak disertakan dalam produk akhir. Biasanya karena sudah disediakan oleh container tempat aplikasi dijalankan. Contoh: library <code class="language-plaintext highlighter-rouge">javax.servlet</code>.</li>
</ul>
</li>
</ul>
<p>Untuk memulai, kita bisa copy paste <code class="language-plaintext highlighter-rouge">pom.xml</code> minimalis berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">></span>
<span class="nt"><modelVersion></span>4.0.0<span class="nt"></modelVersion></span>
<span class="nt"><groupId></span>com.muhardin.endy.belajar<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>belajar-maven<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.0-SNAPSHOT<span class="nt"></version></span>
<span class="nt"><packaging></span>jar<span class="nt"></packaging></span>
<span class="nt"><properties></span>
<span class="nt"><maven.compiler.source></span>1.8<span class="nt"></maven.compiler.source></span>
<span class="nt"><maven.compiler.target></span>1.8<span class="nt"></maven.compiler.target></span>
<span class="nt"></properties></span>
<span class="nt"></project></span>
</code></pre></div></div>
<p>Jangan lupa mengganti:</p>
<ul>
<li>groupId</li>
<li>artifactId</li>
</ul>
<p>sesuai dengan project kita.</p>
<p>Untuk Java 9 ke atas, maka setting <code class="language-plaintext highlighter-rouge">maven.compiler.source</code> dan <code class="language-plaintext highlighter-rouge">maven.compiler.target</code> seperti ini</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">></span>
<span class="nt"><modelVersion></span>4.0.0<span class="nt"></modelVersion></span>
<span class="nt"><groupId></span>com.muhardin.endy.belajar<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>belajar-maven<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.0-SNAPSHOT<span class="nt"></version></span>
<span class="nt"><packaging></span>jar<span class="nt"></packaging></span>
<span class="nt"><properties></span>
<span class="nt"><maven.compiler.source></span>11<span class="nt"></maven.compiler.source></span>
<span class="nt"><maven.compiler.target></span>11<span class="nt"></maven.compiler.target></span>
<span class="nt"></properties></span>
<span class="nt"></project></span>
</code></pre></div></div>
<h2 id="struktur-folder-project-maven">Struktur Folder Project Maven</h2>
<p><a href="https://lh6.googleusercontent.com/-wsPV4myilDQ/Uxk77D4MtcI/AAAAAAAAFYg/dQlTHnnx_FQ/w243-h354-no/01-struktur-folder-maven.png"><img src="https://lh6.googleusercontent.com/-wsPV4myilDQ/Uxk77D4MtcI/AAAAAAAAFYg/dQlTHnnx_FQ/w243-h354-no/01-struktur-folder-maven.png" alt="Struktur Folder Maven" /></a></p>
<ul>
<li><code class="language-plaintext highlighter-rouge">src/main/java</code> : Tempat meletakkan source code java</li>
<li><code class="language-plaintext highlighter-rouge">src/main/resources</code> : Tempat meletakkan file konfigurasi, icon image, dan lain-lain</li>
<li><code class="language-plaintext highlighter-rouge">src/main/webapp</code> : Khusus aplikasi web, untuk meletakkan file html, img, js, css, dsb</li>
<li><code class="language-plaintext highlighter-rouge">target</code> : file dan folder hasil compile. Folder target ini <strong>jangan</strong> di-commit ke Git repo.</li>
</ul>
<h2 id="menggunakan-maven">Menggunakan Maven</h2>
<h3 id="instalasi">Instalasi</h3>
<ul>
<li>Unduh versi terbaru di <a href="http://maven.apache.org">http://maven.apache.org</a></li>
<li>Extract. Untuk Linux biasanya saya taruh di <code class="language-plaintext highlighter-rouge">/opt</code>. Untuk Windows, saya letakkan di <code class="language-plaintext highlighter-rouge">Program Files</code></li>
<li>Set <code class="language-plaintext highlighter-rouge">M2_HOME</code> dan <code class="language-plaintext highlighter-rouge">PATH</code>, caranya bisa dibaca <a href="http://software.endy.muhardin.com/java/persiapan-coding-java/">di sini</a></li>
</ul>
<h3 id="cara-pakai-maven">Cara Pakai Maven</h3>
<ul>
<li>Membuat project baru</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn archetype:generate -DgroupId=belajar -DartifactId=belajar-maven -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
</code></pre></div></div>
<ul>
<li>Menambahkan dependensi. Tambahkan blok dependensi di <code class="language-plaintext highlighter-rouge">pom.xml</code> dalam tag <code class="language-plaintext highlighter-rouge"><dependencies></code></li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>junit<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>junit<span class="nt"></artifactId></span>
<span class="nt"><version></span>4.9<span class="nt"></version></span>
<span class="nt"><scope></span>test<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<ul>
<li>
<p>Dependensi akan diunduh dari internet (repo.maven.org) dan diletakkan di folder <code class="language-plaintext highlighter-rouge">.m2</code> dalam home user.</p>
</li>
<li>
<p>Compile. Jalankan <code class="language-plaintext highlighter-rouge">mvn clean install</code></p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building belajar-maven 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ belajar-maven ---
[INFO] Deleting /home/endy/tmp/belajar-maven/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ belajar-maven ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/endy/tmp/belajar-maven/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ belajar-maven ---
[INFO] Compiling 1 source file to /home/endy/tmp/belajar-maven/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ belajar-maven ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/endy/tmp/belajar-maven/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ belajar-maven ---
[INFO] Compiling 1 source file to /home/endy/tmp/belajar-maven/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ belajar-maven ---
[INFO] Surefire report directory: /home/endy/tmp/belajar-maven/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running belajar.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.037 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ belajar-maven ---
[INFO] Building jar: /home/endy/tmp/belajar-maven/target/belajar-maven-1.0.0.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ belajar-maven ---
[INFO] Installing /home/endy/tmp/belajar-maven/target/belajar-maven-1.0.0.jar to /home/endy/.m2/repository/belajar/belajar-maven/1.0.0/belajar-maven-1.0.0.jar
[INFO] Installing /home/endy/tmp/belajar-maven/pom.xml to /home/endy/.m2/repository/belajar/belajar-maven/1.0.0/belajar-maven-1.0.0.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.501s
[INFO] Finished at: Fri Mar 07 10:40:38 WIB 2014
[INFO] Final Memory: 14M/150M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<ul>
<li>Run. Jalankan <code class="language-plaintext highlighter-rouge">mvn exec:java -Dexec.mainClass=belajar.App</code></li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building belajar-maven 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) @ belajar-maven >>>
[INFO]
[INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) @ belajar-maven <<<
[INFO]
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ belajar-maven ---
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.917s
[INFO] Finished at: Fri Mar 07 10:39:50 WIB 2014
[INFO] Final Memory: 8M/150M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<h2 id="konfigurasi-tambahan">Konfigurasi Tambahan</h2>
<p>Adakalanya kita membutuhkan setting tambahan untuk menyesuaikan penggunaan Maven, misalnya bila kita ada di belakang proxy ataupun ingin memindahkan lokasi download ke tempat lain. Untuk melakukan konfigurasi, buat file <code class="language-plaintext highlighter-rouge">settings.xml</code> di dalam folder <code class="language-plaintext highlighter-rouge">.m2</code>.</p>
<h3 id="proxy">Proxy</h3>
<p>Berikut isi filenya bila kita menjalankan Maven di belakang proxy.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><settings></span>
<span class="nt"><proxies></span>
<span class="nt"><proxy></span>
<span class="nt"><protocol></span>http<span class="nt"></protocol></span>
<span class="nt"><host></span>proxy.host.net<span class="nt"></host></span>
<span class="nt"><port></span>80<span class="nt"></port></span>
<span class="nt"><nonProxyHosts></span>local.net|some.host.com<span class="nt"></nonProxyHosts></span>
<span class="nt"><username></span>proxyuser<span class="nt"></username></span>
<span class="nt"><password></span>proxypass<span class="nt"></password></span>
<span class="nt"></proxy></span>
<span class="nt"></proxies></span>
<span class="nt"></settings></span>
</code></pre></div></div>
<h3 id="lokasi-download-folder">Lokasi Download Folder</h3>
<p>Setting ini saya gunakan karena komputer saya menggunakan harddisk SSD yang berkapasitas kecil, sehingga lokasi downloadnya perlu dipindah ke external harddisk.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><settings></span>
<span class="nt"><localRepository></span>/Volumes/SDUF128G/m2/repository<span class="nt"></localRepository></span>
<span class="nt"></settings></span>
</code></pre></div></div>
<h2 id="penutup">Penutup</h2>
<p>Demikianlah sekilas penggunaan Maven. Untuk lebih lengkapnya, kita bisa baca <a href="http://books.sonatype.com/mvnref-book/reference/">buku referensi Maven</a> yang sudah disediakan gratis oleh Sonatype. Anda juga bisa tonton video Youtube saya yang membahas lebih lanjut tentang Apache Maven ini</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/_BahIP7XYfk" frameborder="0" allowfullscreen=""></iframe>
Membuat Blog Gratis di Openshift2015-02-13T22:28:00+07:00https://software.endy.muhardin.com/aplikasi/membuat-blog-gratis-di-openshift<blockquote>
<p>Pengen cepat jadi expert?</p>
</blockquote>
<p>Caranya sederhana. Cukup bikin blog, kemudian konsisten menulis tentang topik tertentu paling tidak seminggu sekali.</p>
<blockquote>
<p>Waduh, menulis itu sulit. Apalagi konsisten.</p>
</blockquote>
<p>Makanya saya bilang sederhana, bukan mudah :)</p>
<p>Sebetulnya membuat blog jaman sekarang sudah semakin mudah dan murah. Tanpa keluar uang sepeserpun kita sudah bisa punya blog bagus. Desain tampilan juga banyak tersedia free. Bahkan desain gratis tersebut sudah <em>responsive</em>, artinya tetap bagus dilihat dari berbagai ukuran layar.</p>
<p>Setelah blog kita jadi, tempat menaruhnya pun gratis. Beberapa yang populer diantaranya: <a href="http://www.wordpress.com/">wordpress</a>, <a href="https://www.tumblr.com/">tumblr</a>, dan lain sebagainya.</p>
<p>Buat kita para programmer, tersedia juga aplikasi blog yang geeky, diantaranya <a href="http://jekyllrb.com/">jekyll</a>, <a href="http://octopress.org/">octopress</a> (dibangun di atas jekyll), <a href="https://middlemanapp.com/basics/blogging/">middleman</a>, <a href="https://ghost.org/">ghost</a>, <a href="https://iwantmyname.com/blog/2011/02/list-static-website-generators.html">dan teman-temannya</a>.</p>
<p>Berbagai aplikasi yang saya sebutkan di atas memiliki kesamaan, semuanya adalah <a href="https://wiki.python.org/moin/StaticSiteGenerator">static content generator</a>. Artinya, dia mengkonversi tulisan kita menjadi HTML. File HTML tersebut tinggal kita upload ke webserver merek apapun. Dia juga tidak butuh database server. Ini akan sangat menyederhanakan kebutuhan di sisi server.</p>
<p>Pada artikel ini, kita akan membahas cara membuat blog menggunakan Jekyll dan kemudian memasangnya di PaaS provider Openshift.</p>
<!--more-->
<p>Sebetulnya ada beberapa provider yang mendukung Jekyll, selain Openshift kita juga bisa pakai Heroku atau Github. Cara deploy ke kedua provider tersebut sudah dijelaskan di dokumentasi Jekyll, sehingga tidak perlu kita bahas lagi.</p>
<p><a href="https://lh5.googleusercontent.com/-0v-aweJAqUQ/VNSNiRPuxbI/AAAAAAAAH38/gzzHemMzJEY/w916-h558-no/07.%2BJekyll%2BHomepage.png"><img src="https://lh5.googleusercontent.com/-0v-aweJAqUQ/VNSNiRPuxbI/AAAAAAAAH38/gzzHemMzJEY/w916-h558-no/07.%2BJekyll%2BHomepage.png" alt="Dokumentasi Deployment" /></a></p>
<p>Secara garis besar, berikut langkah-langkah yang akan kita kerjakan:</p>
<ol>
<li>Instalasi Jekyll</li>
<li>Mencari theme yang cocok</li>
<li>Mulai menulis artikel</li>
<li>Compile dan Preview di komputer kita</li>
<li>Buat akun Openshift</li>
<li>Buat aplikasi untuk blog kita</li>
<li>Deploy ke Openshift</li>
<li>Periksa hasilnya</li>
</ol>
<h2 id="instalasi-jekyll">Instalasi Jekyll</h2>
<p>Jekyll merupakan aplikasi yang dibuat dengan Ruby. Dengan demikian, kita perlu menginstall Ruby terlebih dulu.</p>
<h3 id="instalasi-ruby">Instalasi Ruby</h3>
<p>Tambahkan repository PPA brightbox.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-add-repository ppa:brightbox/ruby-ng
</code></pre></div></div>
<p>Update database aplikasi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get update
</code></pre></div></div>
<p>Install Ruby 1.9</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install ruby1.9.3
</code></pre></div></div>
<p>Setelah itu, kita bisa langsung menginstall jekyll</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo gem install jekyll
</code></pre></div></div>
<h2 id="jekyll-theme">Jekyll Theme</h2>
<p>Sebetulnya pada titik ini kita sudah bisa langsung membuat website dengan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll new blogsaya
</code></pre></div></div>
<p>Tetapi, kita tentu ingin tampilan yang menarik. Untuk itu, kita cari dulu theme yang bagus untuk Jekyll.</p>
<p><a href="https://lh4.googleusercontent.com/--lDlOyCn2Tw/VNSNva-nFTI/AAAAAAAAH4c/uNvFEtLvnBA/w791-h582-no/10.%2BCari%2BTheme.png"><img src="https://lh4.googleusercontent.com/--lDlOyCn2Tw/VNSNva-nFTI/AAAAAAAAH4c/uNvFEtLvnBA/w791-h582-no/10.%2BCari%2BTheme.png" alt="Mencari theme Jekyll" /></a></p>
<p>Ada banyak hasil di sana. Coba satu persatu sampai ketemu yang cocok.</p>
<p>Baca instruksinya, dia bilang langsung saja extract.</p>
<p><a href="https://lh4.googleusercontent.com/-VMnzqkZ07dA/VNSNvmnMcXI/AAAAAAAAH4Y/TQ9en2RctLI/w855-h578-no/11.%2BDownload%2Bdan%2BExtract.png"><img src="https://lh4.googleusercontent.com/-VMnzqkZ07dA/VNSNvmnMcXI/AAAAAAAAH4Y/TQ9en2RctLI/w855-h578-no/11.%2BDownload%2Bdan%2BExtract.png" alt="Extract Theme" /></a></p>
<p>Berikut isi folder setelah diextract.</p>
<p><a href="https://lh3.googleusercontent.com/-dt0_MXF-F_E/VNSN8_MmXLI/AAAAAAAAH4k/3VLoa6em0mY/w855-h578-no/12.%2BHasil%2BExtract.png"><img src="https://lh3.googleusercontent.com/-dt0_MXF-F_E/VNSN8_MmXLI/AAAAAAAAH4k/3VLoa6em0mY/w855-h578-no/12.%2BHasil%2BExtract.png" alt="Hasil Extract" /></a></p>
<p>Coba kita jalankan dengan perintah <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code></p>
<p><a href="https://lh5.googleusercontent.com/-w7tQ96eZQ8o/VNSOBSAWlDI/AAAAAAAAH4s/QDyxCTb-lKU/w728-h494-no/13.%2BJalankan%2Bdi%2Blocal.png"><img src="https://lh5.googleusercontent.com/-w7tQ96eZQ8o/VNSOBSAWlDI/AAAAAAAAH4s/QDyxCTb-lKU/w728-h494-no/13.%2BJalankan%2Bdi%2Blocal.png" alt="Run Jekyll" /></a></p>
<p>Kemudian browse ke <code class="language-plaintext highlighter-rouge">http://localhost:4000</code>.</p>
<p><a href="https://lh5.googleusercontent.com/-4E-myZULncY/VNSOJDurMBI/AAAAAAAAH40/jiwixObwMnw/w917-h566-no/14.%2BBrowse%2BLocal.png"><img src="https://lh5.googleusercontent.com/-4E-myZULncY/VNSOJDurMBI/AAAAAAAAH40/jiwixObwMnw/w917-h566-no/14.%2BBrowse%2BLocal.png" alt="Local Deployment" /></a></p>
<p>Nah, website kita sudah jalan di komputer kita sendiri. Sekarang, tinggal kita pasang di Openshift.</p>
<h2 id="pendaftaran-openshift">Pendaftaran Openshift</h2>
<p>Kita perlu membuat akun di Openshift terlebih dulu. Caranya mudah, langsung saja isi form yang disediakan di websitenya.</p>
<p><a href="https://lh3.googleusercontent.com/-RAP1-lNzkkk/VNSNcEHC_oI/AAAAAAAAH3Y/9hRUK_dB3CM/w916-h528-no/01.%2BOpenshift%2BWebsite.png"><img src="https://lh3.googleusercontent.com/-RAP1-lNzkkk/VNSNcEHC_oI/AAAAAAAAH3Y/9hRUK_dB3CM/w916-h528-no/01.%2BOpenshift%2BWebsite.png" alt="Website Openshift" /></a></p>
<h2 id="membuat-aplikasi">Membuat Aplikasi</h2>
<p>Setelah membuat akun dan login, kita bisa langsung klik link untuk membuat aplikasi baru.</p>
<p><a href="https://lh4.googleusercontent.com/-osxSBeceqjc/VNSNbx9oc1I/AAAAAAAAH3U/vTV27OtGsLA/w916-h522-no/02.%2BOpenshift%2BWelcome.png"><img src="https://lh4.googleusercontent.com/-osxSBeceqjc/VNSNbx9oc1I/AAAAAAAAH3U/vTV27OtGsLA/w916-h522-no/02.%2BOpenshift%2BWelcome.png" alt="Welcome Screen" /></a></p>
<p>Untuk aplikasi Jekyll, Openshift telah menyediakan starter pack khusus (cartridge dalam istilah Openshift). Langsung saja search</p>
<p><a href="https://lh4.googleusercontent.com/-W_H0vkwfxS8/VNSNcNWgLLI/AAAAAAAAH3c/WR0cj05L0QQ/w876-h623-no/03.%2BJenis%2BAplikasi.png"><img src="https://lh4.googleusercontent.com/-W_H0vkwfxS8/VNSNcNWgLLI/AAAAAAAAH3c/WR0cj05L0QQ/w876-h623-no/03.%2BJenis%2BAplikasi.png" alt="Search Cartridge" /></a></p>
<p>Kemudian klik</p>
<p><a href="https://lh4.googleusercontent.com/-sHIp8sVVCpY/VNSNfEgoYvI/AAAAAAAAH3s/57pQUsECd5A/w898-h623-no/04.%2BJekyll%2BCartridge.png"><img src="https://lh4.googleusercontent.com/-sHIp8sVVCpY/VNSNfEgoYvI/AAAAAAAAH3s/57pQUsECd5A/w898-h623-no/04.%2BJekyll%2BCartridge.png" alt="Jekyll Cartridge" /></a></p>
<p>Isikan nama aplikasi</p>
<p><a href="https://lh4.googleusercontent.com/-Gxc-Bkk9USk/VNSNf1PmZCI/AAAAAAAAH3w/uBq-Nux9ceY/w857-h623-no/05.%2BCukup%2Bisi%2Bnama%2Baplikasi.png"><img src="https://lh4.googleusercontent.com/-Gxc-Bkk9USk/VNSNf1PmZCI/AAAAAAAAH3w/uBq-Nux9ceY/w857-h623-no/05.%2BCukup%2Bisi%2Bnama%2Baplikasi.png" alt="Nama Aplikasi" /></a></p>
<p>Klik OK. Aplikasi sudah dibuat. Kita akan diberikan URL untuk melakukan <code class="language-plaintext highlighter-rouge">git clone</code></p>
<p><a href="https://lh3.googleusercontent.com/-siiXJ3o4rwo/VNSNgEMQjwI/AAAAAAAAH30/E9VrPkeHglU/w868-h623-no/06.%2BAplikasi%2Bselesai%2Bdibuat.png"><img src="https://lh3.googleusercontent.com/-siiXJ3o4rwo/VNSNgEMQjwI/AAAAAAAAH30/E9VrPkeHglU/w868-h623-no/06.%2BAplikasi%2Bselesai%2Bdibuat.png" alt="Sukses" /></a></p>
<p>Sebetulnya ini juga bisa kita lakukan melalui command prompt.</p>
<p>Install dulu aplikasi Openshift di komputer. Aplikasinya dibuat menggunakan Ruby, sehingga kita gunakan <code class="language-plaintext highlighter-rouge">gem</code> untuk menginstalnya</p>
<p>Setelah terinstall, kita login dulu</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rhc setup
</code></pre></div></div>
<p>dia akan meminta username dan password Openshift kita. Kemudian menampilkan daftar aplikasi yang sudah kita miliki.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>OpenShift Client Tools (RHC) Setup Wizard
This wizard will help you upload your SSH keys, set your application namespace,
and check that other programs like Git are properly installed.
If you have your own OpenShift server, you can specify it now. Just hit enter to
use the server for OpenShift Online: openshift.redhat.com.
Enter the server hostname: |openshift.redhat.com|
You can add more servers later using 'rhc server'.
Using an existing token for endy.muhardin@gmail.com to login to
openshift.redhat.com
Saving configuration to /home/endy/.openshift/express.conf ... done
Checking for git ... found git version 2.2.2
Checking common problems .. done
Checking for a domain ... endymuhardin
Checking for applications ... found 1
blogbisnis http://blogbisnis-endymuhardin.rhcloud.com/
You are using 1 of 3 total gears
The following gear sizes are available to you: small
Your client tools are now configured.
</code></pre></div></div>
<p>Setelah berhasil login, kita buat aplikasi dengan menggunakan cartridge Jekyll</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rhc app create jekyll https://raw.github.com/openshift-cartridges/openshift-jekyll-cartridge/master/metadata/manifest.yml
</code></pre></div></div>
<p>Aplikasi siap diisi</p>
<p>Jangan lupa catat URL aplikasi. Inilah URL yang akan menampilkan blog kita nantinya. Berikut adalah URL aplikasi yang kita dapatkan sesuai contoh di atas</p>
<h2 id="proses-deployment">Proses Deployment</h2>
<p>Deployment Openshift dilakukan menggunakan aplikasi version control Git.</p>
<p>Pertama, kita clone dulu repo aplikasi yang sudah dibuatkan Openshift.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone ssh://54d478d14382ec586c000058@blogbisnis-endymuhardin.rhcloud.com/~/git/blogbisnis.git blogbisnis
</code></pre></div></div>
<p>Kemudian, pindahkan blog Jekyll kita tadi ke dalam folder hasil clone dari Openshift</p>
<p>Selanjutnya commit seperti biasa</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add -A .
git commit -m "commit pertama website jekyll"
</code></pre></div></div>
<p>Lalu lakukan push untuk mendeploy</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push
</code></pre></div></div>
<p>Berikut output dari perintah di atas</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Counting objects: 155, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (149/149), done.
Writing objects: 100% (155/155), 1.24 MiB | 0 bytes/s, done.
Total 155 (delta 8), reused 0 (delta 0)
remote: Stopping jekyll cart
remote: Sending SIGTERM to jekyll:193432 ...
remote: Building git ref 'master', commit 52fa2f6
remote: Preparing build for deployment
remote: Deployment id is 45c5465a
remote: Activating deployment
remote: Starting jekyll cart
remote: Executing bundle install
remote: Fetching gem metadata from https://rubygems.org/.........
remote: Resolving dependencies...
remote: Using blankslate (2.1.2.4)
remote: Using hitimes (1.2.2)
remote: Using timers (4.0.1)
remote: Using celluloid (0.16.0)
remote: Using fast-stemmer (1.0.2)
remote: Using classifier-reborn (2.0.3)
remote: Using coffee-script-source (1.9.0)
remote: Using execjs (2.3.0)
remote: Using coffee-script (2.3.0)
remote: Using colorator (0.1)
remote: Using ffi (1.9.6)
remote: Using jekyll-coffeescript (1.0.1)
remote: Using jekyll-gist (1.1.0)
remote: Using jekyll-paginate (1.1.0)
remote: Using sass (3.4.11)
remote: Using jekyll-sass-converter (1.3.0)
remote: Using rb-fsevent (0.9.4)
remote: Using rb-inotify (0.9.5)
remote: Using listen (2.8.5)
remote: Using jekyll-watch (1.2.1)
remote: Using kramdown (1.5.0)
remote: Using liquid (2.6.2)
remote: Using mercenary (0.3.5)
remote: Using posix-spawn (0.3.9)
remote: Using yajl-ruby (1.2.1)
remote: Using pygments.rb (0.6.2)
remote: Using redcarpet (3.2.2)
remote: Using safe_yaml (1.0.4)
remote: Using parslet (1.5.0)
remote: Using toml (0.1.2)
remote: Using jekyll (2.5.3)
remote: Installing jekyll-sitemap (0.8.0)
remote: Installing octopress-deploy (1.0.5)
remote: Installing octopress-hooks (2.4.1)
remote: Installing octopress-escape-code (2.0.6)
remote: Installing titlecase (0.1.1)
remote: Installing octopress (3.0.0.rc.32)
remote: Using bundler (1.3.5)
remote: Your bundle is complete!
remote: Use `bundle show [gemname]` to see where a bundled gem is installed.
remote: Starting Jekyll server
remote: Found 127.6.249.129:8080 listening port
remote: -------------------------
remote: Git Post-Receive Result: success
remote: Activation status: success
remote: Deployment completed with status: success
To ssh://54d478d14382ec586c000058@blogbisnis-endymuhardin.rhcloud.com/~/git/blogbisnis.git
722b0dc..52fa2f6 master -> master
</code></pre></div></div>
<p>Coba browse ke url aplikasi kita di Openshift, yaitu <code class="language-plaintext highlighter-rouge">http://blogbisnis-endymuhardin.rhcloud.com</code></p>
<p><a href="https://lh6.googleusercontent.com/-iY15Z743RWw/VNSORWJhavI/AAAAAAAAH5E/YgL7ibpTUOc/w874-h623-no/15.%2BDeploy%2Bke%2BOpenshift.png"><img src="https://lh6.googleusercontent.com/-iY15Z743RWw/VNSORWJhavI/AAAAAAAAH5E/YgL7ibpTUOc/w874-h623-no/15.%2BDeploy%2Bke%2BOpenshift.png" alt="Deploy di Openshift" /></a></p>
<p>Voila, website kita sudah selesai.</p>
<h2 id="custom-domain">Custom Domain</h2>
<p>Pada titik ini sebetulnya website kita sudah berfungsi dengan sempurna. Walaupun demikian, untuk keperluan personal branding ini belum maksimal, karena namanya masih mengandung nama Redhat dan berbau gratisan. Dengan menambah investasi USD10 per tahun, kita bisa membeli domain atas nama kita sendiri, misalnya <code class="language-plaintext highlighter-rouge">endy.muhardin.com</code> untuk kemudian kita arahkan ke website kita tadi.</p>
<p>Proses pembelian domain tidak akan saya ceritakan di sini karena amat sangat terlalu mudahnya. Asumsikan saja kita sudah punya domain dan DNS server untuk mengaturnya.</p>
<p>Cara untuk mengaktifkan custom domain di Openshift bisa dicari dengan mudah di Google</p>
<p><a href="https://lh6.googleusercontent.com/-eWzSXRQo5wI/VNSOSNew31I/AAAAAAAAH5M/1MqLL4CsW3w/w744-h623-no/16.%2BCari%2Bcara%2Buntuk%2Bcustom%2Bdomain.png"><img src="https://lh6.googleusercontent.com/-eWzSXRQo5wI/VNSOSNew31I/AAAAAAAAH5M/1MqLL4CsW3w/w744-h623-no/16.%2BCari%2Bcara%2Buntuk%2Bcustom%2Bdomain.png" alt="Search Custom Domain Deployment" /></a></p>
<p>Kita akan mendapatkan tutorialnya di website Openshift sendiri</p>
<p><a href="https://lh3.googleusercontent.com/-p8QXyZfZDEA/VNSOSPxhrtI/AAAAAAAAH5I/OT5C0HVx1EY/w891-h552-no/17.%2BCommand%2Balias.png"><img src="https://lh3.googleusercontent.com/-p8QXyZfZDEA/VNSOSPxhrtI/AAAAAAAAH5I/OT5C0HVx1EY/w891-h552-no/17.%2BCommand%2Balias.png" alt="Cara setting custom domain" /></a></p>
<h3 id="konfigurasi-dns">Konfigurasi DNS</h3>
<p>Agar nama domain kita tadi mengarah ke website kita, perlu ditambahkan konfigurasi CNAME di DNS. Berikut ilustrasinya dengan menggunakan layanan ClouDNS yang saya gunakan</p>
<p><a href="https://lh5.googleusercontent.com/-YqGp8TjNQsQ/VNSOUI7qP4I/AAAAAAAAH5g/n8U3y1YHxBQ/w917-h376-no/18.%2BDNS%2BConfig.png"><img src="https://lh5.googleusercontent.com/-YqGp8TjNQsQ/VNSOUI7qP4I/AAAAAAAAH5g/n8U3y1YHxBQ/w917-h376-no/18.%2BDNS%2BConfig.png" alt="Edit CNAME ClouDNS" /></a></p>
<p>Seperti Anda lihat pada screenshot di atas, saya memiliki beberapa domain lain yang mengarah ke github dan heroku. Yaitu</p>
<ul>
<li><a href="http://software.endy.muhardin.com">software.endy.muhardin.com</a> : mengarah ke servernya Github</li>
<li><a href="http://rana.endy.muhardin.com">rana.endy.muhardin.com</a> : mengarah ke servernya Heroku</li>
</ul>
<p>Tambahkan entri berjenis CNAME untuk nama domain <code class="language-plaintext highlighter-rouge">bisnis.endy.muhardin.com</code> dan arahkan ke <code class="language-plaintext highlighter-rouge">blogbisnis-endymuhardin.rhcloud.com</code>.</p>
<p><a href="https://lh3.googleusercontent.com/-uMAGnXHALqA/VN4krEtZs3I/AAAAAAAAH6k/6-eGym0Z0hI/w917-h418-no/19.%2BCNAME%2BEntry.png"><img src="https://lh3.googleusercontent.com/-uMAGnXHALqA/VN4krEtZs3I/AAAAAAAAH6k/6-eGym0Z0hI/w917-h418-no/19.%2BCNAME%2BEntry.png" alt="Konfigurasi CNAME" /></a></p>
<h3 id="virtualhost-openshift">VirtualHost Openshift</h3>
<p>Alamat domain kita sudah mengarah ke server Openshift. Ini bisa dibuktikan kalau kita browse ke <code class="language-plaintext highlighter-rouge">http://bisnis.endy.muhardin.com</code> akan diarahkan ke servernya Openshift.</p>
<p><a href="https://lh4.googleusercontent.com/-S8Ul3djmpWA/VNSOU26jUoI/AAAAAAAAH5k/PmtKZJDR2Zo/w917-h401-no/20.%2BBelum%2Bdibikin%2Balias.png"><img src="https://lh4.googleusercontent.com/-S8Ul3djmpWA/VNSOU26jUoI/AAAAAAAAH5k/PmtKZJDR2Zo/w917-h401-no/20.%2BBelum%2Bdibikin%2Balias.png" alt="Virtual Host Error" /></a></p>
<p>Kita bisa lihat pada screenshot di atas, Openshift sudah menerima request. Tapi muncul pesan error karena dia tidak paham bagaimana menangani request tersebut.</p>
<p>Untuk itu, kita perlu membuat konfigurasi virtual host dulu. Gunanya supaya Openshift bisa mengenali request ke <code class="language-plaintext highlighter-rouge">bisnis.endy.muhardin.com</code></p>
<p>Ini bisa dilakukan melalui web</p>
<p><a href="https://lh6.googleusercontent.com/-BK2dMIan4VM/VN4XscJhKGI/AAAAAAAAH6U/f83fyASiyK8/w663-h623-no/22.%2BCustom%2BDomain%2BWeb%2BBased.png"><img src="https://lh6.googleusercontent.com/-BK2dMIan4VM/VN4XscJhKGI/AAAAAAAAH6U/f83fyASiyK8/w663-h623-no/22.%2BCustom%2BDomain%2BWeb%2BBased.png" alt="Web Based Custom Domain" /></a></p>
<p>Atau melalui command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rhc alias add blogbisnis bisnis.endy.muhardin.com
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Alias 'bisnis.endy.muhardin.com' has been added.
</code></pre></div></div>
<p>Setelah selesai, coba kita browse <code class="language-plaintext highlighter-rouge">http://bisnis.endy.muhardin.com</code></p>
<p><a href="https://lh6.googleusercontent.com/-8dmIdYTjdyc/VNSOdH0KxqI/AAAAAAAAH5s/nMwJOFxyJe4/w827-h623-no/21.%2BSudah%2Bdibikin%2Balias.png"><img src="https://lh6.googleusercontent.com/-8dmIdYTjdyc/VNSOdH0KxqI/AAAAAAAAH5s/nMwJOFxyJe4/w827-h623-no/21.%2BSudah%2Bdibikin%2Balias.png" alt="Custom Domain Success" /></a></p>
<p>Nah selesailah website kita. Tinggal diisi saja</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Membuat blog atau website pribadi itu mudah dan murah. Sebagai praktisi IT, personal website sudah menjadi hal yang wajib. Ini bukan trend sesaat seperti dinyatakan ‘pakar’ pornomatika nasional, tapi adalah saluran kita untuk berbagi pengetahuan buat orang lain.</p>
<p>Yuk, mari menulis di blog.</p>
Scope Variabel2014-12-16T15:40:00+07:00https://software.endy.muhardin.com/java/scope-variabel<p>Masih dari <a href="https://www.facebook.com/groups/ForumJavaIndonesia/10152533869543017">pertanyaan di grup Facebook</a>, berikut kode programnya (saya edit sedikit supaya sesuai)</p>
<h2 id="alternatif-pertama">Alternatif Pertama</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nf">CobaPertama</span> <span class="o">(){</span>
<span class="nc">String</span> <span class="n">a</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">coba1</span><span class="o">(){</span>
<span class="n">a</span> <span class="o">=</span> <span class="s">"ari"</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">coba2</span><span class="o">(){</span>
<span class="n">a</span> <span class="o">=</span> <span class="s">"ira"</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="alternatif-kedua">Alternatif Kedua</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nf">CobaKedua</span> <span class="o">(){</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">coba1</span><span class="o">(){</span>
<span class="nc">String</span> <span class="n">a</span> <span class="o">=</span> <span class="s">"ari"</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">coba2</span><span class="o">(){</span>
<span class="nc">String</span> <span class="n">a</span> <span class="o">=</span> <span class="s">"ira"</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pertanyaannya sebagai berikut</p>
<blockquote>
<p>lebih cepatan mana y masta performance aplikasi yg entar kt buat</p>
</blockquote>
<!--more-->
<p>Seperti pada <a href="http://software.endy.muhardin.com/java/mengapa-kita-membuat-class/">posting saya sebelumnya</a>, urusan performance itu belakangan, setelah <em>correctness</em>, <em>readability</em>, dan <em>maintainability</em>.</p>
<p>Kode program pada alternatif pertama salah, seharusnya deklarasi dilakukan dalam scope sekecil mungkin. Apa maksudnya?</p>
<p>Variabel adalah tempat penyimpanan data. Berikut statement yang biasa kita gunakan untuk membuat variabel.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">a</span> <span class="o">=</span> <span class="s">"ari"</span><span class="o">;</span>
</code></pre></div></div>
<p>Statement di atas akan:</p>
<ol>
<li>Booking tempat di memori untuk data bertipe String</li>
<li>Mengisi tempat tersebut dengan data bernilai <code class="language-plaintext highlighter-rouge">ari</code></li>
</ol>
<p>Dalam bahasa teknis, statement di atas terdiri dari dua hal:</p>
<ol>
<li>Deklarasi</li>
<li>Inisialisasi</li>
</ol>
<p>Sehingga statement di atas bisa dipecah menjadi seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">a</span><span class="o">;</span>
</code></pre></div></div>
<p>dan</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">a</span> <span class="o">=</span> <span class="s">"ari"</span><span class="o">;</span>
</code></pre></div></div>
<p>Deklarasi variabel menentukan umur(lifetime) dan visibility(scope) variabel.</p>
<h3 id="umur-variabel">Umur Variabel</h3>
<p>Di bahasa pemrograman modern, penggunaan memori sudah dikelola secara otomatis oleh virtual machine. Ini berlaku untuk bahasa yang jalan di VM seperti Java, PHP, Ruby, Python, C#, VB.net. Ini <strong>tidak</strong> berlaku di bahasa C/C++ di mana kita harus mengelola sendiri penggunaan memori. Bahasa pemrograman yang jalan di atas VM disebut dengan istilah <a href="http://en.wikipedia.org/wiki/Managed_code"><em>managed code</em> atau <em>managed_language</em></a>.</p>
<p>Di <em>managed language</em>, variabel yang sudah tidak terpakai akan dibersihkan dari memori, supaya tempatnya bisa dipakai oleh variabel lainnya. Proses bersih-bersih ini disebut dengan istilah <a href="http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29"><em>Garbage Collection</em></a>. Salah satu pedoman yang digunakan oleh garbage collector untuk membersihkan variabel adalah umur variabel tersebut.</p>
<p>Sebagai ilustrasi tentang umur variabel, lihat kembali ke kode program di atas. Kita akan membuat kode program yang menggunakan class <code class="language-plaintext highlighter-rouge">CobaPertama</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HaloSatu</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">x</span><span class="o">){</span>
<span class="nc">CobaPertama</span> <span class="n">cp</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CobaPertama</span><span class="o">();</span>
<span class="n">cp</span><span class="o">.</span><span class="na">coba1</span><span class="o">();</span>
<span class="c1">// 1000 baris melakukan hal yang lain</span>
<span class="n">cp</span><span class="o">.</span><span class="na">coba2</span><span class="o">();</span>
<span class="c1">// 2000 baris melakukan hal lain</span>
<span class="nc">System</span><span class="o">.</span><span class="na">exit</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="c1">// selesai</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada kode program di atas, VM akan menyediakan tempat untuk variabel <code class="language-plaintext highlighter-rouge">a</code> pada baris ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CobaPertama</span> <span class="n">cp</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CobaPertama</span><span class="o">();</span>
</code></pre></div></div>
<p>karena variabel <code class="language-plaintext highlighter-rouge">a</code> dideklarasikan di dalam class.</p>
<p>Variabel <code class="language-plaintext highlighter-rouge">a</code> akan terus hidup selama object <code class="language-plaintext highlighter-rouge">cp</code> hidup. Sehingga tempat yang digunakan variabel <code class="language-plaintext highlighter-rouge">a</code> tidak bisa digunakan oleh variabel lain, sampai <code class="language-plaintext highlighter-rouge">System.exit(0);</code> dijalankan.</p>
<p>Ini tentu tidak efisien, karena sebetulnya variabel <code class="language-plaintext highlighter-rouge">a</code> hanya digunakan di baris</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cp</span><span class="o">.</span><span class="na">coba1</span><span class="o">();</span>
</code></pre></div></div>
<p>dan</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cp</span><span class="o">.</span><span class="na">coba2</span><span class="o">();</span>
</code></pre></div></div>
<p>Bandingkan dengan kode program berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HaloDua</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">x</span><span class="o">){</span>
<span class="nc">CobaKedua</span> <span class="n">ck</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CobaKedua</span><span class="o">();</span>
<span class="n">ck</span><span class="o">.</span><span class="na">coba1</span><span class="o">();</span>
<span class="c1">// 1000 baris melakukan hal yang lain</span>
<span class="n">ck</span><span class="o">.</span><span class="na">coba2</span><span class="o">();</span>
<span class="c1">// 2000 baris melakukan hal lain</span>
<span class="nc">System</span><span class="o">.</span><span class="na">exit</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="c1">// selesai</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Variabel <code class="language-plaintext highlighter-rouge">a</code> baru akan disediakan tempat pada waktu <code class="language-plaintext highlighter-rouge">ck.coba1()</code> dijalankan. Dan tempatnya segera bebas untuk bisa digunakan variabel lain sepanjang 1000 baris berikut. VM baru akan membuatkan tempat lagi di perintah <code class="language-plaintext highlighter-rouge">ck.coba2()</code>, untuk kemudian segera dibebaskan dan dapat dipakai orang lain sampai akhir program.</p>
<p>Nah, lebih efisien kan? Buat apa booking tempat kalau tidak dipakai? Semakin banyak tempat yang dibooking, semakin tinggi konsumsi memori yang dibutuhkan aplikasi kita.</p>
<h2 id="visibility--scope">Visibility / Scope</h2>
<p>Deklarasi variabel menentukan di mana variabel tersebut bisa digunakan (dilihat), atau dalam istilah lain juga disebut dengan scope variabel. Mari kita tambahkan method di class CobaKedua</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nf">CobaKedua</span> <span class="o">(){</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">coba1</span><span class="o">(){</span>
<span class="nc">String</span> <span class="n">a</span> <span class="o">=</span> <span class="s">"ari"</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">coba2</span><span class="o">(){</span>
<span class="nc">String</span> <span class="n">a</span> <span class="o">=</span> <span class="s">"ira"</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">coba3</span><span class="o">(){</span>
<span class="nc">Integer</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">7</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kode program di atas akan menimbulkan error pada saat dicompile. Kenapa demikian?</p>
<p>Error terjadi karena <code class="language-plaintext highlighter-rouge">System.out.println(a)</code> dalam method <code class="language-plaintext highlighter-rouge">coba3</code> tidak bisa melihat variabel <code class="language-plaintext highlighter-rouge">a</code>. Dia hanya bisa melihat variabel <code class="language-plaintext highlighter-rouge">x</code>. Ini terjadi karena variabel <code class="language-plaintext highlighter-rouge">a</code> dideklarasikan di luar scope method <code class="language-plaintext highlighter-rouge">coba3</code>, sehingga dia <code class="language-plaintext highlighter-rouge">invisible</code> di dalam method <code class="language-plaintext highlighter-rouge">coba3</code>.</p>
<p>Jadi, lokasi di mana kita mendeklarasikan variabel bukan ditentukan oleh pertimbangan performance, melainkan di mana variabel tersebut mau kita pakai. Walaupun deklarasi dalam method lebih irit memori, tapi tetap saja tidak bisa dipakai karena tidak terlihat di dalam method <code class="language-plaintext highlighter-rouge">coba3</code>.</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Jadi, untuk urusan deklarasi variabel ini, tidak pada tempatnya kalau kita mempertimbangkan masalah performance. Deklarasi variabel ditentukan oleh baris kode mana yang mau menggunakannya. Seperti pada contoh di atas, bila dia mau digunakan di method <code class="language-plaintext highlighter-rouge">coba1</code> dan <code class="language-plaintext highlighter-rouge">coba2</code>, maka tidak ada pilihan selain mendeklarasikannya sejajar dengan kedua method tersebut, seperti pada class <code class="language-plaintext highlighter-rouge">CobaPertama</code>.</p>
<p>Tapi kalau hanya ingin digunakan di dalam satu method saja, cukup deklarasikan sejajar dengan kode program yang menggunakannya.</p>
<p>Sebagai panduan umum, berikut aturan yang biasa saya gunakan</p>
<blockquote>
<p>Deklarasikan variabel dalam scope se-sempit mungkin</p>
</blockquote>
<p>Dengan demikian, variabel kita tidak booking tempat lebih dari yang dibutuhkan. Sehingga aplikasi kita tidak boros memori.</p>
Mengapa kita membuat class2014-12-16T15:26:00+07:00https://software.endy.muhardin.com/java/mengapa-kita-membuat-class<p>Ada <a href="https://www.facebook.com/groups/ForumJavaIndonesia/10152533869543017">pertanyaan di grup Facebook</a> yang cukup menarik untuk kita bahas di sini. Sample code yang ditanyakan saya modifikasi sedikit supaya lebih cocok.</p>
<h2 id="class-mahasiswa">Class Mahasiswa</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Mahasiswa</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">nama</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">tanggalLahir</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">JenisKelamin</span> <span class="n">jenisKelamin</span><span class="o">;</span>
<span class="c1">// getter setter</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">enum</span> <span class="nc">JenisKelamin</span><span class="o">{</span>
<span class="no">PRIA</span><span class="o">,</span> <span class="no">WANITA</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="class-simpanmahasiswaservlet">Class SimpanMahasiswaServlet</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpanMahasiswaServlet</span> <span class="kd">extends</span> <span class="nc">HttpServlet</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">MahasiswaDao</span> <span class="n">md</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MahasiswaDao</span><span class="o">();</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doPost</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">req</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">resp</span><span class="o">){</span>
<span class="nc">Mahasiswa</span> <span class="n">m</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Mahasiswa</span><span class="o">();</span>
<span class="n">m</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"nama"</span><span class="o">));</span>
<span class="nc">SimpleDateFormat</span> <span class="n">formatTanggal</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleDateFormat</span><span class="o">(</span><span class="s">"yyyy-MM-dd"</span><span class="o">);</span>
<span class="n">m</span><span class="o">.</span><span class="na">setTanggalLahir</span><span class="o">(</span><span class="n">formatTanggal</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"tanggal"</span><span class="o">)));</span>
<span class="n">m</span><span class="o">.</span><span class="na">setJenisKelamin</span><span class="o">(</span><span class="nc">JenisKelamin</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"jenis"</span><span class="o">)));</span>
<span class="n">md</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">m</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="class-mahasiswadao">Class MahasiswaDao</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MahasiswaDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Mahasiswa</span> <span class="n">m</span><span class="o">){</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"INSERT INTO mahasiswa values (?,?,?)"</span><span class="o">;</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">koneksi</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">m</span><span class="o">.</span><span class="na">getNama</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setDate</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="k">new</span> <span class="n">java</span><span class="o">.</span><span class="na">sql</span><span class="o">.</span><span class="na">Date</span><span class="o">(</span><span class="n">m</span><span class="o">.</span><span class="na">getTanggalLahir</span><span class="o">().</span><span class="na">getTime</span><span class="o">()));</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">m</span><span class="o">.</span><span class="na">getJenisKelamin</span><span class="o">().</span><span class="na">name</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">executeUpdate</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<blockquote>
<p>Pertanyaan : Lebih kenceng mana contoh kode di atas (menggunakan class Mahasiswa) dibandingkan langsung saja pakai Map/Array ?</p>
</blockquote>
<p>Supaya lebih jelas, berikut contoh yang pakai array.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpanMahasiswaServlet</span> <span class="kd">extends</span> <span class="nc">HttpServlet</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">MahasiswaDao</span> <span class="n">md</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MahasiswaDao</span><span class="o">();</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doPost</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">req</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">resp</span><span class="o">){</span>
<span class="nc">String</span><span class="o">[]</span> <span class="n">m</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="mi">3</span><span class="o">];</span>
<span class="n">m</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"nama"</span><span class="o">);</span>
<span class="n">m</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"tanggal"</span><span class="o">);</span>
<span class="n">m</span><span class="o">[</span><span class="mi">2</span><span class="o">]</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"jenis"</span><span class="o">);</span>
<span class="n">md</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">m</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="class-mahasiswadao-1">Class MahasiswaDao</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MahasiswaDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">m</span><span class="o">){</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"INSERT INTO mahasiswa values (?,?,?)"</span><span class="o">;</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">koneksi</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">m</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">m</span><span class="o">++){</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">m</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
<span class="o">}</span>
<span class="n">ps</span><span class="o">.</span><span class="na">executeUpdate</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pembuatan class <code class="language-plaintext highlighter-rouge">Mahasiswa</code> di atas merupakan salah satu implementasi prinsip <code class="language-plaintext highlighter-rouge">encapsulation</code> dalam OOP. Class <code class="language-plaintext highlighter-rouge">Mahasiswa</code> disebut dengan istilah domain class, yaitu class yang dibuat untuk memodelkan kasus yang akan dibuat.</p>
<!--more-->
<blockquote>
<p>Jawaban : gak usah mikirin kenceng dulu. Soalnya pakai Array/Map itu salah. Buat apa aplikasi salah dibikin kenceng?</p>
</blockquote>
<p>Kenapa salah?</p>
<ol>
<li>Susah dipahami</li>
<li>Tidak bisa pakai library pihak ketiga</li>
<li>Menyia-nyiakan dukungan tipe data yang sudah disediakan Java</li>
</ol>
<h2 id="source-code-susah-dipahami">Source code susah dipahami</h2>
<p>Bayangkan kita lagi mau ngisi variabel di Netbeans/Eclipse.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Mahasiswa</span> <span class="n">m</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Mahasiswa</span><span class="o">();</span>
<span class="n">g</span><span class="o">.</span> <span class="o"><--</span> <span class="nc">Ctrl</span><span class="o">-</span><span class="nc">Spasi</span> <span class="n">autocomplete</span><span class="o">,</span> <span class="n">keluar</span> <span class="n">pilihan</span> <span class="n">setNama</span><span class="o">,</span> <span class="n">setTanggalLahir</span><span class="o">,</span> <span class="n">dsb</span>
</code></pre></div></div>
<p>Lebih gampang mana dipahami daripada seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span><span class="o">[]</span> <span class="n">m</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="mi">3</span><span class="o">];</span>
<span class="n">m</span> <span class="o"><--</span> <span class="n">gak</span> <span class="n">bisa</span> <span class="nc">Ctrl</span><span class="o">-</span><span class="nc">Spasi</span><span class="o">,</span> <span class="n">gak</span> <span class="n">keluar</span> <span class="n">pilihan</span> <span class="n">apa</span><span class="o">-</span><span class="n">apa</span>
</code></pre></div></div>
<p>Kalau seandainya ada programmer baru masuk, lebih besar mana kemungkinan melakukan kesalahan ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">m</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"jenis"</span><span class="o">));</span>
</code></pre></div></div>
<p>atau ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>m[0] = req.getParameter("tanggal");
</code></pre></div></div>
<h2 id="tidak-bisa-menggunakan-library-pihak-ketiga">Tidak bisa menggunakan library pihak ketiga</h2>
<p>Semua baris untuk set nilai di <code class="language-plaintext highlighter-rouge">SimpanMahasiswaServlet</code> itu bisa diotomasi pakai Spring MVC. Semua baris untuk get isinya dan simpan ke db di <code class="language-plaintext highlighter-rouge">MahasiswaDao</code> bisa diotomasi pakai Hibernate.</p>
<p>Bila suatu saat kita ingin konversi objek <code class="language-plaintext highlighter-rouge">Mahasiswa</code> menjadi JSON, kita bisa dengan mudah melakukan ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Mahasiswa m = new Mahasiswa();
// isi nama, tanggal lahir, jenis kelamin
ObjectMapper om = new ObjectMapper();
String json = om.writeValue(m);
</code></pre></div></div>
<p>Nah, coba lakukan hal di atas pakai array. Mana bisa?</p>
<p>Hal yang sama berlaku kalau mau konversi ke XML menggunakan JAXB.</p>
<h2 id="menyia-nyiakan-fitur-yang-sudah-disediakan">Menyia-nyiakan fitur yang sudah disediakan</h2>
<p>Kalau kita bikin class Mahasiswa, kita bisa bikin nama itu String, jenis kelamin Enum, tanggal lahir Date. Dengan pakai tipe data, kalau salah entri langsung ketahuan error pada saat ngetik source code. Kalau gak pake itu, pas dijalanin baru ketahuan.</p>
<p>Sebagai contoh, error ini langsung ketahuan di Eclipse/Netbeans, atau pada waktu compile (kalau pakai Notepad++)</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Mahasiswa</span> <span class="n">m</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Mahasiswa</span><span class="o">();</span>
<span class="c1">// bakalan error karena tipe data tanggalLahir adalah Date, bukan String</span>
<span class="n">m</span><span class="o">.</span><span class="na">setTanggalLahir</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"nama"</span><span class="o">));</span>
</code></pre></div></div>
<p>Kalau pakai array, tidak akan ketahuan</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span><span class="o">[]</span> <span class="n">m</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="mi">3</span><span class="o">];</span>
<span class="c1">// gak error karena sama-sama String</span>
<span class="n">m</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"nama"</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Prioritas utama dalam menulis kode program adalah sebagai berikut:</p>
<ol>
<li>Benar (correctness)</li>
<li>Mudah dipahami (readability)</li>
<li>Mudah dikembangkan (maintainability)</li>
<li>Performance</li>
</ol>
<p>Nah, jadi jangan pagi-pagi udah mikir performance dengan mengorbankan 3 hal diatasnya ya ;)</p>
Pembagian Kerja dalam Programming2014-10-25T09:25:00+07:00https://software.endy.muhardin.com/manajemen/pembagian-kerja-dalam-programming<p>Ada pertanyaan bagus di Forum Java Programmer Indonesia di Facebook</p>
<p><a href="https://lh6.googleusercontent.com/-WwcyvgyzpyI/VEsOaOMQ3pI/AAAAAAAAG9Q/z_KHGG79bUY/w560-h174-no/Selection_023.png"><img src="https://lh6.googleusercontent.com/-WwcyvgyzpyI/VEsOaOMQ3pI/AAAAAAAAG9Q/z_KHGG79bUY/w560-h174-no/Selection_023.png" alt="Foto" /></a></p>
<blockquote>
<p>Bagaimana cara pembagian tugas kalau mengerjakan aplikasi dengan tim?</p>
</blockquote>
<p>Berikut pendekatan yang biasa saya gunakan</p>
<!--more-->
<p>Pada prinsipnya, ada dua pendekatan:</p>
<ol>
<li>Bagi per fitur</li>
<li>Bagi per layer</li>
</ol>
<h3 id="pembagian-per-fitur">Pembagian per Fitur</h3>
<p>Masing-masing programmer mengerjakan satu fitur lengkap mulai dari tampilan sampai ke database. Kelebihan dari pendekatan ini, project manager mudah memonitor progressnya. Tinggal cek saja fitur mana yang udah mana yang belum. Kalau ada fitur yang tidak selesai-selesai, mudah mencari siapa yang bertanggung jawab.</p>
<p>Kekurangannya, masing-masing programmer harus paham dari depan sampai ke belakang. Dengan <a href="http://software.endy.muhardin.com/java/development-stack-2014/">development stack yang kita gunakan di ArtiVisi</a>, berarti tiap programmer harus paham :</p>
<ul>
<li>HTML</li>
<li>CSS dan Bootstrap</li>
<li>JavaScript dan <a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbza1Q1GfDxuy-0Q8EgkNlJQ">AngularJS</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYby1r_lG7eH2B2hz47MsRBek">Yeoman</a></li>
<li>Spring Framework</li>
<li>Spring Data JPA</li>
<li><a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbzyEMil0SjN5Xl5vOmxGTRD">Hibernate ORM</a></li>
</ul>
<p>Nah, ini cukup sulit dipenuhi. Dalam suatu perusahaan software development, biasanya ada junior programmer (fresh graduate) dan senior programmer (sudah pengalaman bertahun-tahun membuat berbagai macam aplikasi). Pendekatan seperti ini membuat junior programmer tidak ada kerjaan.</p>
<h3 id="pembagian-per-layer">Pembagian per Layer</h3>
<p>Aplikasi bisnis pada umumnya bisa dibagi menjadi 3 layer:</p>
<ul>
<li>User Interface / Tampilan : terdiri dari tampilan aplikasi (web atau desktop) dan report (pdf,xls,csv)</li>
<li>Implementasi Proses Bisnis</li>
<li>Akses ke Sumber Data : biasanya database relasional. Tapi jaman sekarang mulai terdiversifikasi ke sumber data jenis lain seperti aplikasi lain (melalui web service), NoSQL (mongodb, redis, memcached, dsb), email, sms, cloud storage (Dropbox, Google Drive), dan sebagainya.</li>
</ul>
<p>Dengan pembagian seperti ini, seluruh anggota tim mendapat pekerjaan yang sesuai dengan level kemampuannya. Junior programmer di ArtiVisi biasanya mendapat bagian User Interface. Yang lebih senior mengerjakan Implementasi Proses Bisnis dan Akses Data. Beberapa akses data yang mudah, misalnya CRUD satu tabel tanpa relasi, juga bisa diserahkan ke junior programmer supaya mereka bisa naik level.</p>
<p>Kelemahan cara ini, menambah kepusingan project manager. Apabila ada fitur yang delay, dia harus lihat secara detail ke proses development untuk menentukan bagian mana yang menemui kesulitan. Apakah sisi tampilan? Atau akses data?</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Nah, masing-masing pendekatan ada plus dan minusnya. Silahkan pilih yang sesuai. Tidak juga harus pilih salah satu, bisa juga kombinasi keduanya. Misalnya untuk CRUD tabel master, satu orang satu fitur. Tapi untuk yang kompleks seperti transaksi penjualan yang melibatkan update stok gudang, dibagi per layer.</p>
Waterfall is The Best2014-10-14T20:45:00+07:00https://software.endy.muhardin.com/manajemen/waterfall-is-the-best<p>Di berbagai komunitas Agile, metodologi waterfall merupakan bulan-bulanan dan wajib untuk dibully. Jangankan bilang waterfall itu bagus, coba katakan <code class="language-plaintext highlighter-rouge">Ada beberapa situasi di mana waterfall bisa dijalankan</code>. Saya jamin 1000% Anda akan segera dibully seperti layaknya pendukung Justin Bieber hadir di konser Metallica.</p>
<p>Kalau ada satu hal penting yang saya dapatkan dari belasan tahun berkecimpung di dunia IT adalah ini</p>
<blockquote>
<p>Banyak jargon, metodologi, framework, library, teknologi datang dan pergi setiap saat. Untuk tiap pilihan yang kita buat, ada biaya yang menyertainya. Yaitu waktu dan tenaga yang kita investasikan untuk mempelajari dan mengadopsi pilihan tersebut. Kunci agar kita bisa memilih hal yang tepat sehingga imbal hasil pilihan kita lebih besar dari biayanya adalah, gunakan akal sehat dan jangan gampang terbuai jargon dan marketing gimmick.</p>
</blockquote>
<p>Prinsip akal sehat tersebut telah menyelamatkan saya dari berbagai teknologi gagal di masa lalu seperti misalnya:</p>
<ul>
<li>Struts 1</li>
<li>EJB 1 dan 2</li>
<li>XA Transaction</li>
<li>JMS</li>
<li>dan lain sebagainya</li>
</ul>
<p>Teknologi gagal bukan artinya teknologi tersebut tidak inovatif atau kurang canggih. Tapi biaya implementasinya jauh melebihi benefit yang diperoleh darinya.</p>
<p>Nah, kali ini dimana orang-orang ‘agile’ sibuk membully Waterfall, saya punya pendapat yang berbeda.</p>
<blockquote>
<p>Waterfall justru adalah cara paling alami untuk membuat software</p>
</blockquote>
<p>Wah kok bisa begitu?</p>
<!--more-->
<p>Sebelum kita bahas lebih lanjut, kita jelaskan dulu. Apa itu waterfall?</p>
<p>Yang senang nonton Youtube, silahkan langsung simak video berikut</p>
<div class="video"><figure><iframe width="640" height="480" src="//www.youtube.com/embed/X1c2--sP3o0?rel=0" frameborder="0" allowfullscreen=""></iframe></figure></div>
<p>Buat yang gak paham bahasa bule, baiklah saya ceritakan saja.</p>
<p>Waterfall adalah siklus pembuatan aplikasi (software development lifecycle) yang diperkenalkan oleh Winston Royce pada tahun 1970. Siklus ini terdiri dari beberapa tahap yaitu:</p>
<ol>
<li>Requirement Specification</li>
<li>Design</li>
<li>Implementation (kita menyebutnya coding)</li>
<li>Testing</li>
<li>Production / Go Live / Operation (aplikasi dipakai di kehidupan nyata)</li>
</ol>
<p>Sekilas, siklus ini terlihat sederhana dan logis. Sehingga banyak orang langsung menganggapnya solusi terhebat.</p>
<p>Seperti segala macam hal di dunia ini, tidak ada yang sempurna. Semua hal ada plus minusnya, termasuk siklus waterfall. Ini sebetulnya sudah dijelaskan juga oleh Opa Winston. Tapi apa lacur, metodologi ini sudah terlanjur dijual oleh para konsultan manajemen sebagai obat kuat tanpa tandingan. Dan seperti dagangan konsultan manajemen lainnya, segera dibumbui dengan berbagai jargon, success story, dan marketing gimmick lainnya sehingga terlihat canggih, mampu mengatasi global warming, kelaparan di Afrika, mendatangkan perdamaian di Timur Tengah, bahkan menyembuhkan kanker dan AIDS.</p>
<p>Ironisnya, gerombolan konsultan manajemen ini pula yang sekarang membully waterfall habis-habisan.</p>
<p>Oke, cukup belajar sejarah, sekarang kita kembali ke akal sehat.</p>
<h2 id="apa-bagusnya-waterfall-">Apa bagusnya waterfall ?</h2>
<p>Sebetulnya, siklus waterfall merupakan hukum alam dalam pembuatan software. Membantah waterfall sama saja dengan mengatakan air mengalir ke atas.</p>
<p>Tidak percaya? Mari kita telusuri satu per satu</p>
<h3 id="requirement-specification">Requirement Specification</h3>
<p>Coba sekarang saya ketemu Anda, trus saya bilang</p>
<blockquote>
<p>Ayo mulai coding !!</p>
</blockquote>
<p>Apakah Anda bisa mulai coding? Tentu tidak bisa. Mau coding apa? Aplikasi perpustakaan? Aplikasi payroll? Social media? Kan harus ditentukan dulu.</p>
<p>Oke, saya kasi petunjuk. Aplikasi social media. Apakah sekarang bisa mulai coding?</p>
<p>Tentu belum. Social media seperti apa? Relationship-based (ala facebook), location-based (ala foursquare), photo-based (ala flickr dan instagram), atau apa?</p>
<blockquote>
<p>Oh ternyata perlu dijelaskan ya apa yang mau dibuat?</p>
</blockquote>
<p>Nah ini namanya fase requirement specification.</p>
<p>Beberapa orang akan langsung komentar.</p>
<blockquote>
<p>Wah, bikin dokumen tebel dong. Apa dibaca? Kalau ada perubahan gimana? Apakah ada business valuenya?</p>
</blockquote>
<p>Ini mitos. Spesifikasi requirement gak harus berupa dokumen 300 halaman. Bisa berupa <a href="http://software.endy.muhardin.com/manajemen/prototyping/">UI Mockup</a>.</p>
<p>Justru intinya proses requirement adalah supaya user bisa menjelaskan maksudnya pada programmer. Kalau ternyata tidak ditangkap dengan baik, ya tentu penjelasannya harus diperbaiki sampai kedua pihak benar-benar sepaham.</p>
<blockquote>
<p>Apakah selalu harus melalui proses requirement?</p>
</blockquote>
<p>Tidak harus, kalau softwarenya kita buat sendiri untuk dipakai sendiri. Misalnya saya mau buka toko, butuh aplikasi. Lalu saya coding sendiri. Dalam kasus ini, tidak perlu proses requirement. Nanti dikira gila, masa programmernya (saya) minta dijelaskan sama usernya (saya juga)?</p>
<p>Tapi pada kenyataannya, user dan programmer biasanya beda orang. Jadi ya tentu harus dijelaskan dong.</p>
<p>Jadi, ternyata Opa Winston benar. Sebelum coding ya requirement dulu dong. Wajib hukumnya. Gak ada requirement, gak bisa mulai coding. Setelah selesai coding, masa iya perlu requirement?</p>
<h3 id="design">Design</h3>
<blockquote>
<p>Apakah wajib melakukan proses desain?</p>
</blockquote>
<p>Tergantung dua hal:</p>
<ol>
<li>Seberapa kompleks aplikasi yang dibuat?</li>
<li>Berapa orang yang terlibat?</li>
</ol>
<p>Untuk aplikasi 2-3 tabel database, kalau saya kerjakan sendiri, biasanya saya langsung coding. Sebenarnya ada juga saya merenungkan skema database dan mau ditaruh di class mana method dan property. Tapi karena saya sendiri yang coding, tabelnya juga cuma sedikit, yang terlihat dari luar ya saya langsung mengetik kode program.</p>
<p>Kalau aplikasinya besar, tidak mungkin semua saya simpan di kepala sendiri. Harus saya tulis. Selain itu, bikin software mirip main catur. Satu langkah bidak harus kita hitung konsekuensinya beberapa langkah ke depan. Pilih teknologi apa, desktop atau web, berapa layer, berapa modul, bagaimana integrasi antar modul, dan sebagainya. Proses mengidentifikasi alternatif solusi, kemudian memilih salah satunya, ini disebut desain aplikasi.</p>
<p>Contoh kasus, salah satu produk ArtiVisi adalah aplikasi pembayaran tagihan. Aplikasi tersebut bisa melunasi tagihan listrik, telepon, cicilan, beli pulsa listrik, pulsa handphone, dan sebagainya. Jenis produk datang dan pergi. Format cetakan struk berubah-ubah sesuai ketentuan bank. Nah tentu kita harus pikirkan bagaimana supaya aplikasinya mudah beradaptasi kalau ada produk baru, perubahan cetakan struk, dan berbagai perkembangan bisnis lainnya. Segala hal ini namanya proses desain software.</p>
<blockquote>
<p>Faktor kedua, siapa yang akan coding?</p>
</blockquote>
<p>Masa saya semua? Kalau saya yang coding semua kapan lagi saya sempat menulis blog untuk Anda semua :D Belum lagi jadwal <a href="https://www.youtube.com/watch?v=A3sM9ePaBvw&list=PL9oC_cq7OYbxjvQd8_isrxi8RxVmHXjZJ">syuting Youtube</a>. Asal tau saja, jadi seleb internet cukup menyita waktu :P</p>
<p>Oke, jadi perlu dibantu orang lain untuk coding. Dengan demikian saya perlu jelaskan di mana harus menaruh template struk, bagaimana skema database, dan berbagai hal lainnya. Sekarang saya wajib membuat dokumen desain.</p>
<blockquote>
<p>Kenapa gak dijelaskan aja desainnya? Repot-repot bikin dokumen, nanti juga berubah lagi.</p>
</blockquote>
<p>Mendingan mana, saya tulis sekali dengan resiko bakalan berubah, atau gak saya tulis trus programmernya cuti/sakit/resign/melahirkan dan saya harus jelaskan lagi setiap kali ganti orang?</p>
<p>Oke Opa Winston, saya harus desain dan bikin dokumennya. Kecuali aplikasinya buat saya, yang desain saya, yang coding saya, yang mau pake saya juga.</p>
<p>Kapan kita bisa desain? Tentu setelah requirement. Dan juga pastinya sebelum coding.</p>
<blockquote>
<p>Apakah desain bisa tidak dikerjakan?</p>
</blockquote>
<p>Bisa saja langsung coding. Tapi gini, semua programmer yang pernah <a href="http://software.endy.muhardin.com/manajemen/aplikasi-prakarya-vs-aplikasi-production/">bikin aplikasi production</a> pasti tau fakta ini</p>
<blockquote>
<p>Aplikasi production tidak sekedar semua fitur ada. Tapi juga siap dideploy di berbagai environment, kebal menerima data invalid, punya pesan error yang informatif, performancenya acceptable, bisa menangani load tinggi, aman dari berbagai serangan, mudah dimaintenance (backup, archive, offline, monitoring), dan berbagai hal lainnya.</p>
</blockquote>
<p>Artinya untuk tiap fitur, kita harus :</p>
<ul>
<li>tes</li>
<li>buatkan validasinya</li>
<li>buatkan pesan errornya</li>
<li>buatkan log file yang bagus</li>
<li>ukur performancenya</li>
<li>lihat apa yang terjadi kalau dihajar request tinggi</li>
<li>dsb</li>
</ul>
<blockquote>
<p>Bagaimana kalau kita sudah implement semua kebutuhan aplikasi production, ternyata kita baru sadar bahwa skema database kita kurang optimal?</p>
</blockquote>
<p>Nah, selamat bekerja mengulang semua kelengkapan production di atas :D</p>
<blockquote>
<p>Ampun Opa Winston. Anda benar, requirement dulu, trus desain, baru deh coding. Untuk testing, jangan diomelin lagi, saya udah baca <a href="https://www.google.com/search?q=site%3Asoftware.endy.muhardin.com&q=ruthless+testing&gws_rd=ssl">serial ruthless testing di blog Om Endy</a>.</p>
</blockquote>
<p>Lalu, kalau si Opa benar, kenapa waterfall dibully?</p>
<h2 id="kesalahan-dalam-implementasi-waterfall">Kesalahan dalam implementasi Waterfall</h2>
<p>Sebenarnya Opa Winston juga sudah menjelaskan bahwa ada resiko dalam penggunaan waterfall. Ini juga sudah dibahas Agan Steve McConnell dalam bukunya Rapid Development. Singkatnya kira-kira begini</p>
<blockquote>
<p>Jangan gunakan waterfall satu siklus kalau projectnya besar.</p>
</blockquote>
<p>Jadi kalau misalnya kita bikin aplikasi ERP, terdiri dari modul akunting, procurement, inventory, manufacture, sales, tax, dan lain sebagainya, jangan dikerjakan sekali pukul satu kali requirement satu kali desain, satu kali coding.</p>
<p>Kenapa gak boleh sekali pukul? Karena aplikasinya sangat besar, requirementnya bisa makan waktu 4 bulan. Nanti desainnya 2 bulan lagi. Total sudah 6 bulan dan belum juga mulai menginstall Netbeans.</p>
<p>Di pertengahan tahun biasanya manajemen rapat untuk evaluasi bisnis. Ternyata sales gak masuk target. Ada revisi program penjualan, tadinya minta diskon harus ke direktur, sekarang cukup ke kepala cabang.</p>
<p>Nah, belum sempat nulis <code class="language-plaintext highlighter-rouge">Hello World</code>, requirement sudah berubah. Revisi requirement, revisi desain, makan waktu 3 bulan. Menjelang akhir tahun, manajemen rapat lagi, proses bisnis berubah lagi. Trus kapan kita mau mulai coding <code class="language-plaintext highlighter-rouge">Hello World</code>?</p>
<p>Fenomena seperti ini yang bikin waterfall dibully. Padahal balik ke premis awal, ya akal sehat dipakai dong. Masa 6 bulan dokumentasi doang belum nulis kode program satu barispun? Emangnya situ programmer atau pegawai kelurahan?</p>
<blockquote>
<p>Bagaimana solusinya?</p>
</blockquote>
<p>Ini sebetulnya juga sudah dijelaskan oleh Opa Winston. Ya di tahun 1970 itu juga barengan sambil menjelaskan waterfall. Agan Steve juga udah menjelaskan di <a href="http://www.stevemcconnell.com/rdcntnt.htm">Rapid Development</a>. Yang optimal itu begini</p>
<blockquote>
<p>Requirement menyeluruh dilakukan terhadap seluruh aplikasi, tapi global saja. Tujuannya supaya bisa membagi modul-modul dan mendapatkan gambaran bagaimana nanti integrasinya. Setelah itu, bagi per modul. Tiap modul lakukan mini-waterfall sampai selesai modul per modul. Nanti tiap beberapa modul selesai, ada lagi iterasi untuk proses integrasi antar modul.</p>
</blockquote>
<p>Cara seperti ini oleh Agan Steve disebut dengan siklus <code class="language-plaintext highlighter-rouge">Staged Delivery</code>. Walaupun namanya keren dan terlihat beda, tapi ya sebenarnya menurut saya ini sama saja dengan <code class="language-plaintext highlighter-rouge">Waterfall dengan Akal Sehat</code>. Yaitu memecah aplikasi besar menjadi modul-modul, dimana masing-masing modul dikerjakan dengan waterfall juga. Dan tidak ada juga yang melarang modul-modul ini dikerjakan secara paralel.</p>
<p>Bahkan Agan Steve bilang, gak semuanya harus dibikin sendiri. Kalau misalnya aplikasi akunting udah ada yang jual (Commercial Off The Self - COTS) ya beli aja daripada ribet-ribet bikin sendiri. Malahan ada beberapa perusahaan yang kerjanya beli-beli doang, digabungin, trus dijual. Sampai sempat-sempatnya dia bikin nama keren buat dirinya sendiri, yaitu <code class="language-plaintext highlighter-rouge">System Integrator</code>.</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Di dunia IT, banyak sekali pilihan. Mulai dari bahasa pemrograman, framework, metodologi, macam-macam. Kita harus pintar-pintar memilih dan tidak gampang terbuai bujuk rayu konsultan. Caranya gampang saja, gunakan akal sehat.</p>
<p>Jadi kalau lain kali ada yang menganjurkan dua programmer komputernya satu aja gantian, renungkan dengan akal sehat. Apakah gak sebaiknya sekalian dibeliin perlengkapan fitness? Jadi pas project selesai, programmernya punya perut sixpack.</p>
<p><a href="https://lh3.googleusercontent.com/-kWEJAXJQqiY/U01NMQkQ7_I/AAAAAAAAFp0/099uScfrhP0/w768-h576-no/rambo-wallpaper-800x600.jpg"><img src="https://lh3.googleusercontent.com/-kWEJAXJQqiY/U01NMQkQ7_I/AAAAAAAAFp0/099uScfrhP0/w768-h576-no/rambo-wallpaper-800x600.jpg" alt="Foto" /></a></p>
<p><em>Fotonya Rambo diambil <a href="http://wallpaperswide.com/rambo-wallpapers.html">dari sini</a></em></p>
Tips Meletakkan File Konfigurasi Aplikasi2014-09-08T14:00:00+07:00https://software.endy.muhardin.com/java/tips-meletakkan-file-konfigurasi-aplikasi<p>Dalam membuat aplikasi, biasanya ada nilai variabel yang ingin kita simpan di file konfigurasi, supaya pada saat isinya berubah tidak perlu melakukan kompilasi ulang. Misalnya konfigurasi koneksi database. Alamat database server, username, dan password biasanya sering berubah, tergantung tempat di mana aplikasi dideploy.</p>
<p>Ada beberapa hal yang biasanya menjadi pertanyaan, yaitu:</p>
<ol>
<li>Di mana sebaiknya kita menyimpan file tersebut?</li>
<li>Bagaimana caranya supaya aplikasi kita portable? Portable artinya bisa dicopy paste ke berbagai komputer dengan lokasi folder berbeda-beda.</li>
<li>Bagaimana cara membaca file konfigurasi tersebut?</li>
</ol>
<p>Pertanyaan ini diajukan salah satu member di grup <a href="https://www.facebook.com/groups/netbeans.id/?fref=nf">Netbeans Indonesia</a></p>
<p>Berikut screenshot kode program yang sudah dia buat</p>
<p><a href="https://lh4.googleusercontent.com/-5j9PxOw3oUM/VA1UZR2oPRI/AAAAAAAAGpM/FxnUMInN8ME/w660-h312-no/error-load-file.jpg"><img src="https://lh4.googleusercontent.com/-5j9PxOw3oUM/VA1UZR2oPRI/AAAAAAAAGpM/FxnUMInN8ME/w660-h312-no/error-load-file.jpg" alt="Foto" /></a></p>
<!--more-->
<h2 id="lokasi-file-konfigurasi">Lokasi File Konfigurasi</h2>
<p>Kita jawab dulu tentang tempat menaruh file konfigurasi. Ada tiga lokasi utama yang umum digunakan orang, yaitu:</p>
<ul>
<li>
<p>folder konfigurasi global. Kalau di Linux biasanya di folder <code class="language-plaintext highlighter-rouge">/etc</code>. Di Windows biasanya dalam folder <code class="language-plaintext highlighter-rouge">system32</code>. Konfigurasi yang disimpan di sini biasanya berlaku global dalam satu komputer. Nilainya terlihat dan digunakan oleh semua user komputer.</p>
</li>
<li>
<p>folder tempat aplikasi terinstal. Di Linux orang biasanya menginstal aplikasi di <code class="language-plaintext highlighter-rouge">/opt</code>, <code class="language-plaintext highlighter-rouge">/usr/local</code>, atau di home foldernya. Di Windows biasanya dalam <code class="language-plaintext highlighter-rouge">C:\Program Files</code></p>
</li>
<li>
<p>folder home masing-masing user. Di Linux biasanya <code class="language-plaintext highlighter-rouge">/home/namauser</code>, misalnya <code class="language-plaintext highlighter-rouge">/home/endy</code>. Di Windows, tergantung versinya. Windows 7 ke atas ada di <code class="language-plaintext highlighter-rouge">C:\Users\namauser</code>. Windows XP ada di <code class="language-plaintext highlighter-rouge">C:\Documents and Settings\namauser</code></p>
</li>
</ul>
<blockquote>
<p>Wuah banyak sekali.. Lalu kita pilih yang mana?</p>
</blockquote>
<p><a href="http://software.endy.muhardin.com/manajemen/aplikasi-prakarya-vs-aplikasi-production/">Aplikasi yang baik</a>, menggunakan tiga-tiganya dengan urutan prioritas yang berbeda-beda. Pertama dia akan mencari ke folder konfigurasi global. Kalau ada, maka nilainya akan digunakan. Selanjutnya, dia lihat ke folder tempat aplikasi terinstal. Terakhir, baru dia lihat ke folder home usernya.</p>
<blockquote>
<p>Lalu bagaimana kalau di ketiga tempat konfigurasi diisi dengan nilai berbeda?</p>
</blockquote>
<p>Misalnya username database, nilainya terisi di ketiga tempat tersebut. Username mana yang dipakai aplikasi?</p>
<p>Kebiasaan yang berlaku adalah, yang lebih spesifik lebih diutamakan daripada yang umum. Dalam hal ini, setting username di home lebih menang daripada setting username di folder aplikasi. Bila ada setting username di folder aplikasi dan folder konfigurasi global, maka yang dipakai adalah yang di folder aplikasi.</p>
<h2 id="cara-membaca-konfigurasi">Cara Membaca Konfigurasi</h2>
<p>Karena ada tiga tempat:</p>
<ul>
<li>folder global</li>
<li>folder instalasi</li>
<li>folder home</li>
</ul>
<p>Maka cara membacanya juga berbeda-beda. Sebagai ilustrasi, misalnya nama filenya sama, yaitu <code class="language-plaintext highlighter-rouge">config.txt</code>. Cuma lokasinya saja yang berbeda.</p>
<h3 id="folder-global">Folder Global</h3>
<p>Folder global biasanya sama antar komputer. Dia hanya akan berbeda kalau sistem operasinya berbeda (Windows vs Linux). Tapi dalam sesama sistem operasi, biasanya tetap sama, misalnya folder <code class="language-plaintext highlighter-rouge">/etc</code> ada di seluruh Linux. Demikian juga dengan folder <code class="language-plaintext highlighter-rouge">C:\Windows\System32</code>.</p>
<p>Karena nama foldernya sama, kita bisa langsung load menggunakan absolute path seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span> <span class="n">konfig</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="s">"/etc/config.txt"</span><span class="o">);</span>
</code></pre></div></div>
<p>atau di Windows</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span> <span class="n">konfig</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="s">"C:\\Windows\\System32\\config.txt"</span><span class="o">);</span>
</code></pre></div></div>
<p>Jangan lupa menggunakan escape character, karena karakter backslash (\) memiliki arti khusus.</p>
<h3 id="folder-aplikasi">Folder Aplikasi</h3>
<p>Kalau di Java, biasanya file konfigurasi akan disimpan di dalam file jar atau dalam folder yang terdaftar dalam <code class="language-plaintext highlighter-rouge">CLASSPATH</code> seperti misalnya <code class="language-plaintext highlighter-rouge">WEB-INF/classes</code> dalam aplikasi web.</p>
<p>Folder atau jar yang terdaftar dalam CLASSPATH memiliki keistimewaan, yaitu bisa diakses dengan mengacu pada class yang memanggilnya. Misalnya kita memiliki deklarasi class seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">belajar.java</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BacaKonfig</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">xx</span><span class="o">){</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Bila file <code class="language-plaintext highlighter-rouge">config.txt</code> ada dalam folder <code class="language-plaintext highlighter-rouge">belajar</code> dalam jar atau CLASSPATH, maka kita bisa akses sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">belajar.java</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BacaKonfig</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">xx</span><span class="o">){</span>
<span class="nc">InputStream</span> <span class="n">konfig</span> <span class="o">=</span> <span class="nc">BacaKonfig</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span><span class="s">"/belajar/config.txt"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dengan method <code class="language-plaintext highlighter-rouge">getResourceAsStream</code>, di manapun lokasi file atau foldernya tidak masalah. Yang penting terdaftar dalam CLASSPATH. Metode ini biasa saya gunakan untuk membaca file-file template cetakan dan laporan (misalnya file jrxml-nya Jasper Report).</p>
<h3 id="folder-home">Folder Home</h3>
<p>Sebagai ilustrasi, file <code class="language-plaintext highlighter-rouge">config.txt</code> kita taruh dalam folder aplikasi dalam home user. Di Linux ini artinya <code class="language-plaintext highlighter-rouge">/home/endy/aplikasi-saya/config.txt</code>. Di Windows 7 namanya adalah <code class="language-plaintext highlighter-rouge">C:\Users\endy\aplikasi-saya\config.txt</code>.</p>
<p>Untuk membaca file tersebut, pertama kita harus dapatkan dulu lokasi sebenarnya dari home user</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">lokasiHome</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"user.home"</span><span class="o">);</span>
</code></pre></div></div>
<p>Setelah itu, baru kita load filenya. Pastikan kita menggunakan konstanta <code class="language-plaintext highlighter-rouge">File.separator</code> untuk mengatasi perbedaan separator folder di Linux (<code class="language-plaintext highlighter-rouge">/</code>) dan Windows (<code class="language-plaintext highlighter-rouge">\</code>).</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span> <span class="n">konfig</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">lokasiHome</span> <span class="o">+</span> <span class="nc">File</span><span class="o">.</span><span class="na">separator</span> <span class="o">+</span> <span class="s">"config.txt"</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Demikianlah <em>best-practices</em> dalam menaruh dan membaca file konfigurasi. Jangan lupa tujuan utamanya adalah supaya aplikasi kita portable (bisa berjalan di berbagai kondisi dan lokasi).</p>
<p>Semoga bermanfaat.</p>
Tanya Judul Skripsi2014-09-06T22:05:00+07:00https://software.endy.muhardin.com/akademik/tanya-judul-skripsi<p>Salah satu pertanyaan yang sering saya temui di berbagai forum pemrograman adalah</p>
<blockquote>
<p>Saya mahasiswa jurusan Teknik Informatika semester akhir.
Kira-kira judul skripsi yang bagus apa ya?</p>
</blockquote>
<p>Berikut jawaban saya …</p>
<!--more-->
<p>Sebetulnya rada useless nanya topik skripsi di forum. Kenapa? Walaupun banyak usulan, besar kemungkinan kamu gak bakal bisa implement karena alasan-alasan berikut:</p>
<ol>
<li>Gak boleh sama kampusnya. Beberapa kampus mengharamkan topik yang sudah terlalu umum seperti e-commerce. Tidak bagus buat koleksi perpustakaan mereka kalau ada ratusan skripsi dengan judul “Implementasi e-Commerce di PT Angin Ribut” cuma beda di nama PT saja.</li>
<li>Boleh sama kampus, tapi kamunya gak bisa. Contoh: bikin aplikasi tracking pengiriman pakai android dan google maps. Aplikasi seperti ini melibatkan berbagai antarmuka (web, mobile, desktop) dan butuh integrasi yang cukup rumit.</li>
<li>Dosen pembimbing untuk topik yang diusulkan anggota forum pemrograman belum tentu tersedia. Misalnya ada yang mengusulkan untuk membuat sistem otomasi peralatan rumah tangga (smarthouse) yang bisa diakses dari smartphone. Ini aplikasi yang canggih dan menarik. Bayangkan kamu bisa menyalakan AC kamar dari angkot pada waktu perjalanan pulang. Nah, emangnya di kampus kamu ada dosen yang menguasai mikro kontroller, pemrograman web (HTML,CSS,JS), pemrograman mobile (Android, iOS) sekaligus punya banyak waktu luang untuk membimbing kamu?</li>
<li>Scope terlalu besar. Misalnya membuat aplikasi warehouse dengan menggunakan NFC. Aplikasi seperti ini nanti akan terhubung dengan aplikasi akunting, aplikasi penjualan, aplikasi supply chain management, dan lain sebagainya. Hanya bermodalkan beberapa baris penjelasan dari komentator socmed tidak akan cukup untuk mendefinisikan lingkup skripsi yang akan kamu kerjakan.</li>
</ol>
<blockquote>
<p>Jadi gimana dong?</p>
</blockquote>
<ol>
<li>Minta dosen favorit kamu jadi pembimbing. Ini yang paling penting, karena kamu bakalan berinteraksi paling sering dengan beliau. Jangan asal ramah, pastikan juga punya banyak waktu luang, gampang dicari, mau ngereview, dan bisa ngasi feedback yang actionable. Dosen yang cuma bisa ngasi feedback ini jelek ini malah bakal menyulitkan kamu. Contoh paling top dosen pembimbing yang mantap adalah <a href="http://romisatriawahono.net/">Pak Romi Satria Wahono</a>. Kalo gak percaya, coba simak <a href="http://romisatriawahono.net/2013/12/12/metode-mengelola-penelitian-tesis-mahasiswa/">prosedur bimbingan yang beliau terapkan</a>.</li>
<li>Setelah dapat dosen, cari topik yang menarik buat kamu. Seneng makan? Bikin aplikasi review restoran atau pesan antar via mobile. Seneng nonton film? Bikin aplikasi review film dan bioskop. Topik menarik ini penting supaya kamu gak gampang menyerah waktu nanti mulai coding.</li>
<li>Konsultasikan sama dosennya mengenai scope pekerjaan. Aplikasi yang sama bisa dibuat lebar bisa dibuat sempit. Bisa cuma permukaan bisa mendalam. Pastikan sesuai dengan ketentuan kampus. Sesuaikan juga sama kemampuan dan waktu yang tersedia. Jangan mau bikin ERP komplit tapi codingnya baru bisa Hello World dan kamu udah di ambang DO.</li>
<li>Terakhir, niatkan agar aplikasi yang kamu bikin supaya go live. Beneran dipake orang dan gak cuma berakhir di sidang skripsi. Ini bisa jadi portfolio bagus pada saat cari kerja. Dan hei, mungkin aja bukan cuma dapat kerjaan, tapi malah bisa dapat investor. Jaman sekarang hostingan gratis banyak. Ada Google App Engine, Heroku, Cloudbees, dsb. Jadi tidak ada alasan aplikasi kamu tidak go live.</li>
</ol>
<p>Nah, demikian sekilas tentang memilih judul skripsi. Mudah-mudahan bermanfaat.</p>
Belajar Project Management dari Ubuntu2014-08-30T23:49:00+07:00https://software.endy.muhardin.com/manajemen/belajar-project-management-dari-ubuntu<p>Hampir seluruh aktivitas yang saya lakukan dengan komputer (smartphone juga termasuk komputer) menggunakan aplikasi open source. Jaman sekarang, aplikasi open source sudah jauh mengungguli aplikasi closed source baik dalam hal fitur, user-friendliness, reliability, dan kecepatan development (bug fix, fitur baru, enhancement, dan sebagainya).</p>
<p>Selama ini kita hanya tahu pakai saja. Download, instal, pakai, happy. Kita juga hanya tahu bahwa open source = kode programnya bisa dilihat, dimodifikasi, dan dibagikan ke siapa saja. Sebenarnya ada aspek lain dari open source yang juga menarik, penting, dan banyak sekali manfaatnya buat para pembaca blog saya yang utamanya adalah programmer, software project manager, dan juga pengusaha di sektor IT. Aspek tersebut adalah software development process yang terjadi sehingga semua produk canggih ini bisa berada di tangan kita saat ini.</p>
<p>Kehandalan manajemen proyek open source bisa kita lihat bukti nyatanya di distro linux Ubuntu. Coba lihat beberapa fakta berikut:</p>
<ul>
<li>Ubuntu rilis setiap enam bulan sekali, yaitu tiap bulan April dan Oktober.</li>
<li>Sejak rilis pertama, belum pernah terjadi keterlambatan rilis.</li>
<li>Kontributor Ubuntu (programmer, pembuat dokumentasi, desain tampilan, tukang burn CD, tukang update website, tukang upload iso, admin server, dsb) datang dari seluruh dunia. Bekerja 24 jam sehari 7 hari seminggu dengan timezone yang berbeda-beda. Jadi kalau kita masih kerja di ruangan yang sama, hanya dipisahkan <em>cubicle</em> saja, datang bareng, makan siang bareng, pulang bareng, sebaiknya jangan terlalu sering mengeluh di socmed ;p</li>
<li>Jumlah kontributornya ribuan orang. Bahasanya berbeda-beda. Lokasinya juga berbeda-beda.</li>
<li>Tulang punggung Ubuntu adalah kernel Linux dan desktop manager Gnome. Harus menunggu Gnome rilis dulu, baru Ubuntu bisa dikerjakan. Jangan sampai di tengah-tengah development Ubuntu, ada perubahan signifikan yang terjadi di Gnome sehingga Ubuntu harus rombak total.</li>
<li>Ubuntu basisnya adalah distro Debian varian unstable. Proses development harus memperhatikan agar jangan sampai menyimpang terlalu jauh dari Debian supaya tetap bisa sinkronisasi pada saat Debian naik versi.</li>
<li>Ubuntu digunakan orang dari seluruh dunia, sehingga harus bisa mendukung berbagai bahasa, format angka, format tanggal, dan berbagai variasi lainnya.</li>
<li><a href="http://software.endy.muhardin.com/manajemen/release-management/">Kompatibilitas antar rilis</a> harus terjaga, supaya orang bisa <a href="http://software.endy.muhardin.com/linux/upgrade-ubuntu/">upgrade dengan mudah</a>.</li>
</ul>
<blockquote>
<p>Nah, apa rahasianya supaya bisa seperti itu?</p>
</blockquote>
<!--more-->
<p>Oh ternyata tidak rahasia, karena semua tahapnya dijelaskan <a href="https://wiki.ubuntu.com/UbuntuDevelopment/ReleaseProcess">di sini</a>. Ya namanya juga dibuat oleh orang seluruh dunia. Tentu saja tidak ada yang rahasia di sini. Ini namanya rahasia umum :D</p>
<p>Ubuntu menggunakan skema <a href="https://wiki.ubuntu.com/TimeBasedReleases">Time Based Release</a>. Skema ini juga digunakan banyak project open source lain, misalnya <a href="https://wiki.gnome.org/ReleasePlanning/TimeBased">Gnome</a>.</p>
<p>Proses development Ubuntu dimulai dari issue tracker, di mana semua user (dari seluruh dunia) menginputkan bug report dan feature request. Semua request ini kemudian akan divoting oleh sesama user untuk menunjukkan seberapa banyak orang yang membutuhkan request tersebut. Request populer tentu ratingnya akan naik. Untuk request fitur baru dibuatkan <a href="https://wiki.ubuntu.com/FeatureSpecifications">spesifikasinya</a>.</p>
<p>Selanjutnya, Canonical mengadakan <a href="http://uds.ubuntu.com/agenda/">Ubuntu Developer Summit</a>. Pesertanya ada yang datang secara fisik, ada juga yang ikutan secara remote. Eventnya terbuka untuk umum dan <a href="http://www.jonobacon.org/2014/02/11/the-next-ubuntu-developer-summit-11-13-march-2014/">semua sesi direkam sehingga semua orang tahu proses pengambilan keputusan yang terjadi</a>.</p>
<p>UDS ini akan menghasilkan gambaran umum fitur apa saja yang akan dibuat, bagaimana skala prioritasnya, dan apa saja kira-kira pekerjaan yang harus dilakukan. Output dari UDS dapat kita lihat contohnya di <a href="https://blueprints.launchpad.net/ubuntu/karmic/+specs">blueprint rilis Karmic</a>.</p>
<p>Tahap selanjutnya adalah coding/development. Semua kontributor bekerja membuat fitur sesuai wilayah masing-masing. Ini dilakukan sampai terjadi freeze (stop penambahan fitur). Ada macam-macam freeze, misalnya:</p>
<ul>
<li><a href="https://wiki.ubuntu.com/FeatureFreeze">Feature Freeze</a> : berhenti menambahkan fitur baru</li>
<li><a href="https://wiki.ubuntu.com/UserInterfaceFreeze">User Interface Freeze</a> : berhenti mengubah tampilan, supaya bisa mulai membuat user manual / dokumentasi aplikasi</li>
<li><a href="https://wiki.ubuntu.com/DocumentationStringFreeze">Documentation String Freeze</a> : berhenti mengubah tulisan di dokumentasi aplikasi utama yang berbahasa Inggris, supaya bisa mulai diterjemahkan ke berbagai bahasa</li>
</ul>
<p>Setelah penambahan fitur berhenti, semua orang berkonsentrasi fixing bug, tuning performance, refactoring, membuat dokumentasi, dan kegiatan beres-beres lainnya untuk <a href="http://software.endy.muhardin.com/manajemen/aplikasi-prakarya-vs-aplikasi-production/">mengubah barang prakarya menjadi barang production</a>. Ada beberapa milestone di sini yang menunjukkan tingkat kematangan produk, yaitu Alpha, Beta, Release Candidate, sampai akhirnya Stable Release. Proses coding dan testing di berbagai milestone yang biasa kita lakukan di ArtiVisi bisa dibaca <a href="http://software.endy.muhardin.com/java/staged-deployment/">di sini</a>.</p>
<p>Setelah stable release, kembali ke awal proses, yaitu issue tracker.</p>
<p>Simak juga <a href="http://www.ubuntu-user.com/Online/Blogs/Amber-Graner-You-in-Ubuntu/Interview-Kate-Stewart-Ubuntu-Release-Manager-at-Canonical">penjelasan Kate Stewart, Release Manager Ubuntu dari Canonical</a>.</p>
<p>Nah, demikianlah proses yang terjadi di dalam dapur Ubuntu. Sebagai bahan perbandingan, kita juga bisa menyimak skema project management yang terjadi di berbagai project open source lain, misalnya:</p>
<ul>
<li><a href="http://wiki.eclipse.org/SimRel/Overview">Eclipse</a></li>
<li><a href="https://github.com/git/git/blob/master/Documentation/howto/maintain-git.txt">Git</a></li>
<li><a href="https://www.kernel.org/doc/Documentation/development-process/2.Process">Linux Kernel</a>, untuk yang butuh penjelasan dengan diagram bisa <a href="http://techblog.aasisvinayak.com/linux-kernel-development-process-how-it-works/">baca ini</a></li>
<li><a href="https://source.android.com/source/code-lines.html">Android OS</a></li>
<li>masih banyak lagi, silahkan google sendiri</li>
</ul>
<p>Semoga bermanfaat.</p>
Capacity Planning2014-08-19T17:48:00+07:00https://software.endy.muhardin.com/manajemen/capacity-planning<p>Ada pertanyaan bagus di milis IT Project Management, sebagai berikut</p>
<blockquote>
<p>Mohon di-share caranya menentukan spesifikasi minimal hardware yang dibutuhkan sebuah sistem.
contoh <a href="http://en.wikipedia.org/wiki/System_requirements">http://en.wikipedia.org/wiki/System_requirements</a>
Kriteria apa saja yang digunakan dalam memilih hardware? Terutama menentukan jumlah RAM dan Speed Processor nya. Apakah cukup dari jumlah concurrent user? Atau menggunakan metode trial-and-error.
Kalaupun trial-and-error, tentunya ada standar yang digunakan sebagai benchmark awal ya kan? Bagaimana standar awal tersebut?</p>
</blockquote>
<p>Mari kita bahas</p>
<!--more-->
<p>Proses perencanaan seperti ini disebut dengan istilah Capacity Planning. Penjelasan lengkapnya bisa dibaca <a href="http://en.wikipedia.org/wiki/Capacity_planning">di Wikipedia</a>.</p>
<p>Pada intinya, proses Capacity Planning di dunia IT dilakukan untuk mengetahui perencanaan infrastruktur yang dibutuhkan agar sistem yang dibangun bisa bekerja dengan baik sesuai kebutuhannya. Ada beberapa metrik yang ingin dihasilkan dari proses ini, antara lain:</p>
<ul>
<li>kebutuhan hardware (berapa server, masing-masing prosesornya apa, memori berapa, harddisk berapa, dan sebagainya)</li>
<li>kebutuhan jaringan (berapa besar bandwidth, berapa IP public, berapa jalur redundan/backup, dan sebagainya)</li>
<li>kebutuhan tempat (berapa luas ruang server, apakah dibutuhkan lokasi sekunder untuk Disaster Recovery Center)</li>
<li>kebutuhan energi (berapa watt listriknya, berapa UPS, perlu genset atau tidak, dan sebagainya)</li>
</ul>
<h2 id="data-yang-dibutuhkan">Data yang dibutuhkan</h2>
<p>Angka-angka di atas tentu tidak turun dari langit. Ada beberapa data yang kita butuhkan sebagai input, antara lain:</p>
<ul>
<li>proyeksi pertumbuhan kegiatan bisnis. Berapa user di tahun pertama, berapa user di tahun kedua, dan seterusnya</li>
<li>profil perilaku user. Apakah ramai di tanggal tertentu (misalnya aplikasi pembayaran tagihan)? Apakah ramai di bulan tertentu (misalnya aplikasi penjualan tiket pesawat)? Atau di hari tertentu (misalnya tiket bioskop)?</li>
<li>constraint yang harus dipenuhi secara bisnis. Berapa detik user boleh menunggu? Berapa banyak user yang diperbolehkan mengantri dalam sistem? Apakah boleh sistemnya down? Kalau boleh, berapa lama batas waktunya sampai dia harus aktif lagi?</li>
<li>profil perilaku aplikasi kita sendiri. Berapa besar data yang disimpan dalam disk untuk tiap user? Berapa besar data tersimpan untuk satu transaksi? Berapa besar data yang lewat di jaringan untuk satu transaksi? Bagaimana rinciannya, berapa byte antara database server ke application server, berapa byte antara application server ke perangkat yang digunakan user?</li>
</ul>
<h2 id="cara-memperoleh-data">Cara Memperoleh Data</h2>
<p>Data-data ini bisa kita peroleh dengan berbagai cara, misalnya:</p>
<ul>
<li>proyeksi pertumbuhan bisa diminta dari divisi marketing. Mereka tentu punya target pasar yang nantinya kita konversi menjadi jumlah user.</li>
<li>profil perilaku user juga bisa diminta dari divisi marketing. Segmentasinya seperti apa (anak, remaja, atau dewasa; orang kota atau orang desa), demografinya bagaimana, sebaran geografisnya di mana saja, dan sebagainya.</li>
<li>constraint juga bisa diperoleh dari divisi bisnis / marketing. Seperti apa penawaran kompetitor, apakah mereka menjanjikan fitur tertentu yang diunggulkan, dan sebagainya.</li>
<li>data tentang aplikasi kita, tentu kita bisa kumpulkan sendiri. Deploy aplikasi kita di testing server, lalu lakukan functional dan stress test dengan tools seperti <a href="http://jmeter.apache.org/">JMeter</a>. Jalankan aplikasi, jalankan testing tools, dan kumpulkan datanya. Cara melakukan profiling bisa dibaca di <a href="http://software.endy.muhardin.com/programming/tuning-performance/">artikel saya tentang tuning performance</a></li>
</ul>
<p>Beberapa data yang bisa dikumpulkan dari cara ini misalnya:</p>
<h3 id="storage">Storage</h3>
<ul>
<li>besar data yang digunakan oleh satu user aplikasi</li>
<li>besar data yang digunakan untuk masing-masing jenis transaksi bisnis (penjualan, pembelian, barang masuk, barang keluar, dan sebagainya)</li>
</ul>
<h3 id="processing">Processing</h3>
<p>Processing di sini maksudnya, dengan prosesor dan RAM tertentu, berapa</p>
<ul>
<li>jumlah maksimal request yang bisa dihandle tanpa terjadi error (100% sukses)</li>
<li>jumlah maksimal request yang bisa dihandle dengan tingkat error yang bisa diterima (10% transaksi gagal)</li>
<li>waktu yang dibutuhkan untuk memproses masing-masing jenis request (login, entri transaksi, generate report, pencarian data, dsb)</li>
</ul>
<h3 id="network">Network</h3>
<ul>
<li>berapa byte yang ditransmisikan untuk masing-masing jenis request dan response</li>
<li>waktu yang dibutuhkan untuk transmisi dengan berbagai jenis koneksi (ADSL, leased line, fiber optik, GPRS, 3G, dsb)</li>
<li>waktu yang ditoleransi aplikasi sebelum request dinyatakan timeout</li>
</ul>
<p>Sampai di sini, kita sudah tahu output, dan juga sudah punya input. Tinggal diproses saja.</p>
<h2 id="memproses-data">Memproses Data</h2>
<p>Ada beberapa hal yang bisa kita analisa dari data di atas, yaitu:</p>
<ul>
<li>kebutuhan hardware</li>
<li>kebutuhan jaringan</li>
<li>skema deployment</li>
<li>kebutuhan infrastruktur pendukung</li>
</ul>
<h3 id="kebutuhan-hardware">Kebutuhan Hardware</h3>
<p>Dari stress test yang kita lakukan, kita bisa tahu 2 hal:</p>
<ol>
<li>Spesifikasi hardware yang kita gunakan pada waktu test</li>
<li>Jumlah maksimal load yang bisa ditangani oleh hardware tersebut</li>
</ol>
<p>Sebagai ilustrasi, misalnya kita menggunakan server test dengan spesifikasi sebagai berikut:</p>
<ul>
<li>Prosesor Xeon Dual Core</li>
<li>RAM 8GB</li>
<li>SATA 120GB</li>
</ul>
<p>Bisa menangani load sebagai berikut</p>
<ul>
<li>1000 concurrent user</li>
<li>masing-masing user melakukan login, entri transaksi dua kali, melihat report, dan kemudian mengunduh laporan dalam format PDF</li>
</ul>
<p>Dan bisa menampung data untuk</p>
<ul>
<li>10.000 registered user</li>
<li>1.000.000 transaksi</li>
</ul>
<p>Perlu diperhatikan bahwa jumlah user yang bisa ditangani tentu berbeda tergantung kompleksitas aplikasinya. Jadi jangan bertanya di forum seperti ini</p>
<blockquote>
<p>Aplikasi saya kok boros resource ya? Padahal sudah pakai Xeon Dual Core dan RAM 8GB, tapi cuma bisa menghandle 1000 user?</p>
</blockquote>
<p>Pertanyaan di atas tidak bisa dijawab tanpa tahu persis isi perut aplikasinya. Hanya arsitek aplikasinya yang bisa menjawab, karena dia yang tahu apa bisnis proses yang dijalankan, apa teknologi yang digunakan, bagaimana teknik query database, bagaimana strategi caching, dan sebagainya. Bahkan mungkin saja angka 1000 user itu sudah merupakan prestasi yang luar biasa.</p>
<p>Jadi sebetulnya, dari proses test tersebut kita cuma butuh kesimpulan sebagai berikut</p>
<blockquote>
<p>Xeon Dual Core + RAM 8GB + Disk 120GB = 1000 concurrent user + 10.000 registered user + 1.000.000 transaksi</p>
</blockquote>
<p>Dari kesimpulan di atas, kita bisa interpolasi ke requirement dari sisi bisnis. Kalau bisnisnya ingin melayani 2000 concurrent user, 20.000 registered user, dan 2.000.000 transaksi, tentu kita harus beli minimal Xeon Quad Core, RAM 16GB, dan Disk 300GB.</p>
<p>Jangan lupa, selain spesifikasi minimal kita juga bisa mengeluarkan spesifikasi yang ideal / direkomendasikan. Soalnya bagian finance dan purchasing biasanya suka kalau mereka punya alternatif. Pastikan saja spesifikasi minimal yang kita berikan sudah mempunyai cadangan kapasitas. Biasanya saya berikan tiga level spesifikasi:</p>
<ul>
<li>minimal</li>
<li>rekomendasi</li>
<li>ideal</li>
</ul>
<p>Tentu saja sebenarnya sudah saya markup dari perhitungan sebenarnya, sehingga spesifikasi minimal sebenarnya adalah spesifikasi rekomendasi, spesifikasi rekomendasi sebenarnya adalah spesifikasi ideal, dan yang saya tulis sebagai spesifikasi ideal sebenarnya adalah spesifikasi <em>all you can eat</em>. Jangan heran kalau nanti isinya adalah <a href="http://en.wikipedia.org/wiki/NonStop">NonStop Server</a></p>
<h3 id="skema-deployment">Skema Deployment</h3>
<p>Beberapa requirement bisnis menuntut skema deployment tertentu. Contohnya, kita mendapatkan requirement seperti ini</p>
<blockquote>
<p>Sistem harus berjalan dengan SLA 99.9%. Bila terjadi downtime, sistem cadangan harus aktif paling lambat dalam waktu 1 jam. Sistem cadangan harus menyimpan data transaksi minimal satu hari kerja sebelumnya.</p>
</blockquote>
<p>Requirement bisnis tersebut harus kita terjemahkan dulu menjadi angka yang bisa kita pahami secara teknis.</p>
<p>Pertama, kita <a href="http://www.joshualyman.com/2010/12/sla-uptime-guarantees-in-minutes-and-hours/">terjemahkan SLA tersebut</a>. Angka 99.9% artinya dalam setahun sistem cuma boleh mati maksimal 525.6 menit atau 8.76 jam. Perlu diperhatikan bahwa angka ini sudah termasuk proses terencana (scheduled downtime) seperti misalnya upgrade versi aplikasi, deployment karena fixing bug, ataupun server crash karena berbagai alasan.</p>
<p>Kedua, dari requirement di atas, kita bisa menyimpulkan adanya kebutuhan sistem cadangan. Sistem cadangan ini biasanya harus berjauhan dari sistem utama (geographic diversity).</p>
<p>Ketiga, dari waktu aktivasi sistem cadangan dan sinkronisasi data, kita dapat menentukan strategi replication antar kedua sistem. Delay data satu hari artinya kita bisa melakukan sinkronisasi data dengan file transfer biasa (FTP atau Rsync).</p>
<p>Aktivasi 1 jam artinya kita bisa menyimpan data dalam bentuk dump database karena waktu 1 jam memungkinkan kita untuk melakukan proses restore.</p>
<p>Bila data harus sinkron secara hampir real-time dan aktivasi di bawah 10 menit, maka kita harus mempertimbangkan strategi sinkronisasi yang berbeda (misalnya di level disk menggunakan DRBD).</p>
<h3 id="kebutuhan-jaringan">Kebutuhan Jaringan</h3>
<p>Lakukan logika yang sama untuk kebutuhan jaringan. Interpolasikan antara requirement bisnis dengan karakteristik aplikasi. Nanti kita akan dapatkan kesimpulan misalnya seperti ini:</p>
<ul>
<li>untuk menangani 2000 transaksi per detik</li>
<li>dibutuhkan koneksi uplink dengan bandwidth 1Mbps dengan maksimum delay 0.1ms</li>
</ul>
<p>Dengan contoh kasus di atas, setidaknya ada tiga segmen jaringan yang kita harus pikirkan:</p>
<ul>
<li>dari sistem utama / cadangan ke end user</li>
<li>dari sistem utama ke sistem cadangan (untuk keperluan sinkronisasi dan replikasi data)</li>
<li>di internal sistem (utama dan cadangan)</li>
</ul>
<p>Kebutuhan jaringan menuju end user dapat dikalkulasi dengan mengkombinasikan:</p>
<ul>
<li>jumlah concurrent user</li>
<li>apa yang dilakukan masing-masing user di aplikasi</li>
<li>besar data yang ditransmisikan untuk masing-masing aktifitas user</li>
</ul>
<p>Kebutuhan jaringan dari sistem utama ke sistem cadangan bisa dikalkulasi dengan mempertimbangkan:</p>
<ul>
<li>delay sinkronisasi data. Bila data harus sinkron secara real time, maka bandwidth harus besar dan delay harus kecil supaya data bisa ditransfer dengan lancar dan cepat</li>
<li>delay aktivasi sistem cadangan. Bila aktivasinya tidak harus cepat, kita bisa mentransfer data dalam bentuk dump yang membutuhkan proses restore sebelum sistem aktif. Tapi kalau aktivasi harus cepat, maka data harus ditransfer dalam bentuk yang lebih instan seperti byte-per-byte blok dalam disk. Ini butuh jaringan dengan spesifikasi yang tinggi</li>
</ul>
<p>Kebutuhan jaringan di internal sistem juga harus dipertimbangkan, terutama antara application server dan database server. Walaupun demikian, karena sifatnya jaringan internal, maka kita memiliki keleluasaan untuk memasang perangkat gigabit.</p>
<h3 id="infrastruktur-pendukung">Infrastruktur Pendukung</h3>
<p>Hasil perhitungan terhadap kebutuhan hardware, skema deployment, dan jaringan bisa kita gunakan untuk menghitung berapa ruangan, rak, listrik yang kita butuhkan untuk menjalankan sekian banyak server dan perangkat jaringan. Sesuai dengan requirement SLA, kita juga mungkin perlu menyewa lokasi kedua untuk sistem cadangan.</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Singkatnya, untuk membuat Capacity Planning kita perlu mengetahui:</p>
<ul>
<li>requirement bisnis sekarang dan beberapa tahun ke depan</li>
<li>profil dan karakteristik aplikasi yang kita bangun</li>
</ul>
<p>Dengan data tersebut, kita bisa menghitung:</p>
<ul>
<li>apa saja perangkat yang dibutuhkan</li>
<li>berapa jumlah masing-masing perangkat</li>
<li>bagaimana spesifikasi masing-masing perangkat</li>
<li>apa saja infrastruktur untuk menjalankan semua perangkat tersebut</li>
<li>sampai kapan perangkat bisa digunakan</li>
<li>pada titik mana kita harus upgrade / menambah perangkat</li>
</ul>
<p>Demikianlah sekilas tentang Capacity Planning, semoga bermanfaat.</p>
Memahami Authentication2014-07-26T23:49:00+07:00https://software.endy.muhardin.com/java/memahami-authentication<p>Menjelang penghujung bulan Ramadhan, ada yang <a href="https://groups.yahoo.com/neo/groups/jug-indonesia/conversations/messages/92685">bertanya di milis JUG</a>. Alhamdulillah, ada kesempatan bersedekah di detik-detik terakhir bulan puasa ;) Karena pertanyaannya membutuhkan jawaban yang cukup panjang, baiklah kita jawab di blog saja.</p>
<p>Berikut pertanyaannya.</p>
<blockquote>
<p>Saya pernah menggunakan Spring Security Framework. Tapi kalau cuma pakai, tanpa ngerti konsep dasar dan cara kerjanya dasarnya sakit kepala juga akhirnya kalau semua terjadi secara automagically.</p>
</blockquote>
<blockquote>
<p>Jadi kalau murni tanpa menggunakan Framework apapun :</p>
</blockquote>
<blockquote>
<ul>
<li>Bagaimana membuat proses loginnya? dari beberapa artikel yang saya baca (saya baru tahu) katanya login username dan passwordnya dimasukan kedalam HTTP Header.</li>
</ul>
</blockquote>
<blockquote>
<p>Contohnya :</p>
</blockquote>
<blockquote>
<p><code class="language-plaintext highlighter-rouge">Authorization:Basic c2VzdWF0dUB5YWhvby5jb206YmlzbWlsbGFo</code></p>
</blockquote>
<blockquote>
<p>dimana <code class="language-plaintext highlighter-rouge">c2VzdWF0dUB5YWhvby5jb206YmlzbWlsbGFo</code> adalah Base64 encoding dari <code class="language-plaintext highlighter-rouge">sesuatu@yahoo.com:bismillah</code> (username:password)</p>
</blockquote>
<blockquote>
<p>benarkah begitu?</p>
</blockquote>
<blockquote>
<ul>
<li>Bagaimana server mengenali client jika kita menggunakan method GET? (ini pertanyaan konsep sangat mendasar sekali). Mungkin jawabannya ya dengan token.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Tapi ditaruh dimana? apakah dengan saya tempelkan saja di URL? misal</li>
</ul>
</blockquote>
<blockquote>
<p><code class="language-plaintext highlighter-rouge">GET /thewebapp/category/324?token=abcde1234</code></p>
</blockquote>
<blockquote>
<p>atau saya masukkan dalam HTTP header? tapi dengan header apa?</p>
</blockquote>
<p>Dan berikut jawaban saya</p>
<!--more-->
<p>Yang ditanyakan ini dinamakan proses authentication, artinya memastikan bahwa seseorang/sesuatu yang datang (principal) sesuai dengan yang diakuinya.
Konkretnya, kalau ada request datang dari user <code class="language-plaintext highlighter-rouge">endy</code>, harus diverifikasi bahwa yang request benar-benar <code class="language-plaintext highlighter-rouge">endy</code> yang asli.</p>
<p>Bagaimana caranya? Biasanya dengan cara menanyakan sesuatu yang hanya dimiliki oleh principal. Sesuatu ini disebut dengan istilah credential.</p>
<p><a name="principal-credential"></a></p>
<h2 id="principal-dan-credential">Principal dan Credential</h2>
<p>Principal dan credential itu macam-macam bentuknya. Yang paling umum ditemui : principal = username, credential = password.</p>
<p>Ini kalau principalnya manusia. Bagaimana kalau principalnya aplikasi lain? Ada beberapa credential yang biasa digunakan, misalnya:</p>
<ul>
<li>digital certificate</li>
<li>secret shared key</li>
<li>private key</li>
<li>dsb</li>
</ul>
<p>Bahkan walaupun usernya manusia, credential tidak cuma password. Bisa juga random number yang digenerate menggunakan algoritma. Dikenal juga dengan istilah <a href="http://en.wikipedia.org/wiki/One-time_password">One Time Password (OTP)</a>. Generatornya bisa software dan bisa juga hardware. Contoh hardwarenya seperti token internet banking yang biasa kita gunakan</p>
<p><a href="https://farm4.staticflickr.com/3670/12361262404_50b4ab650e_z.jpg"><img src="https://farm4.staticflickr.com/3670/12361262404_50b4ab650e_z.jpg" alt="Foto" /></a></p>
<p>Sedangkan contoh software yang populer antara lain <a href="http://code.google.com/p/google-authenticator/">Google Authenticator</a> dan <a href="https://www.wikidsystems.com/community-edition">WiKiD</a></p>
<p>Mekanisme OTP ini biasanya digunakan bersama-sama dengan password, sehingga disebut juga dengan <a href="http://en.wikipedia.org/wiki/Two_factor_authentication">2 Factor Authentication</a>. Kenapa 2 Factor? Karena untuk bisa mengakses aplikasi, kita akan dimintai 2 hal : <em>what you know</em> dan <em>what you have</em>. What you know adalah password, karena hanya kita yang tahu. What you have adalah generator token, karena tiap generator akan menghasilkan nilai yang berbeda.</p>
<p>Kalau mau lebih canggih lagi, kita bisa menambahkan berbagai lain sehingga menjadi <a href="http://en.wikipedia.org/wiki/Multi-factor_authentication">Multi Factor Authentication</a> yaitu <em>who you are</em> atau biometrik (scan mata, sidik jari, DNA, suara, dsb)</p>
<p><a name="protokol-authentication"></a></p>
<h2 id="protokol-authentication">Protokol Authentication</h2>
<p>Setelah kita bicara principal dan credential, selanjutnya bicara protokol. Bagaimana cara principal dan credential disajikan?</p>
<p>Ada beberapa cara:</p>
<ul>
<li>form login. Aplikasi yang mau diakses menampilkan form, principal mengisi credential. Setelah itu diperiksa valid atau tidak</li>
<li>basic authentication. Informasi principal dan credential diencode (bukan encrypt ya) dengan algoritma Base64, kemudian ditaruh di HTTP Header.</li>
<li>digest. Principal mengirim data yang diencode menggunakan credential. Kemudian data ini dikirim ke aplikasi yang mau diakses. Aplikasi (dengan mekanisme yang sama) membuat data yang sama dan diencode dengan proses yang sama. Kalau klop, berarti credential yang tersimpan di aplikasi sama dengan yang dimiliki principal.</li>
<li><a href="http://en.wikipedia.org/wiki/Mutual_authentication">Mutual / Two-way SSL</a> Handshake. Principal mengirim digital signature yang kemudian akan divalidasi oleh aplikasi yang ingin diakses</li>
</ul>
<p>Untuk alasan efisiensi dan keamanan, proses authentication biasanya dilakukan sekali saja. Setelah proses selesai, principal akan diberikan token yang memiliki masa berlaku singkat. Request selanjutnya akan menggunakan token ini. Dengan demikian, credential tidak terus menerus dikirim melalui jaringan sehingga meningkatkan resiko dicuri.</p>
<p>Jadi, token ini harus disimpan oleh principal dan disertakan pada setiap request.</p>
<p>Bagaimana menyimpannya? Ada beberapa cara :</p>
<ul>
<li>cookie</li>
<li>browser storage (ada local storage dan session storage)</li>
</ul>
<p>Bagaimana menyertakannya pada tiap request? Ada beberapa cara:</p>
<ul>
<li>ditaruh di URL (teknik URL rewrite). Request yang tadinya <code class="language-plaintext highlighter-rouge">http://aplikasi.com/halo</code> menjadi <code class="language-plaintext highlighter-rouge">http://aplikasi.com/halo?token=abcd</code></li>
<li>dikirim dalam cookie</li>
<li>ditaruh di form data (biasanya menggunakan <code class="language-plaintext highlighter-rouge">input type=hidden</code>)</li>
</ul>
<p>Jangan lupa bahwa semua informasi yang berlalu-lintas ini harus berjalan dalam jalur yang aman. Selalu <a href="http://software.endy.muhardin.com/aplikasi/apa-itu-ssl/">gunakan HTTPS/SSL</a>.</p>
<p><a name="verifikasi-credential"></a></p>
<h2 id="verifikasi-credential">Verifikasi Credential</h2>
<p>Selanjutnya, kita bicara di sisi aplikasi yang menerima request. Dia harus melakukan verifikasi principal dan credential. Bagaimana cara verifikasinya? Ada beberapa cara:</p>
<ul>
<li>in memory data. Username dan password dihardcode di aplikasi. Request authentication akan dicek ke data dalam memori ini. Berguna kalau aplikasinya kecil atau masih dalam fase development.</li>
<li>database relasional. Simpan username dan password dalam database. Password biasanya dihash (bukan encrypt ya) menggunakan berbagai algoritma seperti MD5, SHA, atau BCrypt. Lengkapi dengan salt untuk menambah keamanan.</li>
<li>database hirarkis (LDAP). Sama seperti database relasional, bentuknya saja yang berbeda.</li>
<li>delegasikan ke pihak ketiga (single sign on)</li>
</ul>
<p>Di jaman sekarang ini, mekanisme authentication dengan cara delegasi seperti ini semakin ngetren. Ada beberapa protokol standar yang populer :</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/OpenID">OpenID</a></li>
<li><a href="http://en.wikipedia.org/wiki/OAuth">OAuth</a> (versi 1 dan versi 2). Contoh siklus authentication dengan OAuth versi 2 bisa dilihat di <a href="https://github.com/endymuhardin/belajar-springoauth2">contoh yang saya buatkan di Github</a>. Kalau ada waktu lowong, di lain hari akan saya jelaskan dengan lebih rinci di sini.</li>
</ul>
<p>Selain itu, ada juga protokol proprietary yang disediakan berbagai aplikasi single-sign-on, seperti:</p>
<ul>
<li><a href="http://www.jasig.org/cas">Jasig CAS</a></li>
<li><a href="http://en.wikipedia.org/wiki/Oracle_Identity_Management">Oracle Identity Management</a></li>
<li><a href="http://en.wikipedia.org/wiki/Active_Directory">Microsoft Active Directory</a></li>
<li>dsb</li>
</ul>
<p>Lalu apa kaitannya dengan Spring Security?</p>
<p>Singkat saja. Intinya semua hal di atas harus kita implementasikan dalam aplikasi yang kita buat. Kalau kita pakai Spring Security, sebagian besar (90%) sudah dibuatkan, tinggal pasang saja. 10% sisanya sudah disediakan titik-titik extension di mana kita bisa buat implementasinya dan pasang sehingga klop dengan 90% yang sudah disediakan Spring Security.</p>
Desain Skema Database2014-05-06T10:42:00+07:00https://software.endy.muhardin.com/java/desain-skema-database<p>Pada <a href="http://software.endy.muhardin.com/manajemen/tahapan-membuat-aplikasi/">artikel sebelumnya</a>, kita telah belajar bagaimana memulai membuat aplikasi, yaitu dengan cara:</p>
<ul>
<li>membuat daftar fitur</li>
<li>membuat UI Mockup</li>
</ul>
<p>Kedua langkah di atas membantu kita untuk memvisualisasikan apa yang ada di imajinasi kita menjadi bentuk yang bisa dilihat oleh banyak orang. Ada beberapa keuntungan dari proses visualisasi ini, yaitu:</p>
<ul>
<li>mengurangi kepusingan, karena apa yang sebelumnya cuma dibayangkan, sekarang terlihat bentuknya</li>
<li>menunjukkan kekurangan dalam imajinasi kita. Seringkali saya mendapati ada informasi yang kurang sehingga harus ditanyakan lagi ke user. Kali lain, saya mendapati ternyata perpindahan antar screen tidak sistematis sehingga membingungkan user. Atau saya terlalu kompleks mendesain tampilan yang seharusnya bisa lebih sederhana.</li>
<li>menjadi bahan diskusi dengan user, menghindarkan kita dari membuat sesuatu yang tidak dibutuhkan user.</li>
<li>sebagai pedoman kita untuk merancang skema database</li>
</ul>
<p>Poin terakhir tersebut akan menjadi bahasan kita pada artikel kali ini.</p>
<p>Saya tidak akan membahas tentang teori normal form. Seharusnya para mahasiswa informatika sudah mempelajarinya di mata kuliah Basis Data yang biasanya diberikan di semester 3. Silahkan ambil lagi mata kuliah tersebut di tahun/semester depan kalau ternyata Anda ketiduran waktu dosennya menerangkan. Sedangkan pembaca yang tidak kuliah di informatika (seperti saya), bisa baca-baca <a href="http://en.wikipedia.org/wiki/Database_normalization">referensi dari Wikipedia</a> atau <a href="http://www.bkent.net/Doc/simple5.htm">tutorial di sini</a>.</p>
<p>Artikel ini ditulis dengan asumsi pembaca sudah paham apa itu normal form. Silahkan belajar dulu kalau belum paham, baru kembali lagi ke sini.</p>
<!--more-->
<p>Sebelum kita mulai mendesain skema database, ada satu hal penting yang kita perlu camkan baik-baik</p>
<blockquote>
<p>Hasil desain, baik itu skema database, class diagram, flowchart, dan lainnya, bukanlah merupakan hasil sekali pukul. Dia adalah suatu produk yang harus terus disempurnakan bahkan sampai setelah aplikasinya go live.</p>
</blockquote>
<p>Dengan demikian, adalah lumrah kalau kita merevisi skema database berkali-kali. Setelah go-live saja bisa berubah, apalagi pada saat development.</p>
<h2 id="desain-tabel-master">Desain tabel master</h2>
<p>Dalam mendesain skema database, kita mulai dulu dengan yang paling sederhana, yaitu tabel master. Tabel master biasanya tidak berelasi ke tabel lain. Kalaupun ada relasi, biasanya relasi dengan tabel master lainnya. Contohnya, master pegawai berelasi ke master kabupaten, kecamatan, kodepos, tingkat pendidikan, dan lainnya. Karena dia sederhana, maka dalam membuat aplikasi, biasanya tabel-tabel master ini didefinisikan duluan.</p>
<p>Ada pola yang biasa saya gunakan dalam mendesain tabel database (baik master data maupun transaksi), yaitu:</p>
<ul>
<li>harus ada primary key yang berupa surrogate key</li>
<li>karena pakai surrogate key, maka kandidat natural key menjadi kolom biasa yang diberikan <em>unique constraint</em></li>
<li>buat kolom lain sesuai yang ada di UI mockup</li>
<li>untuk menyimpan nilai uang, gunakan DECIMAL, bukan FLOAT atau DOUBLE, supaya kita bisa mengontrol pembulatan dan tingkat presisi</li>
</ul>
<blockquote>
<p>Whoa, apa itu surrogate key dan natural key?</p>
</blockquote>
<p>Mari kita bahas.</p>
<h2 id="menentukan-primary-key">Menentukan Primary Key</h2>
<p>Misalnya kita akan menyimpan data pegawai dalam tabel <code class="language-plaintext highlighter-rouge">m_pegawai</code>. Informasi pegawai memiliki beberapa data sebagai berikut:</p>
<ul>
<li>nip (nomor induk pegawai), dijamin unik karena tiap pegawai pasti beda.</li>
<li>nama, tidak unik. Agus, Budi, Cahyo biasanya banyak yang pakai.</li>
<li>email, juga unik. Tidak mungkin satu email dipake rame-rame.</li>
</ul>
<p>Dari ketiga data di atas, hanya dua yang bisa jadi primary key yaitu <code class="language-plaintext highlighter-rouge">nip</code> dan <code class="language-plaintext highlighter-rouge">email</code>, karena secara proses bisnis, nilainya harus unik di tiap record pegawai. Kedua data ini, bila kita pilih salah satunya menjadi primary key, maka disebut dengan istilah <a href="http://en.wikipedia.org/wiki/Natural_key">natural key</a>, artinya primary key yang diambil dari data yang memang sudah ada.</p>
<p>Selain natural key, kita juga bisa membuat data lain yang tidak berkaitan dengan proses bisnis. Misalnya menambahkan primary key berupa kolom <code class="language-plaintext highlighter-rouge">id</code> di tabel pegawai yang nilainya kita konfigurasi supaya bertambah satu (increment) tiap ada record baru yang dimasukkan ke tabel. Kolom <code class="language-plaintext highlighter-rouge">id</code> ini disebut dengan istilah <a href="http://en.wikipedia.org/wiki/Surrogate_key">surrogate key</a>.</p>
<h3 id="natural-vs-surrogate">Natural vs Surrogate</h3>
<p>Keuntungan kita menggunakan natural key adalah, datanya sudah ada. Dengan demikian kita tidak perlu menambah kapasitas penyimpanan untuk satu kolom khusus primary key.</p>
<p>Kerugian dari natural key adalah nilainya bisa berubah. Sebagai contoh, bila perusahaan merger, maka nilai <code class="language-plaintext highlighter-rouge">nip</code> bisa dipastikan akan menyesuaikan dengan perusahaan lain yang dimerger. Bisa formatnya ikut perusahaan lain tersebut, dan bisa juga membuat format baru sesuai kesepakatan bersama.</p>
<blockquote>
<p>Kalau gitu, email saja yang jadi primary key.</p>
</blockquote>
<p>Email justru lebih sering berubah. Gak perlu merger, cukup ada layanan yang menawarkan kapasitas lebih besar, orang langsung bikin akun email baru.</p>
<blockquote>
<p>Lalu memangnya kenapa kalau berubah?</p>
</blockquote>
<p>Yang namanya primary key, akan digunakan di tabel lain sebagai <em>foreign key</em>. Apalagi untuk tabel master, pasti dia akan direlasikan di berbagai tabel lainnya. Jika primary key berubah, maka semua tabel lain harus ikut diupdate isi foreign key-nya. Ini akan membutuhkan <em>locking</em> yang luas karena harus meliputi banyak tabel sekaligus. Semakin luas <em>locking</em>, semakin lemot performance, karena proses lain harus antri mendapatkan <em>lock</em>.</p>
<p>Masalah kedua, penggunaan natural key cenderung akan mengarah pada penggunaan <a href="http://en.wikipedia.org/wiki/Compound_key">compound/composite key</a>, yaitu primary key yang terdiri dari dua atau lebih kolom. Ini akan lebih merepotkan lagi, karena foreign key dari composite key juga akan terdiri dari dua/lebih kolom.</p>
<p>Kekurangan dari surrogate key adalah ada tambahan space harddisk untuk menyimpan data kolom tambahan. Juga tambahan space untuk membuat index dari natural key (yang seharusnya otomatis terindex bila dia menjadi primary key).</p>
<blockquote>
<p>Lalu, sebaiknya pakai natural key atau surrogate key?</p>
</blockquote>
<p>Saya selalu pakai surrogate key. Sebabnya karena space harddisk semakin lama semakin murah, sedangkan locking problem dan composite key akan memakan mandays programmer yang semakin lama semakin mahal.</p>
<h3 id="strategi-key-generator">Strategi Key Generator</h3>
<p>Karena nilai dari surrogate key tidak berhubungan dengan data yang diwakilinya, maka kita bisa melakukan optimasi dalam pemilihan algoritma untuk menghasilkan nilai baru. Ada beberapa strategi yang biasa digunakan:</p>
<ul>
<li>Auto increment / sequence. Cara ini disebut dengan berbagai istilah, misalnya AUTO_INCREMENT (MySQL), SEQUENCE (Oracle), SERIAL (PostgreSQL), IDENTITY (MS SQL Server), AutoNumber (MS Access), dan lainnnya. Intinya adalah penggunaan tipe data integer yang nilainya bertambah terus.</li>
<li><a href="http://en.wikipedia.org/wiki/Universally_Unique_Identifier">UUID</a> / <a href="http://en.wikipedia.org/wiki/Globally_Unique_Identifier">GUID</a>. Tipe datanya 32 karakter alfanumerik, bisa diwakili di database dengan CHAR atau VARCHAR.</li>
</ul>
<blockquote>
<p>Pilih yang mana?</p>
</blockquote>
<p>Saya biasa pakai UUID, karena dia dijamin unik siapapun yang menjalankan generator. Ini akan sangat berguna dalam skenario database terdistribusi seperti ini.</p>
<p>Misalnya database kita split menjadi database kantor pusat dan kantor cabang. Masing-masing cabang insert data transaksi ke database yang ada di kantor cabang. Pada sore hari, database cabang diupload ke kantor pusat dan digabungkan ke database pusat, bersama-sama dengan data transaksi dari cabang lain.</p>
<p>Bila kita menggunakan sequence, akan terjadi duplikasi primary key, karena masing-masing cabang memulai sequence dari angka 1. Cabang A akan punya data 1 - 100, demikian juga cabang-cabang lain.</p>
<p>Lain halnya bila kita menggunakan UUID. Nilai yang dihasilkan oleh cabang A dijamin berbeda dengan nilai yang dihasilkan cabang B. Dengan demikian, kita tinggal insert data cabang A dan cabang B ke database pusat tanpa ada primary key yang bentrokan.</p>
<h2 id="tabel-transaksi">Tabel Transaksi</h2>
<p>Sekarang kita beranjak ke tabel untuk menyimpan data transaksi.</p>
<p>Pola yang umum dipakai adalah relasi <code class="language-plaintext highlighter-rouge">master-detail-header</code> seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/-B_icVAf77dg/U2b9zeQw8FI/AAAAAAAAFvk/D7q35jK7U7Y/w915-h221-no/skema-master-header-detail.png"><img src="https://lh3.googleusercontent.com/-B_icVAf77dg/U2b9zeQw8FI/AAAAAAAAFvk/D7q35jK7U7Y/w915-h221-no/skema-master-header-detail.png" alt="Foto" /></a></p>
<p>Pola ini bisa kita gunakan untuk aplikasi perpustakaan seperti ini</p>
<p><a href="https://lh6.googleusercontent.com/-k1DUCU0X4K8/U2b9zUQq0yI/AAAAAAAAFvo/7saKCOClC0U/w876-h484-no/skema-perpustakaan.png"><img src="https://lh6.googleusercontent.com/-k1DUCU0X4K8/U2b9zUQq0yI/AAAAAAAAFvo/7saKCOClC0U/w876-h484-no/skema-perpustakaan.png" alt="Foto" /></a></p>
<p>Ataupun untuk aplikasi bengkel, dimana detailnya ada dua (part dan jasa)</p>
<p><a href="https://lh6.googleusercontent.com/-GDtsnS1_9gE/U2b9zQlqgII/AAAAAAAAFvs/afAswqUErzg/w873-h500-no/skema-bengkel.png"><img src="https://lh6.googleusercontent.com/-GDtsnS1_9gE/U2b9zQlqgII/AAAAAAAAFvs/afAswqUErzg/w873-h500-no/skema-bengkel.png" alt="Foto" /></a></p>
<p>Pola yang sama bisa digunakan dalam pencatatan transaksi lain, misalnya:</p>
<ul>
<li>jurnal akuntansi : jurnal_header berisi tanggal dan keterangan, jurnal_detail berisi id_akun, debet/kredit, dan nilai nominal</li>
<li>transaksi di minimarket : transaksi_header berisi tanggal dan nama kasir, transaksi_detail berisi id_produk, qty, harga_satuan</li>
<li>keluar/masuk barang di gudang : transaksi_header berisi tanggal, transaksi_detail berisi id_barang, qty</li>
<li>dan sebagainya</li>
</ul>
<h3 id="identifying-relationship">Identifying Relationship</h3>
<p>Pembaca yang teliti akan melihat bahwa garis penghubung relasi antara <code class="language-plaintext highlighter-rouge">header</code> dan <code class="language-plaintext highlighter-rouge">detail</code> berbeda dengan <code class="language-plaintext highlighter-rouge">detail</code> dan <code class="language-plaintext highlighter-rouge">master</code>. <code class="language-plaintext highlighter-rouge">Header-detail</code> garisnya menyambung sedangkan <code class="language-plaintext highlighter-rouge">detail-master</code> putus-putus. Garis tersambung disebut dengan istilah <a href="http://stackoverflow.com/a/762994">identifying relationship</a>, yaitu hubungan parent-child. Artinya, kalau induknya (record di tabel header) dihapus, maka anaknya (record di tabel detail) juga harus dihapus karena sudah tidak relevan lagi.</p>
<p>Berbeda dengan master-detail. Kalau masternya dihapus, belum tentu data transaksinya juga ikut dihapus. Bisa jadi relasinya cuma diset menjadi <code class="language-plaintext highlighter-rouge">NULL</code> saja, tapi datanya tetap ada dan tetap bisa dihitung.</p>
<h2 id="tabel-akumulasi">Tabel Akumulasi</h2>
<p>Terakhir, kita bahas tentang tabel akumulasi. Tabel ini sebetulnya tidak wajib dibuat. Kita membuat tabel ini terutama untuk alasan performance.</p>
<p>Misalnya kita ingin menampilkan data jumlah stok buku di satu hari tertentu. Atau kita ingin mencari berapa jumlah barang yang tersisa untuk satu jenis barang tertentu.</p>
<p>Tanpa tabel akumulasi, kita harus mencari dulu saldo awalnya, kemudian kita melakukan penjumlahan berapa penambahan dan pengurangan selama periode bisnis berjalan. Kalau bisnisnya baru jalan 3 hari tidak masalah, tapi bagaimana kalau sudah jalan 3 tahun? Tentu ada jutaan record yang harus dijumlahkan oleh database dalam sekali request. Bagaimana kalau usernya banyak?</p>
<p>Untuk mengatasinya, kita buat tabel untuk akumulasi. Berikut skemanya</p>
<p><a href="https://lh6.googleusercontent.com/-ZKiD_6GBDig/U2hUO-HearI/AAAAAAAAFwM/SJgzkIeJU9E/w619-h625-no/skema-akumulasi.png"><img src="https://lh6.googleusercontent.com/-ZKiD_6GBDig/U2hUO-HearI/AAAAAAAAFwM/SJgzkIeJU9E/w619-h625-no/skema-akumulasi.png" alt="Foto" /></a></p>
<p>Penjelasan kolomnya sebagai berikut:</p>
<ul>
<li>id : surrogate key</li>
<li>id_xx : relasi ke tabel master barang/akun/produk/dsb</li>
<li>tanggal : supaya memudahkan, kita buat satu record per hari</li>
<li>saldo_awal : nilai di awal hari</li>
<li>debet : penambahan nilai dalam hari itu</li>
<li>kredit : pengurangan nilai dalam hari itu</li>
</ul>
<blockquote>
<p>Kok gak ada saldo akhir?</p>
</blockquote>
<p>Ya kan tinggal dihitung saja. Saldo akhir = saldo awal + debet - kredit. Gampang kan?</p>
<p>Ada dua pilihan metode dalam mengisi tabel akumulasi ini:</p>
<ol>
<li>Diupdate setiap insert transaksi. Jika menggunakan metode ini, jangan lupa menggunakan <a href="http://software.endy.muhardin.com/java/database-transaction/">database transaction</a> untuk menjaga konsistensi data.</li>
<li>Diupdate secara batch di akhir hari. Jika menggunakan metode ini, jangan lupa memasang lock supaya tidak ada orang yang insert record baru pada saat kita sedang menghitung akumulasi. Biasanya kalau trafiknya tinggi dan harus selalu online (seperti ATM bank), perlu menggunakan teknik shadow balance supaya transaksi bisa terus jalan walaupun batch update sedang berjalan.</li>
</ol>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Poin penting yang bisa diambil dari artikel ini:</p>
<ul>
<li>natural key vs surrogate key</li>
<li>pola master-detail-header</li>
<li>pola master-akumulasi</li>
</ul>
<p>Skema database dibuat menggunakan aplikasi <a href="http://www.mysql.com/products/workbench/">MySQL Workbench</a>. Desain skema pada contoh di atas bisa <a href="../../downloads/skema-database-aplikasi.mwb">diunduh di sini</a></p>
<p>Demikianlah pola desain skema database yang biasa saya gunakan untuk membuat aplikasi bisnis. Pada posting selanjutnya, kita akan bahas apa saja class dan package yang harus kita buat pada waktu coding. Stay tuned.</p>
Tahapan Membuat Aplikasi2014-04-22T23:51:00+07:00https://software.endy.muhardin.com/manajemen/tahapan-membuat-aplikasi<p>Seperti sudah dijelaskan pada <a href="http://software.endy.muhardin.com/manajemen/aplikasi-prakarya-vs-aplikasi-production/">artikel sebelumnya</a>, anjuran saya buat mereka yang ingin mahir pemrograman adalah</p>
<blockquote>
<p>Buatlah aplikasi production barang 5-10 buah. Insya Allah setelah itu Anda akan mahir.</p>
</blockquote>
<p>Setelah kita menguasai dasar-dasar pemrograman seperti:</p>
<ul>
<li>konsep variabel dan tipe data</li>
<li>control flow (looping, branching, jumps)</li>
<li>pengelompokan (function, class, package)</li>
</ul>
<p>tidak serta merta kita bisa langsung membuat aplikasi. Analoginya, walaupun kita mahir menggunakan pengolah dokumen seperti MS Word atau Open Office, tidak berarti kita bisa menulis seri lanjutan dari <a href="http://en.wikipedia.org/wiki/Harry_Potter_and_the_Deathly_Hallows">Harry Potter and the Deathly Hallows</a>.</p>
<p>Inilah sebabnya banyak pemegang sertifikasi pemrograman seperti Oracle Certified Java Programmer yang tidak bisa membuat aplikasi, walaupun mendapatkan nilai yang tinggi.</p>
<p>Ada berbagai macam aplikasi, diantaranya:</p>
<ul>
<li>aplikasi bisnis, yaitu aplikasi yang membantu pencatatan kegiatan bisnis. Contohnya aplikasi akunting, purchasing, perpustakaan, mini-market, bank, payment gateway, dan sebagainya</li>
<li>aplikasi sistem, yaitu aplikasi yang memungkinkan berbagai perangkat bekerja sama. Contohnya sistem operasi, device driver, embedded system, dan lainnya</li>
<li>dan berbagai kategori aplikasi lainnya.</li>
</ul>
<p>Artikel kali ini akan kita batasi hanya untuk membahas aplikasi bisnis. Mengapa demikian? Karena aplikasi bisnis adalah genre aplikasi yang paling banyak dibuat orang. Sebagai ilustrasi, kita hanya mengenal beberapa sistem operasi populer, yaitu Microsoft Windows, Linux, MacOS. Bandingkan dengan aplikasi bisnis, untuk proses akuntansi saja berapa ribu aplikasi yang tersedia. Bahkan walaupun sudah ada yang gratis dan lengkap, tetap saja banyak perusahaan yang minta dibuatkan aplikasi sejenis. Jadi dari sisi komersial dan ketersediaan lapangan kerja, pembuatan aplikasi bisnis tetaplah pangsa pasar terbesar.</p>
<p>Pada artikel ini, kita akan membahas proses berpikir dan tahapan mulai dari penjabaran aplikasi yang akan dibuat, sampai menjadi skema database dan desain aplikasi berorientasi objek (object-oriented).</p>
<!--more-->
<p>Berikut adalah tahapan yang biasa saya lakukan dalam mengembangkan aplikasi:</p>
<ol>
<li>Membuat deskripsi aplikasi. Berisi penjelasan tentang apa fungsi utama dari aplikasi yang akan kita kembangkan. Tuliskan juga siapa saja user yang terlibat, dan garis besar dari proses bisnis yang akan disediakan oleh aplikasi.</li>
<li>Membuat daftar fitur</li>
<li>Untuk proyek komersial, daftar fitur ini akan kita terjemahkan menjadi estimasi biaya dan waktu. Bila Anda adalah programmer dan tidak berurusan dengan masalah finansial, silahkan skip langkah ini. Bila Anda ingin tahu lebih jauh, silahkan baca <a href="http://software.endy.muhardin.com/manajemen/estimasi-proyek-software/">artikel yang khusus membahas masalah estimasi ini</a>.</li>
<li>Membuat UI Mockup</li>
<li>Membuat skema database</li>
<li>Membuat daftar class dan relasi antar class</li>
<li>Membuat kerangka aplikasi dalam bentuk source code</li>
<li>Membuat detail implementasi source code sampai selesai</li>
<li>User Acceptance Test, baik internal maupun external (kalau ada client/customer yang akan mengetes)</li>
</ol>
<p>Agar lebih mudah dipahami, kita akan menggunakan studi kasus aplikasi perpustakaan. Kita pilih aplikasi ini karena semua orang sudah paham proses bisnisnya. Kalau saya pilih aplikasi seperti misalnya payment gateway, banking, gps tracking, dan aplikasi-aplikasi ‘bergengsi’ lainnya, saya khawatir proses bisnisnya tidak dipahami oleh mayoritas pembaca. Tidak semua orang paham apa yang terjadi pada saat nasabah membuka rekening bank.</p>
<p>Ok mari kita mulai.</p>
<h2 id="penjelasan-aplikasi">Penjelasan Aplikasi</h2>
<p>Penjelasan atau deskripsi aplikasi adalah hal terpenting dalam pembuatan aplikasi. Bila kita membuatnya dengan benar, maka pekerjaan kita di tahap selanjutnya akan mudah. Sebagaimana halnya semua skill yang lain, kemahiran kita dalam mengarang penjelasan aplikasi ini hanya bisa didapatkan dari latihan. Berikut contoh penjelasan aplikasi perpustakaan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Aplikasi perpustakaan ini, sesuai namanya berguna
untuk mengelola kegiatan operasional perpustakaan.
Aplikasi ini akan digunakan oleh penjaga perpustakaan untuk:
* entri data anggota
* entri data buku
* entri peminjaman buku
* entri pengembalian buku
Selain itu, anggota juga bisa menggunakan aplikasi ini untuk:
* mencari buku dengan kriteria tertentu
(misalnya judul, pengarang, topik, dan sebagainya)
* melihat ketersediaan buku
* mengetahui lokasi penyimpanan buku
* booking buku secara online dari rumah agar tidak kehabisan
</code></pre></div></div>
<p>Dari penjelasan aplikasi di atas, kita bisa menentukan apa saja usernya dan apa saja fitur dari aplikasi yang akan kita buat. Mari kita lanjutkan ke tahap selanjutnya.</p>
<h2 id="daftar-fitur">Daftar Fitur</h2>
<p>Aplikasi bisnis (aplikasi yang dibuat untuk melayani operasional bisnis) biasanya memiliki fitur yang dapat diklasifikasikan menjadi beberapa kategori:</p>
<ul>
<li>master data : data referensi yang jarang berubah. Misalnya kelurahan, kode pos, tingkat pendidikan, dan lainnya. Untuk mengubah data master biasanya tidak ada proses bisnis khusus. User langsung saja membuka datanya, kemudian mengedit isinya, dan tekan tombol save.</li>
<li>transaksi : fitur pencatatan transaksi bisnis. Di sini biasanya ada berbagai aturan bisnis yang rumit seperti penambahan/pengurangan stok, perhitungan pajak, dan lain sebagainya</li>
<li>laporan : rekap dan aggregasi dari data transaksi. Umumnya ada fitur pencarian berdasarkan tanggal, jumlah total, dan sejenisnya. Biasanya juga ada fitur export ke berbagai format file (CSV, XLS, PDF)</li>
<li>personalisasi / konfigurasi : fitur untuk memudahkan user. Contohnya daftar menu favorit, mengganti email / no handphone, tema warna, dan lain sebagainya</li>
<li>utilitas / administrasi : fitur untuk pemeliharaan aplikasi. Misalnya backup/restore.</li>
</ul>
<p>Untuk membuat daftar fitur biasanya kita mulai dari fitur transaksi. Setelah itu, kita tentukan master data apa saja yang dibutuhkan tiap transaksi. Selanjutnya, laporan-laporan yang dapat dibuat dari berbagai transaksi. Terakhir baru kita identifikasi fitur personalisasi dan utilitas.</p>
<p>Berikut adalah daftar fitur aplikasi perpustakaan:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* Transaksi
* Registrasi Anggota
* Berhenti jadi anggota
* Peminjaman
* Pengembalian
* Booking
* Buku baru (beli, hibah, dsb)
* Penghapusan buku (hilang, rusak, dsb)
* Master Data
* Buku
* Anggota
* User
* Group
* Permission
* Laporan
* Rekap Buku Masuk per Periode
* Rekap Buku Dihapus per Periode
* Rekap Peminjaman per Periode
* Rekap Pengembalian per Periode
* Histori Peminjaman dan Pengembalian per Anggota
* Rekap Jumlah Buku
* Rekap Anggota
* Personalisasi
* Ganti Password
* Ganti Email
* Ganti HP
* Pengaturan Notifikasi
* Utilitas
* Backup
* Restore
* Import Data Anggota
* Import Data Buku
</code></pre></div></div>
<p>Selanjutnya, berbekal daftar fitur di atas, kita bisa mulai membuat UI Mockup.</p>
<h2 id="ui-mockup">UI Mockup</h2>
<p>UI Mockup merupakan solusi murah meriah dan cepat untuk sinkronisasi pemahaman kita dan pemahaman user tentang aplikasi yang akan dibuat. Kalau kita tidak membuat UI mockup, kesalah pahaman kita baru diketahui setelah aplikasinya selesai dibuat. Ini akan memboroskan waktu dan tenaga.</p>
<p>Sebagai gambaran, untuk membuat UI mockup, satu orang bisa menyelesaikan 1 screen dalam waktu 5 menit. Sedangkan untuk mengimplementasikan screen tersebut dalam aplikasi, belum tentu selesai dalam 5 hari. Padahal screen tersebut belum tentu sesuai dengan kebutuhan user. Kan sayang kalau sudah dicoding 5 hari ternyata salah.</p>
<p>Untuk membuat UI Mockup, kita dapat menggunakan aplikasi <a href="http://pencil.evolus.vn/">Pencil</a> yang gratis dan mudah digunakan. Aplikasi Pencil ini memiliki <a href="http://pencil.evolus.vn/Stencils-Templates.html">berbagai template (stencil)</a> sesuai aplikasi yang akan kita buat, misalnya aplikasi desktop Windows, aplikasi desktop Linux, aplikasi web, aplikasi Android, dan sebagainya.</p>
<p>Ada beberapa pola umum dalam membuat aplikasi bisnis, yaitu:</p>
<ul>
<li>tampilan list</li>
<li>form entri sederhana</li>
<li>form entri header-detail</li>
<li>lookup dialog</li>
</ul>
<p>Untuk menyederhanakan pembahasan, kita hanya akan buat sebagian kecil saja screen dari aplikasi perpustakaan. Soalnya kalau kita buat semua, bisa-bisa artikel ini jadi satu buku sendiri ;)</p>
<p>Berikut daftar screen yang akan kita buat:</p>
<ul>
<li>Entri Anggota</li>
<li>Entri Peminjaman</li>
<li>Histori Peminjaman per Anggota</li>
</ul>
<p>File Pencil dari UI Mockup di artikel ini bisa diunduh <a href="../../downloads/aplikasi-perpustakaan.ep">di sini</a></p>
<h3 id="entri-anggota">Entri Anggota</h3>
<p>Berikut adalah tampilan screen entri anggota.</p>
<p><a href="https://lh4.googleusercontent.com/-1hcFb859pOI/U1acWK5Y8YI/AAAAAAAAFqw/KxhM_jXmybE/w771-h474-no/entri_anggota.png"><img src="https://lh4.googleusercontent.com/-1hcFb859pOI/U1acWK5Y8YI/AAAAAAAAFqw/KxhM_jXmybE/w771-h474-no/entri_anggota.png" alt="Foto" /></a></p>
<p>Ada beberapa hal yang harus kita perhatikan di sini, yaitu:</p>
<ul>
<li>validasi input harus dilakukan, baik di sisi server maupun di sisi client.</li>
<li>pesan error harus ditampilkan secara jelas</li>
</ul>
<p>Pembahasan detailnya sudah kita jabarkan di <a href="http://software.endy.muhardin.com/manajemen/aplikasi-prakarya-vs-aplikasi-production/">artikel sebelumnya</a>, jadi tidak perlu kita ulangi lagi di sini.</p>
<p>Form entri anggota ini termasuk form entri sederhana. Pola ini biasanya dipakai untuk entri data master, seperti misalnya:</p>
<ul>
<li>Form Entri Buku</li>
<li>Form Entri User</li>
<li>dan lainnya</li>
</ul>
<p>Selanjutnya, kita masuk ke form entri header detail yang lebih kompleks</p>
<h3 id="entri-peminjaman">Entri Peminjaman</h3>
<p>Hampir semua kegiatan transaksi dalam aplikasi bisnis bisa direpresentasikan dengan model header-detail. Apa itu model header-detail?</p>
<blockquote>
<p>Satu transaksi akan terdiri dari satu record header dan banyak record detail. Record detail memiliki relasi foreign key ke record header.</p>
</blockquote>
<p>Sebagai contoh, kita lihat transaksi peminjaman. Satu kali pinjam, anggota bisa membawa banyak buku. Bagaimana representasinya?</p>
<p>Salah satu teknik yang sering saya gunakan dalam analisa dan desain aplikasi adalah <code class="language-plaintext highlighter-rouge">visualisasi dengan ilustrasi</code>. Kita buat contoh kasus yang menceritakan transaksi bisnis yang terjadi lengkap dengan datanya. Dengan menggunakan kasus konkrit, kita tidak perlu pusing membayangkan sesuatu yang abstrak. Ini adalah trik yang biasanya tidak diketahui programmer pemula sehingga mereka bingung gak tau mana ujung mana pangkal. Berikut contoh kasusnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Pada hari Senin 21 April 2014 pukul 13:45
anggota bernama `Endy Muhardin`
dilayani oleh petugas bernama `Anton Raharja`
meminjam tiga buku sebagai berikut:
* Java Desktop
* Konsep dan Penggunaan Subversion
* Pengenalan PHP
</code></pre></div></div>
<blockquote>
<p>Bagaimana menyimpan data transaksi di atas dalam database?</p>
</blockquote>
<p>Pertama, kita tentukan dulu headernya. Header adalah satu record yang menjadi induk dari transaksi di atas. Kita buat tabel <code class="language-plaintext highlighter-rouge">peminjaman_header</code> sebagai berikut</p>
<table>
<thead>
<tr>
<th>id</th>
<th>waktu</th>
<th>id_petugas</th>
<th>id_anggota</th>
</tr>
</thead>
<tbody>
<tr>
<td>p001</td>
<td>2014-04-21 13:45:00</td>
<td>u001</td>
<td>u100</td>
</tr>
</tbody>
</table>
<p>User dengan id <code class="language-plaintext highlighter-rouge">u001</code> adalah relasi foreign key ke tabel <code class="language-plaintext highlighter-rouge">master_user</code> yang berisi record <code class="language-plaintext highlighter-rouge">Anton Raharja</code>, sedangkan user dengan id <code class="language-plaintext highlighter-rouge">u100</code> adalah relasi foreign key ke tabel <code class="language-plaintext highlighter-rouge">master_user</code> yang berisi record <code class="language-plaintext highlighter-rouge">Endy Muhardin</code>.</p>
<p>Sesuai dengan prinsip <code class="language-plaintext highlighter-rouge">visualisasi dengan ilustrasi</code>, dalam mendesain tabel kita isikan juga datanya. Itu sebabnya tabel di atas tidak saya tampilkan seperti biasanya orang mendesain database seperti ini:</p>
<table>
<thead>
<tr>
<th>Nama Kolom</th>
<th>Tipe Data</th>
<th>Atribut</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>VARCHAR(32)</td>
<td>PRIMARY KEY</td>
</tr>
<tr>
<td>waktu</td>
<td>DATETIME</td>
<td>NOT NULL</td>
</tr>
<tr>
<td>id_petugas</td>
<td>VARCHAR(32)</td>
<td>FOREIGN KEY, NOT NULL</td>
</tr>
<tr>
<td>id_anggota</td>
<td>VARCHAR(32)</td>
<td>FOREIGN KEY, NOT NULL</td>
</tr>
</tbody>
</table>
<p>Karena kalau kita tampilkan seperti di atas, kita terpaksa harus membayangkan sesuatu yang abstrak.</p>
<blockquote>
<p>Lalu apakah saya harus menggunakan visualisasi dengan ilustrasi seumur hidup saya?</p>
</blockquote>
<p>Tentu tidak, nanti setelah membuat aplikasi production yang ke-sekian puluh, visualisasi ini sudah <em>terinstal</em> dalam sistem operasi otak kita. Demikian juga dengan prinsip-prinsip normalisasi database, desain berorientasi objek, pola header-detail, dan sebagainya. Pada tahap ini, kita akan bisa coding sambil usernya menceritakan requirement aplikasi yang dia inginkan.</p>
<p>Tapi itu nanti, ada waktunya. Bila Anda masih membaca artikel ini, berarti belum waktunya ;) Sementara ini, jangan malas membuat ilustrasi. Bahkan mahasiswa saya di mata kuliah Pemrograman 1 harus membuat ilustrasi di spreadsheet pada waktu looping menampilkan bilangan prima.</p>
<p>Baiklah, kita kembali ke masalah header-detail. Kita sudah mencatat waktu transaksi, petugas, dan anggota. Sekarang kita akan mencatat buku apa saja yang dipinjam. Untuk itu, kita membuat tabel <code class="language-plaintext highlighter-rouge">peminjaman_detail</code> sebagai berikut</p>
<table>
<thead>
<tr>
<th>id</th>
<th>id_header</th>
<th>id_buku</th>
</tr>
</thead>
<tbody>
<tr>
<td>pd001</td>
<td>p001</td>
<td>b001</td>
</tr>
<tr>
<td>pd002</td>
<td>p001</td>
<td>b010</td>
</tr>
<tr>
<td>pd003</td>
<td>p001</td>
<td>b100</td>
</tr>
</tbody>
</table>
<p>Seperti kita lihat pada tabel di atas, ketiga record berelasi ke record <code class="language-plaintext highlighter-rouge">p001</code> di tabel <code class="language-plaintext highlighter-rouge">peminjaman_header</code> yaitu pada kolom <code class="language-plaintext highlighter-rouge">id_header</code>. Kolom <code class="language-plaintext highlighter-rouge">id_buku</code> tentu kita sudah bisa menebak bahwa dia mengacu ke tabel <code class="language-plaintext highlighter-rouge">master_buku</code>.</p>
<p>Untuk memasukkan data header dan detail, berikut adalah screen entri transaksinya:</p>
<p><a href="https://lh5.googleusercontent.com/-X3QeTFVgI04/U1adOQ65DlI/AAAAAAAAFrw/whTHBn110mw/w774-h588-no/entri_peminjaman.png"><img src="https://lh5.googleusercontent.com/-X3QeTFVgI04/U1adOQ65DlI/AAAAAAAAFrw/whTHBn110mw/w774-h588-no/entri_peminjaman.png" alt="Foto" /></a></p>
<p>Berbeda dengan form input sederhana, form entri header-detail memiliki tabel di dalamnya. Tabel inilah yang memuat data detail, sedangkan form inputnya menampung data header.</p>
<p>Karena adanya input ke dalam tabel ini, maka kita membutuhkan screen tambahan, yaitu dialog. Dialog ini akan muncul begitu kita tekan tombol <code class="language-plaintext highlighter-rouge">Tambah</code>. Setelah muncul, kita pilih satu atau lebih (tergantung proses bisnis) record dalam dialog. Kemudian setelah selesai, dialognya akan tertutup dan record yang kita pilih masuk ke dalam tabel.</p>
<p>Berikut tampilan dialog untuk memilih buku yang akan dipinjam. Karena yang dipinjam bisa banyak buku, maka kita sediakan checkbox agar user tidak perlu membuka dialog berkali-kali.</p>
<p><a href="https://lh3.googleusercontent.com/-lPHMx7VATXM/U1acVTFCtYI/AAAAAAAAFqg/xQYotfMd4Ns/w804-h420-no/dialog_tambah_buku.png"><img src="https://lh3.googleusercontent.com/-lPHMx7VATXM/U1acVTFCtYI/AAAAAAAAFqg/xQYotfMd4Ns/w804-h420-no/dialog_tambah_buku.png" alt="Foto" /></a></p>
<p>Penggunaan dialog tidak terbatas hanya untuk entri detail. Dialog juga sebaiknya kita gunakan untuk data yang bersifat pilihan, kalau jumlah pilihannya lebih dari sepuluh record. Sebagai contoh, pada saat memilih anggota yang akan meminjam buku, tidak mungkin kita gunakan dropdown combo. Kalau anggotanya ada ribuan, usernya pasti kesulitan memilihnya. Apalagi dalam dropdown combo, informasi yang bisa ditampilkan terbatas. Paling hanya nama saja. Akan lebih mudah kalau kita sediakan dialog seperti ini</p>
<p><a href="https://lh5.googleusercontent.com/-ZgeS9pGZH-Y/U1acVOVr9HI/AAAAAAAAFqc/5hmU6uXWMAY/w768-h414-no/dialog_cari_anggota.png"><img src="https://lh5.googleusercontent.com/-ZgeS9pGZH-Y/U1acVOVr9HI/AAAAAAAAFqc/5hmU6uXWMAY/w768-h414-no/dialog_cari_anggota.png" alt="Foto" /></a></p>
<p>Pola header-detail ini berlaku untuk sebagian besar fitur transaksi. Mari kita ambil contoh lain, transaksi belanja di minimarket misalnya. Screennya seperti ini</p>
<p><a href="https://lh3.googleusercontent.com/LQsOccTBJ6_nHzLHirLdybAv8CMZ_gl6lw3WGIEfpX8=w1280-no"><img src="https://lh3.googleusercontent.com/LQsOccTBJ6_nHzLHirLdybAv8CMZ_gl6lw3WGIEfpX8=w1280-no" alt="Foto" /></a></p>
<p>Pada transaksi minimarket, ada tambahan data yaitu <code class="language-plaintext highlighter-rouge">quantity</code> untuk mengakomodasi orang beli permen 5 buah. Dialog tambah detailnya juga harus memiliki input field untuk <code class="language-plaintext highlighter-rouge">quantity</code>. Dan karena jumlah produknya ribuan, kita juga harus sediakan lookup dialog sekali lagi untuk memilih produk.</p>
<p>Dialog tambah detail seperti ini bentuknya</p>
<p><a href="https://lh5.googleusercontent.com/-fBQUV0F944w/U1acX5wjQ2I/AAAAAAAAFrM/5ZatJYsudL8/w666-h285-no/tambah_detail_minimarket.png"><img src="https://lh5.googleusercontent.com/-fBQUV0F944w/U1acX5wjQ2I/AAAAAAAAFrM/5ZatJYsudL8/w666-h285-no/tambah_detail_minimarket.png" alt="Foto" /></a></p>
<p>Di situ ada tombol <code class="language-plaintext highlighter-rouge">Cari</code> yang bila diklik akan menampilkan dialog pilih produk seperti ini</p>
<p><a href="https://lh6.googleusercontent.com/-lmToPoVQHZ0/U1acWmCjTsI/AAAAAAAAFq4/3mFYG1Cpz1o/w762-h402-no/dialog_cari_produk.png"><img src="https://lh6.googleusercontent.com/-lmToPoVQHZ0/U1acWmCjTsI/AAAAAAAAFq4/3mFYG1Cpz1o/w762-h402-no/dialog_cari_produk.png" alt="Foto" /></a></p>
<p>Jadi, untuk mengentri satu detail, user akan membuka dialog dua tingkat: dialog entri quantity dan dialog pilih produk.</p>
<p>Adapun tabel di database cukup kita tambahkan satu field quantity seperti ini</p>
<table>
<thead>
<tr>
<th>id</th>
<th>id_header</th>
<th>id_produk</th>
<th>qty</th>
</tr>
</thead>
<tbody>
<tr>
<td>pd001</td>
<td>p001</td>
<td>b001</td>
<td>1</td>
</tr>
<tr>
<td>pd002</td>
<td>p001</td>
<td>b010</td>
<td>5</td>
</tr>
<tr>
<td>pd003</td>
<td>p001</td>
<td>b100</td>
<td>12</td>
</tr>
</tbody>
</table>
<p>Bisa juga detailnya lebih dari satu, seperti screen transaksi servis motor/mobil. Ada komponen sparepart dan ada komponen jasa. Begini bentuknya</p>
<p><a href="https://lh5.googleusercontent.com/-AFCYrZEZlWE/U1acY31B9wI/AAAAAAAAFrY/W3O2-B4mtyY/w598-h623-no/transaksi_servis_motor.png"><img src="https://lh5.googleusercontent.com/-AFCYrZEZlWE/U1acY31B9wI/AAAAAAAAFrY/W3O2-B4mtyY/w598-h623-no/transaksi_servis_motor.png" alt="Foto" /></a></p>
<p>Skema databasenya berarti terdiri dari 3 tabel: <code class="language-plaintext highlighter-rouge">servis_header</code>, <code class="language-plaintext highlighter-rouge">servis_detail_sparepart</code>, dan <code class="language-plaintext highlighter-rouge">servis_detail_jasa</code>.</p>
<p>Selanjutnya, kita buat screen untuk rekap/laporan.</p>
<h3 id="histori-peminjaman-per-anggota">Histori Peminjaman per Anggota</h3>
<p>Di screen laporan, inputan yang paling sering dipakai adalah periode. Inputannya berupa tanggal mulai dan tanggal sampai. Contohnya, kita ingin menampilkan histori peminjaman buku salah satu anggota untuk 3 bulan terakhir, screennya seperti ini</p>
<p><a href="https://lh5.googleusercontent.com/-UPULY4jYmVo/U1acXrfIiJI/AAAAAAAAFrE/IOenvvN8AzM/w867-h486-no/rekap_peminjaman.png"><img src="https://lh5.googleusercontent.com/-UPULY4jYmVo/U1acXrfIiJI/AAAAAAAAFrE/IOenvvN8AzM/w867-h486-no/rekap_peminjaman.png" alt="Foto" /></a></p>
<p>Di situ kita lihat juga tombol <code class="language-plaintext highlighter-rouge">Cari</code>. Itu berfungsi untuk menampilkan dialog pencarian anggota, sama seperti pada screen transaksi peminjaman di atas.</p>
<p>Fitur yang biasa ada di tampilan rekap ini antara lain:</p>
<ul>
<li>pencarian transaksi dengan lebih spesifik. Misalnya menyebutkan buku yang dipinjam, jenis transaksi, nilai/jumlah tertentu, dan sebagainya</li>
<li>paging bila datanya banyak, seperti telah dijelaskan di artikel terdahulu.</li>
<li>sorting, biasanya diimplementasikan melalui kepala tabel yang bisa diklik. Misalnya kita ingin urut tanggal (baik transaksi baru di atas maupun sebaliknya), buku, dan lainnya.</li>
</ul>
<h2 id="langkah-selanjutnya">Langkah Selanjutnya</h2>
<p>Setelah UI Mockup kita selesaikan, adakan sesi review bersama user untuk mendapatkan feedback. Setelah sepakat dengan user (untuk proyek komersial bisa juga disertai tanda tangan berita acara kesepakatan) barulah kita lanjutkan ke tahap desain skema database dan desain class/objek.</p>
<p>Banyak orang menyalah-artikan tahap desain dengan dokumen desain. Saya melakukan tahap desain aplikasi, tapi belum tentu membuat dokumen desain.</p>
<p>Proses desain kita lakukan dengan tujuan agar bisa melihat <em>the big picture</em>. Pada waktu mulai coding, perhatian kita akan terpusat ke satu function/method atau class saja. Ibaratnya masuk ke hutan, jarak pandang kita terbatas. Sangat mudah untuk tersesat, kita sudah implement fitur tertentu, ternyata tidak kompatibel dengan fitur selanjutnya.</p>
<p>Selain itu, dengan melihat <em>the big picture</em>, kita jadi tahu fitur mana yang penting dan mana yang <em>nice to have</em>. Jangan sampai kita menghabiskan waktu untuk mengimplementasikan fungsionalitas yang bersifat kosmetik saja, padahal fitur utama belum selesai.</p>
<p>Walaupun demikian, saya tidak membuat dokumen desain. Mengapa begitu? Jawabannya bisa disimak <a href="http://software.endy.muhardin.com/manajemen/dokumentasi-project/">di artikel ini</a>.</p>
<p>Proses mendesain skema database dan skema class/objek akan kita bahas pada <a href="https://software.endy.muhardin.com/java/desain-skema-database/">artikel selanjutnya</a>. Stay tuned.</p>
Aplikasi Prakarya vs Aplikasi Production2014-04-15T22:00:00+07:00https://software.endy.muhardin.com/manajemen/aplikasi-prakarya-vs-aplikasi-production<p>Salah satu pertanyaan yang sering ditanyakan di forum programmer adalah</p>
<blockquote>
<p>Saya ingin belajar bahasa pemrograman [isi bahasa yang Anda sukai di sini], bagaimana ya caranya supaya bisa mahir?</p>
</blockquote>
<p>Dan jawaban saya selalu sama</p>
<blockquote>
<p>Coba buatlah aplikasi berkualitas production barang 5 - 10 kali, setelah itu insya Allah akan mahir.</p>
</blockquote>
<p>Jawaban di atas memang singkat, tapi jalan untuk menuju kesana <a href="http://endy.muhardin.com/education/tahapan-belajar/">sungguh tidak mudah dan tidak singkat</a>. Apalagi untuk pemula, setidaknya ada dua pertanyaan besar:</p>
<ol>
<li>Apa sih aplikasi production itu?</li>
<li>Bagaimana cara membuat aplikasi?</li>
</ol>
<p>Pertanyaan pertama akan kita jawab pada artikel ini. Sedangkan pertanyaan kedua akan kita jawab pada artikel selanjutnya.</p>
<!--more-->
<p>Dalam siklus pembuatan aplikasi, biasanya ada beberapa tahapan rilis:</p>
<ol>
<li>Development : ini adalah work in progress yang sedang dikerjakan programmer. Aplikasinya belum selesai sehingga wajar kalau banyak errornya.</li>
<li>Testing : programmer sudah mengetes bagian-bagian yang penting. Tinggal bug pada kasus-kasus dan kondisi <em>‘ajaib’</em> yang biasanya cuma bisa ditemukan oleh tester yang handal. Testing biasanya juga ada berbagai tahapan, mulai dari internal tester sampai ke tester dari client/end user.</li>
<li>Production : aplikasi sudah digunakan sehari-hari oleh end user dan berjalan lancar tanpa ada bug yang signifikan (show-stopper).</li>
</ol>
<p>Lebih detail mengenai tahapan rilis bisa dibaca di <a href="http://software.endy.muhardin.com/manajemen/release-management/">artikel terdahulu tentang Release Management</a>.</p>
<p>Tahapan di atas biasanya dijalankan di perusahaan. Di lingkungan akademik (kampus, SMK), saya punya satu kategori lagi yang dinamakan <strong>Aplikasi Prakarya</strong>. Wah apa lagi tuh <strong>Aplikasi Prakarya</strong>??</p>
<blockquote>
<p>Aplikasi Prakarya adalah aplikasi yang cuma bisa digunakan untuk tugas kuliah ataupun skripsi. Itupun cuma bisa lulus kalau dosennya bermurah hati. Aplikasi prakarya ini tidak akan mungkin digunakan oleh end-user, walaupun daftar fiturnya sama dengan aplikasi production.</p>
</blockquote>
<p>Masih belum paham? Baiklah kita coba ilustrasikan dengan cerita.</p>
<p>Pada suatu hari, saya menyuruh mahasiswa saya membuat aplikasi. Mereka saat ini sedang semester 4. Seperti umumnya kampus di negeri kita ini, mahasiswa semester 4 belum pernah membuat aplikasi utuh. Jam terbangnya paling hanya beberapa looping/branching atau paling maksimal beberapa function/method untuk belajar algoritma. Saya beri tugas:</p>
<blockquote>
<p>Coba buat aplikasi user management. Tidak perlu full coding, cukup UI mockup (rancangan screen) saja.</p>
</blockquote>
<p>Aplikasi user management adalah aplikasi yang paling mendasar. Apapun aplikasi yang ingin kita buat, pasti ada modul ini. Fiturnya pun tidak rumit-rumit. Cukup insert-update-delete-display data user saja. Untuk menyederhanakan masalah, tidak perlulah kita buat group dan permission.</p>
<p>Source file UI mockup yang saya buat di artikel ini bisa diunduh <a href="../../downloads/prakarya-production.ep">di sini</a> dan dibuka menggunakan <a href="http://pencil.evolus.vn/">aplikasi Pencil</a>.</p>
<p>Nah kita lihat evolusi dari aplikasi prakarya menjadi aplikasi production. Kita mulai dengan menampilkan data user.</p>
<h2 id="menampilkan-data">Menampilkan Data</h2>
<p>Inilah screen daftar user yang dipresentasikan ke saya</p>
<p><a href="https://lh4.googleusercontent.com/-25Yeh5ay_RY/U01LMUEObTI/AAAAAAAAFpA/p45UmrhH9-4/w888-h408-no/tabelprakarya01.png"><img src="https://lh4.googleusercontent.com/-25Yeh5ay_RY/U01LMUEObTI/AAAAAAAAFpA/p45UmrhH9-4/w888-h408-no/tabelprakarya01.png" alt="Foto" /></a></p>
<blockquote>
<p>Bagaimana menurut Anda? Apakah Anda melihat ada masalah pada screen di atas? Apa masalahnya?</p>
</blockquote>
<p>Masalahnya ada di jumlah data. Aplikasi prakarya biasanya dites menggunakan 1 - 5 baris data saja. Tentu saja tidak ada masalah yang terlihat kalau datanya cuma 1 - 5 baris. Nah, bagaimana kalau tidak ada data? Biasanya begini jadinya.</p>
<p><a href="https://lh3.googleusercontent.com/-mvMdRuXasxU/U01LNOysKkI/AAAAAAAAFpQ/wkv2gyBpzCs/w882-h363-no/tabelprakarya03.png"><img src="https://lh3.googleusercontent.com/-mvMdRuXasxU/U01LNOysKkI/AAAAAAAAFpQ/wkv2gyBpzCs/w882-h363-no/tabelprakarya03.png" alt="Foto" /></a></p>
<p>Ini menunjukkan programmer malas. Padahal dengan menambah beberapa baris <code class="language-plaintext highlighter-rouge">if-else</code>, tampilan di atas bisa terkesan lebih <em>‘serius’</em> seperti ini</p>
<p><a href="https://lh5.googleusercontent.com/-g1HzK09B3W0/U01LOCrimhI/AAAAAAAAFpg/yXw_NJX3OIQ/w654-h306-no/tabelproduction02.png"><img src="https://lh5.googleusercontent.com/-g1HzK09B3W0/U01LOCrimhI/AAAAAAAAFpg/yXw_NJX3OIQ/w654-h306-no/tabelproduction02.png" alt="Foto" /></a></p>
<p>Ok, kita sudah handle kasus tidak ada data. Nah sekarang, pada waktu dipakai di kondisi riil, berapa datanya? Misalnya kita membuat aplikasi perpustakaan untuk kampus. Berapa jumlah anggotanya? Apakah 1-5 saja? Tentu tidak mungkin. Kisaran datanya antara ratusan sampai puluhan ribu tergantung ukuran kampus. Belum lagi kalau perpustakaannya menerima anggota masyarakat umum.</p>
<p>Dengan data ratusan/ribuan, screen di atas akan mengalami masalah:</p>
<ul>
<li>data tentu ditarik dengan query <code class="language-plaintext highlighter-rouge">select * from tabel_user</code>. Kalau satu user datanya 1KB, maka untuk 10 ribu user 10MB harus dikirim dari database server ke webserver, dan kemudian dari webserver ke browser.</li>
<li>asumsikan koneksi internet user 1 MB/s, maka dia harus menunggu 10 detik sampai daftar user bisa dia lihat. Pakai GPRS? Nah, silahkan kembali lagi besok sore ;)</li>
<li>bila ada 10 concurrent user (user yang mengakses aplikasi secara bersamaan), maka kita harus siapkan bandwidth 100 MB/s di sisi server hanya untuk menampilkan daftar user.</li>
</ul>
<blockquote>
<p>Lalu bagaimana solusinya?</p>
</blockquote>
<p>Solusi pertama, paging. Kita bagi data menjadi beberapa halaman. Toh user juga tidak mungkin melihat 10 ribu data sekaligus. Bisa-bisa stres dia kayak caleg kalah pemilu.</p>
<p><a href="https://lh6.googleusercontent.com/-CgyzCMA-Foc/U01LMwcZhfI/AAAAAAAAFpM/8PCtzho72xA/w852-h462-no/tabelprakarya02.png"><img src="https://lh6.googleusercontent.com/-CgyzCMA-Foc/U01LMwcZhfI/AAAAAAAAFpM/8PCtzho72xA/w852-h462-no/tabelprakarya02.png" alt="Foto" /></a></p>
<blockquote>
<p>Selesai?</p>
</blockquote>
<p>Belum. Untuk 10 ribu data, bila satu halaman isinya 20, maka ada 500 halaman. Mana ada orang mau klik satu persatu halamannya. Jadi bagaimana? Ya tentu harus bisa dicari.</p>
<p><a href="https://lh3.googleusercontent.com/-OGKJDR3nnSk/U01LN9OAzmI/AAAAAAAAFpc/Z5mYYDj-O9s/w888-h465-no/tabelproduction01.png"><img src="https://lh3.googleusercontent.com/-OGKJDR3nnSk/U01LN9OAzmI/AAAAAAAAFpc/Z5mYYDj-O9s/w888-h465-no/tabelproduction01.png" alt="Foto" /></a></p>
<p>Nah, dari sini kita sudah bisa membedakan antara aplikasi prakarya dan aplikasi production. Aplikasi prakarya tidak dites secara menyeluruh. Dengan 1-5 data saja sudah puas, cukuplah untuk dapat A kalau dosennya murah hati. Tapi aplikasi production tidak bisa seperti itu kalau aplikasinya mau dipakai orang. Kita harus tes dengan 0 data dan 10 ribu data.</p>
<p>Selanjutnya, mari kita lihat form input data.</p>
<h2 id="entri-data">Entri Data</h2>
<p>Inilah screen iterasi pertama</p>
<p><a href="https://lh4.googleusercontent.com/-gwFv3Yp1hMY/U01LKyx9OcI/AAAAAAAAFoo/pjiHEIjgRYc/w687-h510-no/formprakarya01.png"><img src="https://lh4.googleusercontent.com/-gwFv3Yp1hMY/U01LKyx9OcI/AAAAAAAAFoo/pjiHEIjgRYc/w687-h510-no/formprakarya01.png" alt="Foto" /></a></p>
<blockquote>
<p>Apa masalahnya?</p>
</blockquote>
<p>Untuk input data, ada dua hal yang wajib dilakukan dengan benar:</p>
<ul>
<li>validasi</li>
<li>menampilkan pesan kesalahan</li>
</ul>
<p>Mari kita perbaiki. Ini hasil revisinya</p>
<p><a href="https://lh5.googleusercontent.com/-ptivii5-b3I/U01LLWa7lZI/AAAAAAAAFo0/PMTqrd83k9c/w917-h487-no/formprakarya02.png"><img src="https://lh5.googleusercontent.com/-ptivii5-b3I/U01LLWa7lZI/AAAAAAAAFo0/PMTqrd83k9c/w917-h487-no/formprakarya02.png" alt="Foto" /></a></p>
<p>Sudah ada pesan error pada screen di atas. Tapi kalau berhenti sampai di sini, usernya bisa marah-marah, soalnya nilai yang sudah dia isikan hilang semua. Harusnya tetap ditampilkan, sehingga dia cukup edit yang salah saja. Tidak perlu entri ulang semuanya. Revisi lagi menjadi seperti ini</p>
<p><a href="https://lh6.googleusercontent.com/-pzWfTxTPSaY/U01LLyVPxcI/AAAAAAAAFo4/Qo19dzxO8uM/w900-h459-no/formproduction.png"><img src="https://lh6.googleusercontent.com/-pzWfTxTPSaY/U01LLyVPxcI/AAAAAAAAFo4/Qo19dzxO8uM/w900-h459-no/formproduction.png" alt="Foto" /></a></p>
<p>Nah, jauh lebih baik.</p>
<p>Selain masalah di atas, ada beberapa hal lagi yang perlu kita perhatikan, yaitu:</p>
<ul>
<li>validasi server-side vs client-side</li>
<li>pemilihan jenis komponen</li>
</ul>
<blockquote>
<p>Mana validasi yang harus kita buat? Apakah di sisi server atau di sisi client?</p>
</blockquote>
<p>Validasi yang wajib adalah di sisi server. Kita harus cek semua data yang dikirim client. Kita juga tidak boleh hanya mengandalkan validasi di sisi client, karena berada di luar kendali kita. Bisa saja JavaScript dimatikan, form input di-bypass <a href="http://software.endy.muhardin.com/java/mendebug-aplikasi-ajax/">menggunakan aplikasi debugger</a>, dan berbagai teknik lainnya. Baik secara sengaja, ataupun karena keterbatasan perangkat di sisi client (misalnya browsernya versi jadul).</p>
<blockquote>
<p>Kalau begitu, buat apa kita buat lagi validasi di sisi client? Buang-buang energi saja.</p>
</blockquote>
<p>Validasi di sisi client tujuannya supaya user lebih nyaman. Akan lebih enak kalau setelah kursor pindah, data yang barusan diinput langsung ketahuan benar/salahnya. Daripada isi seluruh form, submit, dan ternyata ada error. Sama seperti tampilan tabel tanpa data di atas. Aplikasi yang hal-hal detailnya diurus dengan baik akan terkesan lebih profesional.</p>
<p>Pemilihan jenis komponen akan kita bahas pada artikel selanjutnya.</p>
<h2 id="pesan-error">Pesan Error</h2>
<p>Kita semua tentu kenal Rambo. Dia bisa survive dimanapun dia diterjunkan dan seburuk apapun perlakuan orang kepadanya.</p>
<p><a href="https://lh3.googleusercontent.com/-kWEJAXJQqiY/U01NMQkQ7_I/AAAAAAAAFp0/099uScfrhP0/w768-h576-no/rambo-wallpaper-800x600.jpg"><img src="https://lh3.googleusercontent.com/-kWEJAXJQqiY/U01NMQkQ7_I/AAAAAAAAFp0/099uScfrhP0/w768-h576-no/rambo-wallpaper-800x600.jpg" alt="Foto" /></a></p>
<p><em>Fotonya Rambo diambil <a href="http://wallpaperswide.com/rambo-wallpapers.html">dari sini</a></em></p>
<p>Aplikasi production juga demikian. Apapun yang diinput user, dia bisa handle dengan baik. Harddisk penuh? Bandwidth lemot? RAM overload? Semua bisa ditangani dengan baik.</p>
<blockquote>
<p>Bagaimana caranya? Apakah aplikasi tidak boleh error?</p>
</blockquote>
<p>Tentu tidak. Mana ada yang sempurna di dunia ini. Kita mengatasi kondisi buruk dengan error handling yang baik. Kalau ada sesuatu yang tidak sesuai kondisi yang dibutuhkan aplikasi, kita tampilkan pesan error yang informatif.</p>
<p>Mari kita lihat error handling aplikasi prakarya, ketika usernya mengetikkan alamat yang tidak ada di aplikasi.</p>
<p><a href="https://lh6.googleusercontent.com/-aZa_dcU50c4/U001E2SUhMI/AAAAAAAAFm4/x502FobAP3s/w614-h238-no/404-prakarya.png"><img src="https://lh6.googleusercontent.com/-aZa_dcU50c4/U001E2SUhMI/AAAAAAAAFm4/x502FobAP3s/w614-h238-no/404-prakarya.png" alt="Foto" /></a></p>
<p>Nah, jelek sekali bukan? Sangat menunjukkan kemalasan programmernya. Padahal dengan beberapa baris tambahan (di Java cuma 4 baris saja), kita bisa buat yang lebih baik seperti ini</p>
<p><a href="https://lh6.googleusercontent.com/-o_w5mMRrh1Q/U001mWLAvAI/AAAAAAAAFnI/lS3c42pYdWc/w616-h465-no/404-production.png"><img src="https://lh6.googleusercontent.com/-o_w5mMRrh1Q/U001mWLAvAI/AAAAAAAAFnI/lS3c42pYdWc/w616-h465-no/404-production.png" alt="Foto" /></a></p>
<p>Waktu yang dibutuhkan tidak lama. Maksimal 5 menit saja. Itupun lama karena kita bimbang apakah mau pakai gambar kucing atau Peter Parker.</p>
<p>Nah kalau pesan error di atas hanya sebatas faktor estetika, pesan error berikut konsekuensinya lebih fatal</p>
<p><a href="https://lh3.googleusercontent.com/-YbMALc355TM/U001Gd2IjAI/AAAAAAAAFnA/6tanv9Q5u8I/w621-h350-no/500-prakarya.png"><img src="https://lh3.googleusercontent.com/-YbMALc355TM/U001Gd2IjAI/AAAAAAAAFnA/6tanv9Q5u8I/w621-h350-no/500-prakarya.png" alt="Foto" /></a></p>
<blockquote>
<p>Kenapa fatal?</p>
</blockquote>
<p>Karena dari pesan error di atas, pemirsa bisa mengetahui:</p>
<ul>
<li>cara menimbulkan error di atas. Tentu dia ingat langkah-langkah yang dia lakukan sehingga terjadi error seperti di atas.</li>
<li>bahasa pemrograman yang digunakan, yaitu Java. Dilihat dari model stacktracenya.</li>
<li>merek dan versi application server yang digunakan, yaitu Tomcat 6.0.29.</li>
<li>lokasi akurat dari kode program yang error, yaitu class <code class="language-plaintext highlighter-rouge">ErrorServlet</code> yang berada dalam package <code class="language-plaintext highlighter-rouge">com.muhardin.endy.servlet</code> di baris <code class="language-plaintext highlighter-rouge">12</code>.</li>
<li>penyebab error, yaitu <code class="language-plaintext highlighter-rouge">IllegalStateException</code>.</li>
<li>estimasi cara kerja program pada waktu error</li>
</ul>
<p>Informasi di atas cukup sebagai titik awal cracker yang akan menjebol aplikasi kita. Dengan informasi tersebut, dia tinggal google:</p>
<ul>
<li>bug yang ada di Tomcat versi 6.0.29.</li>
<li>cara mengeksploitasi bug tersebut</li>
</ul>
<p>Nah, cukup mengerikan bukan? Padahal cukup dengan tambahan 4 baris, kita bisa ganti pesan errornya seperti ini</p>
<p><a href="https://lh4.googleusercontent.com/-JoVmx6mjuNg/U00184vMCTI/AAAAAAAAFnk/PpyXQXOqAyU/w619-h372-no/500-production.png"><img src="https://lh4.googleusercontent.com/-JoVmx6mjuNg/U00184vMCTI/AAAAAAAAFnk/PpyXQXOqAyU/w619-h372-no/500-production.png" alt="Foto" /></a></p>
<p>Screen di atas akan sangat mengurangi informasi yang bisa disalahgunakan orang yang berniat jahat. Lagipula pesan error <code class="language-plaintext highlighter-rouge">IllegalStateException</code> tidak ada gunanya buat user. Error 500 artinya tidak ada perbaikan yang bisa dilakukan end-user. Beda dengan error validasi input dimana user bisa memperbaiki isiannya dan kemudian mencoba submit lagi.</p>
<h2 id="pesan-moral">Pesan Moral</h2>
<p>Perbedaan antara aplikasi prakarya dan production sudah dibahas dalam <a href="http://en.wikipedia.org/wiki/The_Mythical_Man-Month">buku legendaris <code class="language-plaintext highlighter-rouge">The Mythical Man Month</code> karya Fred Brooks</a>. Berikut saya kutipkan satu paragraf dari bab pertama</p>
<blockquote>
<p>Moving down across the horizontal boundary, a program
becomes a programming product. This is a program that can be run,
tested, repaired, and extended by anybody. It is usable in many
operating environments, for many sets of data.
To become a generally usable programming product,
a program must be written in a
generalized fashion. In particular the range and form of inputs
must be generalized as much as the basic algorithm will reasonably
allow. Then the program must be thoroughly tested, so that it can
be depended upon. This means that a substantial bank of test
cases, exploring the input range and probing its boundaries, must
be prepared, run, and recorded. Finally, promotion of a program
to a programming product requires its thorough documentation, so
that anyone may use it, fix it, and extend it. As a rule of thumb,
I estimate that a programming product costs at least three times as
much as a debugged program with the same function.</p>
</blockquote>
<p>Dari paparan Eyang Fred di atas, kita bisa mendapatkan beberapa poin penting:</p>
<ul>
<li>membuat aplikasi production biayanya 3x lipat dibandingkan membuat aplikasi prakarya</li>
<li>aplikasi production dapat dipakai di berbagai kondisi (operating environment)</li>
<li>aplikasi production dapat menangani data dalam jumlah besar (many sets of data)</li>
<li>aplikasi production dites secara menyeluruh (thoroughly tested)</li>
<li>tidak hanya urusan coding saja, untuk naik kelas ke kasta production aplikasi juga harus dilengkapi dokumentasi yang lengkap</li>
</ul>
<p>Jadi, untuk set fitur yang sama:</p>
<ul>
<li>tambah user</li>
<li>edit user</li>
<li>hapus user</li>
<li>tampilkan data user</li>
</ul>
<p>terdapat perbedaan kualitas yang signifikan antara level prakarya dan level production. Kalau aplikasi prakarya dibuat dalam waktu 1 bulan, maka untuk membuatnya berkualitas production butuh tambahan 2 bulan lagi.</p>
<p>Tapi untungnya, untuk naik kelas dari prakarya menjadi production syaratnya tidak sulit, sederhana saja yaitu:</p>
<blockquote>
<p>Jangan malas</p>
</blockquote>
Membuat subreport Jasper dalam SpringMVC2014-04-12T12:26:00+07:00https://software.endy.muhardin.com/java/membuat-subreport-jasper-dalam-springmvc<p>Pada artikel ini, kita akan membuat implementasi download file PDF/XLS dari aplikasi web. Fitur ini akan kita implementasi menggunakan library <a href="http://community.jaspersoft.com/project/jasperreports-library">Jasper Report</a> dan framework <a href="http://docs.spring.io/spring/docs/4.0.2.RELEASE/spring-framework-reference/html/view.html#view-jasper-reports">Spring MVC</a>.</p>
<p>Projectnya akan kita buat menggunakan Maven, supaya bisa dibuka dengan baik (portable) di berbagai IDE. Untuk bisa memahami artikel ini dengan baik, pembaca harus sudah paham tentang Spring Framework dan Spring MVC. Yang belum paham Spring Framework bisa membaca <a href="http://software.endy.muhardin.com/java/memahami-dependency-injection/">artikel terdahulu tentang Dependency Injection</a>. Sedangkan pembahasan tentang Spring MVC bisa dibaca di <a href="https://www.google.com/search?q=site%3Asoftware.endy.muhardin.com&q=aplikasi+web+dengan+spring">rangkaian artikel berseri tentang membuat aplikasi web dengan Spring MVC</a>.</p>
<p>Source code lengkap dapat diakses di <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports">repository Github</a>. Bagi yang ingin tahu urutan langkah-langkah implementasinya, bisa lihat <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/commits/master">commit history</a>.</p>
<!--more-->
<h2 id="setup-project">Setup Project</h2>
<p>Pertama, kita setup dulu projectnya menggunakan Maven Archetype dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn archetype:create -DgroupId=com.muhardin.endy.belajar -DartifactId=belajar-springmvc-jasper
</code></pre></div></div>
<p>Kemudian konversi menjadi aplikasi web dengan cara:</p>
<ul>
<li>edit <code class="language-plaintext highlighter-rouge">pom.xml</code>, ganti <code class="language-plaintext highlighter-rouge"><packaging>jar</packaging></code> menjadi <code class="language-plaintext highlighter-rouge"><packaging>war</packaging></code></li>
<li>tambahkan file <code class="language-plaintext highlighter-rouge">src/main/webapp/WEB-INF/web.xml</code></li>
<li>ini langkah opsional, tambahkan <code class="language-plaintext highlighter-rouge">src/main/webapp/index.html</code> supaya ada halaman Hello World</li>
</ul>
<p>Kondisi project pada tahap ini bisa dilihat di <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/tree/1fa6134d10b2816b40367d1fc2cc4302ddcb9a02">commit 1fa6134d10</a></p>
<p>Setelah selesai di tahap ini, kita bisa buka projectnya menggunakan Netbeans atau Eclipse.</p>
<h2 id="aktivasi-spring-mvc">Aktivasi Spring MVC</h2>
<p>Mulai dari yang mudah dulu, kita buat controller yang menampilkan Hello World dengan Spring MVC. Ini sekedar memastikan bahwa konfigurasi Spring MVC kita sudah benar, sehingga pada waktu implementasi Jasper Report nanti kita tidak perlu lagi memusingkan error Spring MVC.</p>
<p>Tambahkan dependensi Spring MVC di <code class="language-plaintext highlighter-rouge">pom.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-webmvc<span class="nt"></artifactId></span>
<span class="nt"><version></span>${org.springframework.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Saya menggunakan Maven properties untuk mendeklarasikan versi library. Ini akan memudahkan kalau di kemudian hari kita ingin mengupgrade versinya.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><properties></span>
<span class="nt"><org.springframework.version></span>4.0.2.RELEASE<span class="nt"></org.springframework.version></span>
<span class="nt"></properties></span>
</code></pre></div></div>
<p>Agar bisa memproses file JSP, kita tambahkan juga library <code class="language-plaintext highlighter-rouge">jstl</code>.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>javax.servlet<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>jstl<span class="nt"></artifactId></span>
<span class="nt"><version></span>${jstl.version}<span class="nt"></version></span>
<span class="nt"><scope></span>runtime<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Selanjutnya, deklarasikan <code class="language-plaintext highlighter-rouge">DispatcherServlet</code> milik Spring di <code class="language-plaintext highlighter-rouge">web.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><servlet></span>
<span class="nt"><servlet-name></span>belajar<span class="nt"></servlet-name></span>
<span class="nt"><servlet-class></span>org.springframework.web.servlet.DispatcherServlet<span class="nt"></servlet-class></span>
<span class="nt"><load-on-startup></span>1<span class="nt"></load-on-startup></span>
<span class="nt"></servlet></span>
<span class="nt"><servlet-mapping></span>
<span class="nt"><servlet-name></span>belajar<span class="nt"></servlet-name></span>
<span class="nt"><url-pattern></span>/<span class="nt"></url-pattern></span>
<span class="nt"></servlet-mapping></span>
</code></pre></div></div>
<p>Kita harus membuat file konfigurasi sesuai dengan nama servlet. Karena namanya <code class="language-plaintext highlighter-rouge">belajar</code>, maka file yang harus dibuat adalah <code class="language-plaintext highlighter-rouge">belajar-servlet.xml</code>. Isinya tidak banyak, seperti ini:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span>
<span class="na">xmlns:mvc=</span><span class="s">"http://www.springframework.org/schema/mvc"</span>
<span class="na">xmlns:context=</span><span class="s">"http://www.springframework.org/schema/context"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd"</span><span class="nt">></span>
<span class="nt"><mvc:annotation-driven/></span>
<span class="nt"><mvc:default-servlet-handler</span> <span class="nt">/></span>
<span class="nt"><context:component-scan</span> <span class="na">base-package=</span><span class="s">"com.muhardin.endy.belajar.springmvcjasper.controller"</span> <span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"jstlViewResolver"</span> <span class="na">class=</span><span class="s">"org.springframework.web.servlet.view.InternalResourceViewResolver"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"viewClass"</span> <span class="na">value=</span><span class="s">"org.springframework.web.servlet.view.JstlView"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"prefix"</span> <span class="na">value=</span><span class="s">"/WEB-INF/templates/jsp/"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"suffix"</span> <span class="na">value=</span><span class="s">".jsp"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>Sediakan file <code class="language-plaintext highlighter-rouge">halo.jsp</code> dalam folder <code class="language-plaintext highlighter-rouge">WEB-INF/templates/jsp</code>. Isinya tidak saya pasang di sini, silahkan lihat sendiri <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/blob/5e5eed120ab0a58e91a5c130fa2957b82acfebb0/src/main/webapp/WEB-INF/templates/jsp/halo.jsp">di Github</a>.</p>
<p>Buat controller dalam package <code class="language-plaintext highlighter-rouge">com.muhardin.endy.belajar.springmvcjasper.controller</code>, sesuai konfigurasi dalam <code class="language-plaintext highlighter-rouge">belajar-servlet.xml</code>. Isi file bisa dilihat <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/blob/5e5eed120ab0a58e91a5c130fa2957b82acfebb0/src/main/java/com/muhardin/endy/belajar/springmvcjasper/controller/HaloController.java">di Github</a>.</p>
<p>Pada tahap ini, kita bisa menjalankan aplikasinya dengan perintah <code class="language-plaintext highlighter-rouge">mvn clean package tomcat:run</code> dan browse ke <code class="language-plaintext highlighter-rouge">http://localhost:8080/belajar-springmvc-jasper/halo</code></p>
<p>Kondisi folder project pada tahap ini bisa dilihat <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/tree/5e5eed120ab0a58e91a5c130fa2957b82acfebb0">di sini</a>.</p>
<h2 id="aktivasi-jasper-report">Aktivasi Jasper Report</h2>
<p>Kita harus membuat dulu template reportnya. Gunakan <a href="http://community.jaspersoft.com/project/ireport-designer">iReport</a> untuk mendesain template secara visual, supaya lebih mudah. Berikut screenshot report yang saya buat</p>
<p><a href="https://lh4.googleusercontent.com/-j44Qm1BUa70/U0jWOO7SwOI/AAAAAAAAFlM/vReeJlxCI3E/w916-h574-no/01-report-induk.png"><img src="https://lh4.googleusercontent.com/-j44Qm1BUa70/U0jWOO7SwOI/AAAAAAAAFlM/vReeJlxCI3E/w916-h574-no/01-report-induk.png" alt="Foto" /></a></p>
<p>Semua template Jasper Report kita simpan dalam folder <code class="language-plaintext highlighter-rouge">src/main/webapp/WEB-INF/templates/jrxml</code>.</p>
<p>Agar bisa memproses template report tersebut, kita tambahkan dulu library Jasper Report di <code class="language-plaintext highlighter-rouge">pom.xml</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>net.sf.jasperreports<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>jasperreports<span class="nt"></artifactId></span>
<span class="nt"><version></span>${jasperreports.version}<span class="nt"></version></span>
<span class="nt"><scope></span>runtime<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Cukup gunakan scope <code class="language-plaintext highlighter-rouge">runtime</code> karena kita tidak perlu import class Jasper di kode program kita. Tambahkan juga dependensi <code class="language-plaintext highlighter-rouge">javax.servlet.http</code> supaya kita bisa menggunakan class <code class="language-plaintext highlighter-rouge">HttpServletRequest</code> nantinya. Scopenya kita gunakan <code class="language-plaintext highlighter-rouge">provided</code>, karena sudah disediakan dalam application server (misal: Tomcat).</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>javax<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>javaee-web-api<span class="nt"></artifactId></span>
<span class="nt"><version></span>7.0<span class="nt"></version></span>
<span class="nt"><scope></span>provided<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Selanjutnya, kita tinggal isi data yang akan ditampilkan dalam dokumen PDF/XLS. Ini kita lakukan di kode program Java dalam class <code class="language-plaintext highlighter-rouge">HaloController</code>. Berikut methodnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/anggota/export*"</span><span class="o">,</span> <span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ModelMap</span> <span class="nf">exportDataAnggota</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">uri</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getRequestURI</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">format</span> <span class="o">=</span> <span class="n">uri</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="n">uri</span><span class="o">.</span><span class="na">lastIndexOf</span><span class="o">(</span><span class="s">"."</span><span class="o">)</span> <span class="o">+</span> <span class="mi">1</span><span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ModelMap</span><span class="o">()</span>
<span class="o">.</span><span class="na">addAttribute</span><span class="o">(</span><span class="s">"format"</span><span class="o">,</span> <span class="n">format</span><span class="o">)</span>
<span class="o">.</span><span class="na">addAttribute</span><span class="o">(</span><span class="s">"tanggal"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">())</span>
<span class="o">.</span><span class="na">addAttribute</span><span class="o">(</span><span class="s">"dataSource"</span><span class="o">,</span> <span class="n">service</span><span class="o">.</span><span class="na">semuaAnggota</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada kode program di atas, pertama kita ambil dulu akhiran di URL yang diakses user. Contohnya, bila user mengakses <code class="language-plaintext highlighter-rouge">http://localhost:8080/belajar-springmvc-jasper/anggota/export.pdf</code>, maka variabel <code class="language-plaintext highlighter-rouge">format</code> akan berisi <code class="language-plaintext highlighter-rouge">pdf</code>. Variabel <code class="language-plaintext highlighter-rouge">format</code> ini akan digunakan Spring untuk menentukan jenis outputnya, apakah PDF, XLS, CSV, dsb.</p>
<p>Kita isi variabel apapun yang kita butuhkan dalam report. Pada contoh di atas, kita kirim dua variabel yaitu <code class="language-plaintext highlighter-rouge">tanggal</code> dan <code class="language-plaintext highlighter-rouge">dataSource</code>. Variabel <code class="language-plaintext highlighter-rouge">tanggal</code> akan menjadi parameter dalam Jasper Report, sedangkan <code class="language-plaintext highlighter-rouge">dataSource</code> akan menjadi sumber data yang akan ditampilkan dalam <em>band</em> <code class="language-plaintext highlighter-rouge">detail</code>. Untuk lebih jelasnya, silahkan <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/blob/17dd0806ccf41d5edbcf646aa50458cfbd130d8f/src/main/webapp/WEB-INF/templates/jrxml/daftar-anggota.jrxml">buka file templatenya</a> dengan menggunakan iReport.</p>
<p>Selanjutnya, kita konfigurasi Spring agar request ke url <code class="language-plaintext highlighter-rouge">http://localhost:8080/belajar-springmvc-jasper/anggota/export.pdf</code> diteruskan ke Jasper Report. Kita buat deklarasi resolver untuk Jasper Report dalam <code class="language-plaintext highlighter-rouge">belajar-servlet.xml</code> sebagai berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"jasperViewResolver"</span> <span class="na">class=</span><span class="s">"org.springframework.web.servlet.view.XmlViewResolver"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"location"</span> <span class="na">value=</span><span class="s">"classpath:jasper-views.xml"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"order"</span> <span class="na">value=</span><span class="s">"0"</span><span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Tambahkan juga property <code class="language-plaintext highlighter-rouge">order</code> di konfigurasi JSP supaya dia dijalankan setelah Jasper Report.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"jstlViewResolver"</span> <span class="na">class=</span><span class="s">"org.springframework.web.servlet.view.InternalResourceViewResolver"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"viewClass"</span> <span class="na">value=</span><span class="s">"org.springframework.web.servlet.view.JstlView"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"prefix"</span> <span class="na">value=</span><span class="s">"/WEB-INF/templates/jsp/"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"suffix"</span> <span class="na">value=</span><span class="s">".jsp"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"order"</span> <span class="na">value=</span><span class="s">"1"</span><span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Ini kita lakukan karena resolver JSP tidak bisa mengecek apakah templatenya ditemukan atau tidak. Dia akan langsung mengeluarkan pesan error. Dengan resolver Jasper dieksekusi duluan, maka flownya akan seperti ini:</p>
<ol>
<li>Dari controller terima nama view <code class="language-plaintext highlighter-rouge">/anggota/export.pdf</code></li>
<li>Cari view dengan nama tersebut dalam <code class="language-plaintext highlighter-rouge">jasperViewResolver</code></li>
<li>Kalau tidak ketemu lanjutkan cari dalam <code class="language-plaintext highlighter-rouge">jstlViewResolver</code></li>
<li>Kalau tidak ketemu juga, keluarkan pesan error</li>
</ol>
<p>Kondisi folder project pada tahap ini bisa dilihat <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/tree/bce02983b044e80a97db7dc95689b643afbdea8a">di sini</a></p>
<p>Pada tahap ini kita sudah bisa menghasilkan file PDF ataupun XLS menggunakan Jasper Report dan Spring MVC. Tentunya pada aplikasi sebenarnya data diambil dari database. Tapi untuk menyederhanakan masalah agar kita bisa fokus ke konfigurasi Jasper dan Spring, maka data yang ditampilkan kita hardcode saja.</p>
<p>Selanjutnya, kita akan mencoba konfigurasi yang lebih sulit, yaitu sub report.</p>
<h2 id="sub-report">Sub Report</h2>
<p>Subreport adalah report di dalam report. Ini adalah fitur canggih dari Jasper Report. Kita tidak akan membahas detail tentang apa itu subreport. Silahkan baca artikel <a href="http://www.richardnichols.net/2010/02/simple-guide-to-sub-reports-in-jasperreports-ireport/">ini</a> dan <a href="http://bytecoded.blogspot.com/2009/12/jasperreports-370demosamplessubreport.html">ini</a> untuk memahami apa itu subreport.</p>
<p>Satu report induk bisa menampung banyak subreport. Masing-masing subreport memiliki template dan <code class="language-plaintext highlighter-rouge">dataSource</code> sendiri. Untuk itu, kita perlu memberitahukan pada semua pihak tentang lokasi / nama file template subreport dan nama variabel <code class="language-plaintext highlighter-rouge">dataSource</code> yang digunakan di subreport.</p>
<p>Kita mulai dari desain template induk terlebih dulu. Ini kita lakukan di iReport. Drag and drop icon subreport di Palette ke template. iReport akan menampilkan wizard untuk membantu kita.</p>
<p><a href="https://lh5.googleusercontent.com/-gqIBrrd0qeg/U0jWNjg4bSI/AAAAAAAAFk4/XzTxNcCIyOU/w667-h435-no/02-membuat-subreport.png"><img src="https://lh5.googleusercontent.com/-gqIBrrd0qeg/U0jWNjg4bSI/AAAAAAAAFk4/XzTxNcCIyOU/w667-h435-no/02-membuat-subreport.png" alt="Foto" /></a></p>
<p>Saya lebih suka membuat templatenya secara terpisah, kemudian menyatukannya secara manual. Oleh karena itu, kita pilih <em>Just create subreport element</em>.</p>
<p>Hasilnya sebagai berikut.</p>
<p><a href="https://lh5.googleusercontent.com/-3ig57Gmr68A/U0jWOcc-8hI/AAAAAAAAFlI/49LtLA6TDFk/w916-h574-no/03-layout-subreport.png"><img src="https://lh5.googleusercontent.com/-3ig57Gmr68A/U0jWOcc-8hI/AAAAAAAAFlI/49LtLA6TDFk/w916-h574-no/03-layout-subreport.png" alt="Foto" /></a></p>
<p>Sebetulnya di dokumentasi Spring MVC ada penjelasan tentang cara menggunakan subreport.</p>
<p><a href="https://lh6.googleusercontent.com/-j7vN6jiyKIs/U0jWOktav7I/AAAAAAAAFlU/Qs8BbyjvYro/w917-h478-no/04-docs-springmvc-jasper-subreport.png"><img src="https://lh6.googleusercontent.com/-j7vN6jiyKIs/U0jWOktav7I/AAAAAAAAFlU/Qs8BbyjvYro/w917-h478-no/04-docs-springmvc-jasper-subreport.png" alt="Foto" /></a></p>
<p>Tapi sayangnya, isinya tidak nyambung sama penjelasan di atasnya. Penjelasan report sederhana dibuat menggunakan konfigurasi file properties, sedangkan penjelasan subreport dibuat menggunakan konfigurasi file XML. Perbedaannya bisa dilihat <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/commit/bce02983b044e80a97db7dc95689b643afbdea8a">di sini</a> atau pada gambar di bawah.</p>
<p><a href="https://lh4.googleusercontent.com/-7xWAHJ6UnL0/U0jdR--3THI/AAAAAAAAFmI/1ZiMYLibtpM/w916-h617-no/09-properties-vs-xml.png"><img src="https://lh4.googleusercontent.com/-7xWAHJ6UnL0/U0jdR--3THI/AAAAAAAAFmI/1ZiMYLibtpM/w916-h617-no/09-properties-vs-xml.png" alt="Foto" /></a></p>
<p>Selanjutnya, kita tambahkan parameter di desain report induk kita. Bisa menggunakan GUI ataupun langsung edit XMLnya. Kali ini saya pakai XML saja supaya bisa copy-paste</p>
<p><a href="https://lh3.googleusercontent.com/-hCvlDw6HmAE/U0jWPcwj3iI/AAAAAAAAFlc/njZlxs_0lNI/w834-h383-no/05-subreport-params.png"><img src="https://lh3.googleusercontent.com/-hCvlDw6HmAE/U0jWPcwj3iI/AAAAAAAAFlc/njZlxs_0lNI/w834-h383-no/05-subreport-params.png" alt="Foto" /></a></p>
<p>Kita tambahkan dua parameter, yaitu <code class="language-plaintext highlighter-rouge">SubReportKantor</code> dan <code class="language-plaintext highlighter-rouge">dataSourceSubreport</code>. Kedua parameter ini kita gunakan pada deklarasi subreport yang sudah ditambahkan iReport pada waktu kita drag and drop tadi.</p>
<p><a href="https://lh3.googleusercontent.com/-zqWFEblHxvo/U0jWPXhcBVI/AAAAAAAAFlk/r5q5WZw3BlI/w917-h265-no/06-subreport-declaration.png"><img src="https://lh3.googleusercontent.com/-zqWFEblHxvo/U0jWPXhcBVI/AAAAAAAAFlk/r5q5WZw3BlI/w917-h265-no/06-subreport-declaration.png" alt="Foto" /></a></p>
<p>Perhatikan label <code class="language-plaintext highlighter-rouge">X</code> dan <code class="language-plaintext highlighter-rouge">Y</code>. Itu akan kita gunakan pada konfigurasi lainnya.</p>
<p>Nama variabel <code class="language-plaintext highlighter-rouge">dataSourceSubreport</code> yang ditandai dengan label <code class="language-plaintext highlighter-rouge">X</code> kita gunakan untuk menyuplai data yang dibutuhkan subreport dari dalam kode program Java.</p>
<p><a href="https://lh3.googleusercontent.com/-FE5T4GfnzBE/U0jWPTUvL7I/AAAAAAAAFlo/Rl8SG7IZjkI/w672-h217-no/07-spring-controller.png"><img src="https://lh3.googleusercontent.com/-FE5T4GfnzBE/U0jWPTUvL7I/AAAAAAAAFlo/Rl8SG7IZjkI/w672-h217-no/07-spring-controller.png" alt="Foto" /></a></p>
<p>Selanjutnya kita edit konfigurasi Jasper Report di file <code class="language-plaintext highlighter-rouge">jasper-views.xml</code>. Tambahkan lokasi template subreport dan nama variabel yang digunakan untuk <code class="language-plaintext highlighter-rouge">dataSource</code>nya.</p>
<p><a href="https://lh6.googleusercontent.com/-qBxNAJ_nqsU/U0jWQwV3gAI/AAAAAAAAFlw/cqnz8eiWWd0/w917-h285-no/08-konfigurasi-jasperview.png"><img src="https://lh6.googleusercontent.com/-qBxNAJ_nqsU/U0jWQwV3gAI/AAAAAAAAFlw/cqnz8eiWWd0/w917-h285-no/08-konfigurasi-jasperview.png" alt="Foto" /></a></p>
<p>Selesai sudah. Silahkan coba akses lagi <code class="language-plaintext highlighter-rouge">http://localhost:8080/belajar-springmvc-jasper/anggota/export.pdf</code> untuk melihat hasil akhirnya.</p>
<p><a href="https://lh6.googleusercontent.com/-8-GCJgtp70I/U0jgW78hxnI/AAAAAAAAFmY/2jJIFCNeB6g/w917-h485-no/10-final-result.png"><img src="https://lh6.googleusercontent.com/-8-GCJgtp70I/U0jgW78hxnI/AAAAAAAAFmY/2jJIFCNeB6g/w917-h485-no/10-final-result.png" alt="Foto" /></a></p>
<h2 id="faq">FAQ</h2>
<p>Pada umumnya, kita semua menggunakan konfigurasi Jasper yang berbasis file properties, karena itulah yang dicontohkan di dokumentasi Spring Framework. Pada waktu muncul kebutuhan subreport, ternyata file konfigurasinya XML.</p>
<blockquote>
<p>Apakah saya harus konversi semua konfigurasi properties menjadi XML ??</p>
</blockquote>
<p>Jangan khawatir. Kita bisa menggunakan dua-duanya sekaligus. Tidak perlu pilih salah satu. Konfigurasi report yang lama tidak perlu dibuang/ditulis ulang menjadi xml. Cukup deklarasikan dua <code class="language-plaintext highlighter-rouge">view resolver</code>, yang satu properties, satu lagi XML. Begini contohnya</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"propertiesJasperViewResolver"</span> <span class="na">class=</span><span class="s">"org.springframework.web.servlet.view.ResourceBundleViewResolver"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"basename"</span> <span class="na">value=</span><span class="s">"views"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"order"</span> <span class="na">value=</span><span class="s">"0"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"xmlJasperViewResolver"</span> <span class="na">class=</span><span class="s">"org.springframework.web.servlet.view.XmlViewResolver"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"location"</span> <span class="na">value=</span><span class="s">"classpath:jasper-views.xml"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"order"</span> <span class="na">value=</span><span class="s">"1"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"jstlViewResolver"</span> <span class="na">class=</span><span class="s">"org.springframework.web.servlet.view.InternalResourceViewResolver"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"viewClass"</span> <span class="na">value=</span><span class="s">"org.springframework.web.servlet.view.JstlView"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"prefix"</span> <span class="na">value=</span><span class="s">"/WEB-INF/templates/jsp/"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"suffix"</span> <span class="na">value=</span><span class="s">".jsp"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"order"</span> <span class="na">value=</span><span class="s">"2"</span><span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Dengan cara di atas, berikut flow Spring MVC dalam mencari template yang sesuai:</p>
<ol>
<li>Dari controller terima nama view <code class="language-plaintext highlighter-rouge">/anggota/export.pdf</code></li>
<li>Cari view dengan nama tersebut dalam <code class="language-plaintext highlighter-rouge">propertiesJasperViewResolver</code></li>
<li>Kalau tidak ketemu lanjutkan cari dalam <code class="language-plaintext highlighter-rouge">xmlJasperViewResolver</code></li>
<li>Kalau masih tidak ketemu juga lanjutkan cari dalam <code class="language-plaintext highlighter-rouge">jstlViewResolver</code></li>
<li>Kalau tidak ketemu juga, keluarkan pesan error</li>
</ol>
<p>Demikian penjelasan tentang integrasi Spring MVC dan Jasper Report. Source code lengkap bisa diambil <a href="https://github.com/endymuhardin/belajar-springmvc-jasperreports/">di Github</a></p>
<p>Semoga bermanfaat</p>
Cara Mengetahui IP Address dari MAC Address2014-04-08T11:59:00+07:00https://software.endy.muhardin.com/linux/cara-mengetahui-ip-address-dari-mac-address<p>Jaman sekarang banyak sekali perangkat-perangkat aneh yang menarik untuk dioprek, misalnya <a href="http://en.wikipedia.org/wiki/Raspberry_Pi">Raspberry PI</a>, <a href="http://en.wikipedia.org/wiki/PC-on-a-stick">PC on a stick</a>, Smart TV, <a href="http://www.wd.com/en/products/products.aspx?id=280">Harddisk NAS</a>, dan sebagainya. Semua perangkat aneh ini terhubung ke jaringan baik melalui kabel maupun WiFi.</p>
<p>Di mayoritas jaringan WiFi yang ada, alamat IP diberikan secara otomatis (DHCP), sehingga kita tidak tahu berapa alamat IP untuk perangkat yang ingin kita akses. Tambahan lagi, beberapa router wifi juga kurang bagus antarmukanya seperti pada gambar berikut</p>
<p><a href="https://lh5.googleusercontent.com/-nZMqJy94dy8/U0OD8iwCKAI/AAAAAAAAFjg/sxw8F4pe2XI/w917-h429-no/bolt-dhcp-client-list.png"><img src="https://lh5.googleusercontent.com/-nZMqJy94dy8/U0OD8iwCKAI/AAAAAAAAFjg/sxw8F4pe2XI/w917-h429-no/bolt-dhcp-client-list.png" alt="Foto" /></a></p>
<p>Masa yang dicantumkan cuma MAC Address saja? Hostnamenya juga <code class="language-plaintext highlighter-rouge">Unknown</code> lagi -_-</p>
<p>Untungnya, ada cara untuk mengkonversi MAC Address menjadi IP. Berikut caranya</p>
<!--more-->
<p>Pertama, kita gunakan aplikasi <code class="language-plaintext highlighter-rouge">nmap</code> untuk mencari daftar alamat IP yang sedang aktif di jaringan kita.</p>
<p>Jalankan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nmap -nsP 192.168.1.0/24
</code></pre></div></div>
<p>Berikut penjelasan opsi yang kita gunakan:</p>
<ul>
<li>n : jangan cari nama host melalui DNS. Cukup IP Address saja</li>
<li>sP : ping scan. Cari IP mana yang aktif dengan ping. Di versi terbaru opsi <code class="language-plaintext highlighter-rouge">sP</code> ini diganti namanya menjadi <code class="language-plaintext highlighter-rouge">sn</code></li>
<li><code class="language-plaintext highlighter-rouge">192.168.1.0/24</code> : network address dan subnet tempat kita berada</li>
</ul>
<p>Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Starting Nmap 6.40 ( http://nmap.org ) at 2014-04-08 11:21 WIB
Nmap scan report for 192.168.1.1
Host is up (0.0031s latency).
MAC Address: XX:XX:XX:XX:XX:XX (zte)
Nmap scan report for 192.168.1.2
Host is up (0.0031s latency).
MAC Address: XX:XX:XX:XX:XX:XX (zte)
Nmap scan report for 192.168.1.103
Host is up (0.014s latency).
MAC Address: XX:XX:XX:XX:XX:XX (Samsung Electro-mechanics CO.)
Nmap scan report for 192.168.1.107
Host is up (0.015s latency).
MAC Address: XX:XX:XX:XX:XX:XX (Samsung Electronics Co.)
Nmap scan report for 192.168.1.144
Host is up (0.019s latency).
MAC Address: XX:XX:XX:XX:XX:XX (Unknown)
Nmap scan report for 192.168.1.149
Host is up (0.059s latency).
MAC Address: XX:XX:XX:XX:XX:XX (Samsung Electronics Co.)
Nmap scan report for 192.168.1.115
Host is up.
Nmap done: 256 IP addresses (7 hosts up) scanned in 3.09 seconds
</code></pre></div></div>
<p>Dari output di atas, kita mendapatkan informasi yang cukup banyak, yaitu:</p>
<ul>
<li>Alamat IP</li>
<li>Mac Address</li>
<li>Vendor pembuat hardware</li>
</ul>
<p>Kita tinggal bandingkan dengan MAC Address perangkat yang kita ingin cari, korelasikan dengan output di atas, dan kita bisa mendapatkan IPnya. Kita bisa mempersempit pencarian dengan mengeliminasi berdasarkan nama vendor pembuatnya.</p>
Menjadi Mahasiswa Hi-Tech2014-03-09T20:22:00+07:00https://software.endy.muhardin.com/life/menjadi-mahasiswa-hi-tech<p>Seperti sudah diceritakan pada <a href="http://software.endy.muhardin.com/java/silabus-kuliah-pemrograman/">posting sebelumnya</a>, saya sudah beberapa tahun ini mengajar di Universitas Pancasila. Total sudah lebih dari 6 kelas yang saya ajar. Kalau satu kelas saja isinya 30 orang, maka setidaknya ada 180 mahasiswa yang telah mengikuti perkuliahan saya.</p>
<p>Memperhatikan perilaku ratusan mahasiswa dalam mengikuti kuliah, saya menemui suatu kondisi yang ironis.</p>
<blockquote>
<p>Dari sekian banyak mahasiswa, tidak ada satupun yang mengikuti perkuliahan sesuai dengan kondisi jaman sekarang. Semuanya, tanpa terkecuali, masih kuliah seperti halnya saya kuliah di tahun 1997 dulu. Ya benar, selama 17 tahun tidak ada perubahan metodologi sama sekali.</p>
</blockquote>
<p>Padahal ini mahasiswa jurusan IT, yang sepatutnya menjadi yang terdepan dalam hal memahami dan memanfaatkan teknologi terkini. Mereka masih saja:</p>
<ul>
<li>mendengarkan saya berceloteh di depan kelas</li>
<li>mencatat celotehan saya dan gambar-gambar yang saya buat di papan tulis</li>
<li>pada saat mau ujian, belajar dari catatan tersebut</li>
</ul>
<p>Tidak sebanding dengan persenjataan masing-masing individu:</p>
<ul>
<li>Smartphone. Segelintir pakai iPhone, dan mayoritas pakai Android. Minimal Blackberry.</li>
<li>Internet Nonstop. Baik paket data dari smartphone maupun internet gratis dari kampus.</li>
<li>Sesi kuliah saya selalu diadakan di lab komputer. Masing-masing orang menghadapi PC di depan mukanya.</li>
</ul>
<blockquote>
<p>Nah, lalu bagaimana seharusnya??</p>
</blockquote>
<p>Ada beberapa poin yang seharusnya dilakukan mahasiswa jaman sekarang:</p>
<ol>
<li>Jangan mencatat</li>
<li>Tinggal di awan</li>
<li>Kolaborasi menggunakan social networking</li>
<li>Belajar dari internet</li>
</ol>
<blockquote>
<p>Waduh, tidak mencatat gimana mau ingat? Tidak bawa buku lalu mau menulis dimana? Bukannya orang bilang Facebook itu tidak produktif?</p>
</blockquote>
<p>Mari kita bahas..</p>
<!--more-->
<h2 id="jangan-mencatat">Jangan Mencatat</h2>
<p>Urusan catat-mencatat ini kita bagi dua:</p>
<ul>
<li>pada saat kuliah teori</li>
<li>pada saat praktikum</li>
</ul>
<h3 id="saat-kuliah">Saat Kuliah</h3>
<p>Ada beberapa kerugian kalau kita mencatat selama sesi perkuliahan:</p>
<ul>
<li>tidak mengikuti penjelasan dosen secara penuh karena konsentrasi kita terbagi</li>
<li>tidak bisa mengabadikan materi 100%, karena kecepatan menulis kita jauh lebih lambat daripada kecepatan bicara dosen</li>
<li>untuk konten instruksional, seperti sesi <code class="language-plaintext highlighter-rouge">live coding</code> yang biasa saya lakukan di kelas, ada urut-urutan langkah yang sulit untuk ditangkap tulisan tangan</li>
</ul>
<p>Walaupun demikian, menulis itu tetap penting karena banyak manfaatnya:</p>
<ul>
<li>mengatasi faktor lupa. Apalagi pada masa kuliah kita mengikuti beragam perkuliahan. Kalau tidak dicatat, isi kuliah Pemrograman akan ludes begitu kita masuk kelas Basis Data.</li>
<li>kegiatan menulis ulang dengan kalimat sendiri dapat meningkatkan pemahaman kita. Ini sebabnya saya menulis blog.</li>
<li>kegiatan menulis juga bisa mengikat materi lebih kuat ke ingatan kita dibanding hanya mendengarkan saja.</li>
</ul>
<blockquote>
<p>Lalu bagaimana? Katanya jangan mencatat.</p>
</blockquote>
<p>Begini _best practices_nya:</p>
<ul>
<li>Kalau dosen menulis di papan tulis, jangan dicatat. Potret saja.</li>
<li>Selama sesi perkuliahan, nyalakan perekam suara untuk merekam omongan dosen.</li>
<li>
<p>Setelah selesai kuliah, investasikan waktu 30 menit untuk:</p>
<ul>
<li>mengetik ulang materi kuliah dengan kata-kata kita sendiri</li>
<li>tambahkan dengan foto tulisan/diagram yang digambar dosen di papan tulis</li>
<li>bila ada hal-hal yang kurang jelas, segera lengkapi <a href="http://software.endy.muhardin.com/aplikasi/teknik-menggunakan-google/">dengan bantuan Google</a>.</li>
</ul>
</li>
</ul>
<p>Dengan demikian, kita bisa mendapatkan segala keuntungan menulis tanpa terkena kerugian karena menulis selama sesi kuliah. Intinya, pisahkan kegiatan menulis (yang berguna untuk memperjelas pemahaman dan mengikat materi di otak) dengan kegiatan merekam penjelasan dosen.</p>
<blockquote>
<p>Sulitkah melakukan hal tersebut di atas?</p>
</blockquote>
<p>Sama sekali tidak. Lihat daftar persenjataan yang saya jabarkan di atas. Smartphone termurah sekalipun (harga 1 juta ke bawah BNIB) sudah mampu memotret dan merekam suara. Tambahkan budget sekitar 300 ribu untuk memory card berkapasitas 32GB. Sebagai gambaran, rekaman suara 60 menit ukuran filenya 60MB. Dengan 32 GB, kita bisa menampung 500 jam celotehan dosen :D Ok lah kita bagi dua menjadi 200 jam, karena separuhnya kita pakai untuk foto papan tulis. Kalau satu foto ukurannya 4MB, maka kita punya space untuk 4000 foto. Cukuplah untuk satu semester.</p>
<h3 id="saat-praktikum">Saat Praktikum</h3>
<p>Pada sesi praktikum, biasanya tidak ada penjelasan dosen atau asisten. Mahasiswa diberikan tugas untuk dikerjakan. Kadangkala dilengkapi dengan instruksi atau tutorial. Bagi mahasiswa pemrograman, selama praktikum tentu akan disuruh coding. Untuk mata kuliah jaringan, kadangkala disuruh setting server. Apapun itu, untuk menyelesaikan tugas praktikum biasanya dibutuhkan beberapa langkah pengerjaan menggunakan komputer.</p>
<blockquote>
<p>Lalu apa yang dicatat?</p>
</blockquote>
<p>Ya tentu saja langkah demi langkah penyelesaian tugas. Ambil screenshot tiap langkah, kemudian pasang di aplikasi <em>word processor</em> seperti Microsoft Word atau Libre Office Writer. Kalau mau lebih canggih, gunakan format Markdown seperti <a href="http://software.endy.muhardin.com/aplikasi/membuat-dokumen-dengan-markdown-dan-pandoc/">yang saya jelaskan di sini</a>. Bingung bagaimana format pencatatannya? Silahkan ikuti contoh <a href="http://software.endy.muhardin.com/aplikasi/teknik-menggunakan-google/">tulisan saya tentang penggunaan Google</a>.</p>
<p>Selain menggunakan <em>word processor</em>, kita juga bisa rekam kegiatan kita di komputer selama sesi praktikum. Kalau sudah ada rekaman, kapan saja perlu tinggal kita tonton ulang. Bonus point kalau setelah praktikum rekaman <em>screencast</em> tersebut kita dubbing untuk menambahkan penjelasan suara. Contohnya bisa dilihat di <a href="http://www.youtube.com/user/artivisi">berbagai video tutorial ArtiVisi di Youtube</a>.</p>
<h3 id="tools-dan-aplikasi">Tools dan Aplikasi</h3>
<p>Pengguna Ubuntu bisa menggunakan aplikasi <a href="http://shutter-project.org/">Shutter</a> untuk mengambil <em>screenshot</em> dan <a href="http://recordmydesktop.sourceforge.net/about.php">RecordMyDesktop</a> untuk merekam <em>screencast</em>. Bila ingin men-_dubbing_ screencast, rekam dulu penjelasannya pakai smartphone, kemudian gabungkan audio dan video dengan aplikasi <a href="http://www.openshot.org/">OpenShot</a>.</p>
<p>Bagi mereka yang di jaman merdeka ini masih saja terkurung di balik jendela, bisa menggunakan aplikasi <a href="http://www.techsmith.com/snagit.html">SnagIt</a> untuk membuat screenshot, dan <a href="http://www.techsmith.com/camtasia.html">Camtasia</a> untuk membuat screencast.</p>
<blockquote>
<p>Ok pak. Saya sudah mencatat di luar sesi kuliah menggunakan <em>word processor</em>, tidak lagi pakai buku dan pulpen. Berarti saya kemana-mana harus bawa flashdisk dong?</p>
</blockquote>
<p>Tidak perlu. Kan sekarang trend-nya tinggal di awan ;)</p>
<h2 id="tinggal-di-awan">Tinggal di Awan</h2>
<p>Jaman sekarang, orang berlomba-lomba menyediakan <em>cloud services</em>. Ada 4shared, Dropbox, Youtube, Github, Twitter, Facebook, dan sebagainya. Kita harus manfaatkan layanan gratis ini semaksimal mungkin. Kasihan yang bikin, udah susah-susah bikinnya, kita tinggal pake aja gak mau :D</p>
<p>Ada beberapa layanan yang saya gunakan, yaitu:</p>
<ul>
<li>Youtube: untuk mengupload hasil screencast. Silahkan <a href="http://www.youtube.com/user/artivisi">tonton dan subscribe</a>.</li>
<li><a href="http://quip.com">Quip</a>. Ini adalah layanan untuk membuat catatan. Kita bisa membuat catatan, mengupload foto pada catatan tersebut, bahkan sampai membuat buku. Quip ini saya gunakan selama sesi training. Semua penjelasan saya catat di situ, kemudian saya share ke peserta training. Dengan demikian mereka tidak perlu mencatat dan saya tidak perlu mengirim email berisi catatan.</li>
<li>Github. Ini adalah social networking buat programmer. Halaman Github saya bisa diakses <a href="https://github.com/endymuhardin/">di sini</a>. Lihat juga <a href="https://github.com/endymuhardin?tab=repositories">daftar repository</a>. Materi kuliah, tugas praktikum, dan sample source code saya posting di sana. Sebagai contoh, <a href="https://github.com/endymuhardin/materi-kuliah-java-2014-02/blob/master/materi-kuliah/01.maven.md">ini adalah penjelasan tentang Maven</a>. Lihat juga history commit untuk mengetahui <a href="https://github.com/endymuhardin/belajar-akses-database-java/commits/master">langkah-langkah membuat suatu program</a>.</li>
</ul>
<blockquote>
<p>Saya kan baru mahasiswa pak. Tidak perlu bikin akun Github gpp ya?</p>
</blockquote>
<p>Nah, kalau kamu mahasiswa saya, wajib punya. Saya cuma terima pengumpulan tugas via Github. Udah gak jaman mahasiswa <em>ngejunk</em> di inbox saya mengirim file <code class="language-plaintext highlighter-rouge">*.rar</code> atau <code class="language-plaintext highlighter-rouge">*.zip</code>. Tanpa akun Github, nilai tugas kamu nol.</p>
<p>Kalau bukan mahasiswa saya ya terserah saja. Satu hal yang perlu diingat. Github itu adalah portfolio kamu sebagai programmer. Modal utama programmer dalam mencari kerja. Di situ kamu menunjukkan apa yang bisa dan pernah kamu buat. Rekruter jaman sekarang juga sudah canggih. Memanggil orang interview itu buang waktu, tenaga, dan biaya. Akan jauh lebih cepat mudah dan murah untuk langsung saja melihat isi repo Github. Dalam 5 menit udah ketahuan kandidat tersebut bisa apa saja dan sejauh mana kompetensinya.</p>
<p>Setelah tinggal di awan, jangan introvert. Mari kita bersosialisasi.</p>
<h2 id="kolaborasi-di-socmed">Kolaborasi di Socmed</h2>
<p>Jaman dulu, orang diskusi di milis. Jaman sekarang milis sudah mulai sepi. Pindah ke Facebook. Ada beberapa forum Facebook yang saya ikuti, diantaranya:</p>
<ul>
<li><a href="https://www.facebook.com/groups/ForumJavaIndonesia/">Forum Java Indonesia</a></li>
<li><a href="https://www.facebook.com/groups/netbeans.id/">Netbeans Indonesia</a></li>
<li><a href="https://www.facebook.com/groups/aprogsi/">Asosiasi Programmer Indonesia</a></li>
</ul>
<p>Silahkan tanya di situ dan mention saya. Insya Allah ditanggapi kalau lagi tidak sibuk. Perlu diingat, <a href="http://software.endy.muhardin.com/java/tips-melaporkan-error/">buatlah pertanyaan yang bagus, karena kita programmer bukan dukun</a>.</p>
<p>Bila grup di atas terlalu ramai, silahkan buat baru. Gratis kok. Bahkan <a href="https://www.facebook.com/groups/processor.kaspersky.engineers/">siswa asuhan ArtiVisi di SMKN 10 Jakarta saja punya grup Facebook</a>, masa mahasiswa kalah. Di grup tersebut, siswa SMKN 10 diskusi tentang tugas yang diberikan gurunya. Gurunya juga ikut dalam grup sehingga bisa ditanya-tanyai.</p>
<p>Di panel kanan blog ini, ada juga <a href="http://twitter.com/endymuhardin">kontak Twitter saya</a>. Silahkan mention kalau mau tanya-tanya.</p>
<p>Facebook diblokir di kantor? Masih ada grup BBM atau Whatsapp. Pakailah buat diskusi masalah pemrograman, jangan buat gosip artis atau politikus aja ;)</p>
<h2 id="belajar-dari-internet">Belajar dari Internet</h2>
<p>Bapak menteri kita pernah bertanya,</p>
<blockquote>
<p>Memangnya kalau internet kenceng, mau dipake apa??</p>
</blockquote>
<p>Setelah membaca artikel ini sampai di sini, kamu sekarang bisa menjawab dengan yakin.</p>
<blockquote>
<p>Buat belajar pak !! Saya akan donlod semua video tutorial di Youtube. Saya copy ke smartphone saya. Tiap ada waktu luang, misalnya ngantri di ATM, selama kegencet di commutter line, kena macet di angkot, saya akan tonton tutorial tersebut. Insya Allah saya bisa cepet pinter pak, biar bisa gantiin Bapak ngeberesin internet Indonesia ;)</p>
</blockquote>
<p>Banyak sekali materi pelajaran di internet. Mau <a href="http://irlnathan.github.io/sailscasts/blog/archives/">belajar NodeJS dan SailsJS</a> ada. Mau <a href="http://www.youtube.com/user/SpringSourceDev">belajar Spring Framework</a> ada. Mau <a href="http://www.youtube.com/user/adoramaTV">belajar fotografi</a> ada.</p>
<h2 id="modal">Modal</h2>
<blockquote>
<p>Wah, dengan segala macam teknologi di atas, pasti modalnya mahal ya Pak? Saya mahasiswa cekak, bokek, pas-pasan.</p>
</blockquote>
<p>Tidak juga. Ini cuma masalah prioritas dan kesungguhan aja. Coba kita hitung-hitungan.</p>
<p>Pertama, kita beli smartphone dulu. Untuk gampangnya, saya buka <a href="http://www.bhinneka.com/aspx/products/smart-phone-android.aspx">Bhinneka.com, masuk ke menu Smart Phone Android</a>, dan urutkan dari yang termurah.</p>
<p><a href="https://lh3.googleusercontent.com/-6p2Qy-EfWc4/Uxx53-JnWiI/AAAAAAAAFao/tjF4qnRWTyw/w909-h533-no/01-modal-kuliah.png"><img src="https://lh3.googleusercontent.com/-6p2Qy-EfWc4/Uxx53-JnWiI/AAAAAAAAFao/tjF4qnRWTyw/w909-h533-no/01-modal-kuliah.png" alt="Foto" /></a></p>
<p>Lihat kan, dengan 800 ribu rupiah saja sudah dapat prosesor 1Ghz Dual Core, RAM 512MB, Internal Storage 4GB. Sebagai gambaran, waktu saya kuliah tahun 1997 dulu, komputer saya spec-nya 233Mhz single core, RAM 32MB, Harddisk 128MB. Smartphone? Stupidphone aja belum ada. Adanya ini nih</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/pager.JPG"><img src="/images/uploads/2013/10/teknik-menggunakan-google/pager.JPG" alt="Pager " /></a></p>
<blockquote>
<p>Wah, saya gak punya uang 800 ribu buat beli smartphone.</p>
</blockquote>
<p>Gampang, saya ada dua solusi. Solusi pertama butuh otak, solusi kedua gak perlu otak. Tinggal pilih.</p>
<p>Kalau mau pakai otak, cukup bikin selebaran les privat matematika. Bisa disebar di Facebook, Twitter, atau diprint fotokopi dan sebar di SD/SMP/SMU. Atau kalau malas bikin selebaran dan sebar-sebar, bikin lamaran kerja aja selembar ke bimbingan belajar.</p>
<blockquote>
<p>Hmm, solusi pakai otak terlalu berat buat saya. Coba yang satu lagi pak.</p>
</blockquote>
<p>Awas, hati-hati otaknya nanti ngambek karena gak pernah dipake :D</p>
<p><a href="https://lh6.googleusercontent.com/-vA0HTRryVCE/Uxx8AVOrXlI/AAAAAAAAFbA/8YEQrSqIm5s/w423-h597-no/utek-gak-tau-digawe.jpeg"><img src="https://lh6.googleusercontent.com/-vA0HTRryVCE/Uxx8AVOrXlI/AAAAAAAAFbA/8YEQrSqIm5s/w423-h597-no/utek-gak-tau-digawe.jpeg" alt="Foto" /></a></p>
<p>Anyway, ini solusi tanpa otak.</p>
<p><a href="https://lh5.googleusercontent.com/-0yytZ2Tbw-Y/Uxx_OjH5OQI/AAAAAAAAFbg/Iobz_-nrmV4/w519-h300-no/whistle-blue-m.jpg"><img src="https://lh5.googleusercontent.com/-0yytZ2Tbw-Y/Uxx_OjH5OQI/AAAAAAAAFbg/Iobz_-nrmV4/w519-h300-no/whistle-blue-m.jpg" alt="Foto" /></a></p>
<p><em>Gambar diambil <a href="http://www.seron.com/p38.html">dari tokonya</a></em></p>
<p>Harganya sekitar 30 ribu rupiah di toko olahraga terdekat. Dengan bermodalkan peluit tersebut, kamu tinggal nongkrong di parkiran kampus atau minimarket/warung kopi terdekat. Tiap ada yang mau keluar, “Priiitt !!”. Lumayan 100 kendaraan sehari, masing-masing 1000 rupiah. Setengah hari sudah balik modal untuk beli peluit. Seminggu kebeli deh smartphone.</p>
<blockquote>
<p>Ok pak, Berkat strategi peluit, smartphone sudah punya. Gimana dengan akses internetnya?</p>
</blockquote>
<p>Paket internet jaman sekarang makin murah dan cepat. Biayanya dibawah 100 ribu sebulan untuk quota 2 - 3 GB.</p>
<blockquote>
<p>Wah, saya gak punya uang 100 ribu sebulan Pak. Maklum mahasiswa kere.</p>
</blockquote>
<p>Gampang, mau solusi pake otak atau gak pake otak?? Apa perlu saya jelaskan sekali lagi?? :D</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Jaman sekarang semua sudah serba mudah. Mau belajar apa saja gak perlu tunggu guru/dosen. 5 tahun ke depan, harusnya murid/mahasiswa saya bisa 5 kali lebih hebat dari saya. Masa kalah sama generasi pager yang komputernya jauh lebih lemot daripada handphone jaman sekarang?</p>
<p>Tinggal kita saja pintar-pintar mengatur prioritas. Mana yang lebih penting, jago coding atau up to date dengan kelakuan Farhat/Jupe/Justin/whatever yang terbaru?</p>
Silabus Kuliah Pemrograman2014-03-05T12:11:00+07:00https://software.endy.muhardin.com/java/silabus-kuliah-pemrograman<p>Melanjutkan rutinitas 2 tahun terakhir, minggu ini musim perkuliahan semester genap di Universitas Pancasila dimulai. Saya kebagian pegang Pemrograman 2 di kelas reguler, dan Pemrograman 4 di kelas karyawan. Buat para fans yang pengen ketemu dipersilahkan cari saya di Fakultas Teknik Universitas Pancasila setiap Kamis jam 18-21 dan Jumat jam 09-11.</p>
<p>Berikut adalah silabus perkuliahan dari Pemrograman 1 sampai Pemrograman 4. Silabus ini akan sering saya update mengikuti perkembangan jaman.</p>
<!--more-->
<p><a name="pemrograman-1"></a></p>
<h2 id="pemrograman-1--java-fundamental">Pemrograman 1 : Java Fundamental</h2>
<h3 id="tujuan">Tujuan</h3>
<ul>
<li>Paham dasar-dasar pemrograman dengan Java</li>
<li>Paham cara implementasi OOP dengan Java</li>
<li>Paham cara menggunakan class-class penting dalam Java SDK</li>
</ul>
<h3 id="batasan">Batasan</h3>
<ul>
<li>Hanya menggunakan library standar bawaan Java SDK</li>
<li>Tidak membahas konsep OOP (karena ada mata kuliahnya sendiri)</li>
</ul>
<h3 id="referensi">Referensi</h3>
<ul>
<li><a href="http://docs.oracle.com/javase/tutorial/index.html">Official Java Tutorial, Oracle Corp</a></li>
<li><a href="http://docs.oracle.com/javase/7/docs/api/index.html">SDK Documentation, Oracle Corp</a></li>
<li><a href="http://project-template.googlecode.com/files/Java%20Desktop%20-%20Ifnu%20Bima.pdf">Java Desktop, Ifnu Bima</a></li>
</ul>
<h3 id="kebutuhan-software-lab">Kebutuhan Software Lab</h3>
<ul>
<li>Java SDK 1.6</li>
<li><a href="http://www.sublimetext.com/">Sublime Text 3</a></li>
</ul>
<h3 id="materi-kuliah">Materi Kuliah</h3>
<ul>
<li>Penjelasan Silabus</li>
<li>Setup / Instalasi Java</li>
<li>Version Control (Git)</li>
<li>Github</li>
<li>Anatomi Aplikasi Java</li>
<li>Classpath</li>
<li>Variabel dan Tipe Data</li>
<li>Operator</li>
<li>Control Flow (if-else, for/while)</li>
<li>Class & Object</li>
<li>Method</li>
<li>Exception</li>
<li>Konsep OOP</li>
<li>Inheritance</li>
<li>Encapsulation</li>
<li>Polymorphism</li>
<li>Abstract Class & Interface</li>
<li>Composition & Aggregation</li>
<li>Studi Kasus / Tugas</li>
<li>Presentasi Kasus</li>
</ul>
<p><a name="pemrograman-2"></a></p>
<h2 id="pemrograman-2--essential-library">Pemrograman 2 : Essential Library</h2>
<h3 id="tujuan-1">Tujuan</h3>
<ul>
<li>Mahir menggunakan library tambahan dari dunia open source</li>
<li>Mahir menggunakan development tools Java</li>
</ul>
<h3 id="batasan-1">Batasan</h3>
<ul>
<li>Belum membuat user interface</li>
</ul>
<h3 id="kebutuhan-software-lab-1">Kebutuhan Software Lab</h3>
<ul>
<li>Semua requirement dari Pemrograman 1</li>
<li><a href="http://maven.apache.org/">Apache Maven 3</a></li>
<li>MySQL Server</li>
</ul>
<h3 id="materi-kuliah-1">Materi Kuliah</h3>
<ul>
<li>Build Tools</li>
<li>Automated Test</li>
<li>Date & Time</li>
<li>Collections</li>
<li>JDBC</li>
<li>String</li>
<li>Regular Expression</li>
<li>Multithreading / Concurrency</li>
<li>File I/O</li>
<li>Network I/O</li>
<li>Popular Network Protocol (HTTP, FTP, SMTP, POP, IMAP)</li>
<li>Studi Kasus</li>
</ul>
<p><a name="pemrograman-3"></a></p>
<h2 id="pemrograman-3--aplikasi-desktop">Pemrograman 3 : Aplikasi Desktop</h2>
<h3 id="tujuan-2">Tujuan</h3>
<ul>
<li>Bisa membuat aplikasi lengkap dengan UI Desktop</li>
</ul>
<h3 id="batasan-2">Batasan</h3>
<ul>
<li>Aplikasi berjalan lokal</li>
<li>Penyimpanan data dilakukan lokal</li>
<li>Interaksi dengan server dilakukan dengan menggunakan service yang sudah tersedia (tidak membuat sendiri)</li>
</ul>
<h3 id="referensi-1">Referensi</h3>
<ul>
<li><a href="http://project-template.googlecode.com/files/Java%20Desktop%20-%20Ifnu%20Bima.pdf">Java Desktop, Ifnu Bima</a></li>
</ul>
<h3 id="kebutuhan-software-lab-2">Kebutuhan Software Lab</h3>
<ul>
<li>Sama seperti Pemrograman 2</li>
</ul>
<h3 id="materi-kuliah-2">Materi Kuliah</h3>
<ul>
<li>
<p>Arsitektur Aplikasi Desktop</p>
<ul>
<li>Container</li>
<li>Simple Widget</li>
<li>Event Handling</li>
<li>Data backed Widget</li>
</ul>
</li>
<li>
<p>Hello Swing</p>
</li>
<li>Container
<ul>
<li><a href="http://docs.oracle.com/javase/tutorial/uiswing/components/frame.html">Frame</a></li>
<li><a href="http://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html">Dialog</a></li>
<li><a href="http://docs.oracle.com/javase/tutorial/uiswing/components/applet.html">Applet</a></li>
<li><a href="http://docs.oracle.com/javase/tutorial/uiswing/components/panel.html">Panel</a></li>
</ul>
</li>
<li>
<p><a href="http://docs.oracle.com/javase/tutorial/uiswing/components/menu.html">Menu</a></p>
</li>
<li>
<p><a href="http://docs.oracle.com/javase/tutorial/uiswing/layout/using.html">Layout Manager</a></p>
<ul>
<li><a href="http://docs.oracle.com/javase/tutorial/uiswing/layout/border.html">Border Layout</a></li>
<li><a href="http://docs.oracle.com/javase/tutorial/uiswing/layout/grid.html">Grid Layout</a></li>
<li>Flow Layout</li>
</ul>
</li>
<li>
<p>Simple Component</p>
<ul>
<li>Text Field</li>
<li>Button</li>
<li>Checkbox</li>
<li>Radio Button</li>
</ul>
</li>
<li>
<p><a href="http://docs.oracle.com/javase/tutorial/uiswing/events/index.html">Event Handling</a></p>
<ul>
<li>ActionEvent</li>
<li>MouseEvent</li>
</ul>
</li>
<li>
<p>Complex Component</p>
<ul>
<li>Combo/Select</li>
<li>List</li>
</ul>
</li>
<li>
<p>Swing Table</p>
<ul>
<li>JTable</li>
<li>TableModel</li>
</ul>
</li>
<li>
<p><a href="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrent Execution</a></p>
<ul>
<li>Swing Worker</li>
<li>Progress Bar</li>
</ul>
</li>
<li>
<p>Internationalization</p>
</li>
<li>Jasper Report
<ul>
<li>Parameter</li>
<li>Variable</li>
<li>Report Datasource</li>
</ul>
</li>
<li>
<p>iReport</p>
</li>
<li>
<p>Studi Kasus</p>
</li>
<li>
<p>Requirement Specification</p>
<ul>
<li>Daftar Fitur</li>
<li>Arsitektur</li>
<li>Desain UI</li>
<li>Desain Database</li>
</ul>
</li>
<li>Implementasi Frame dan Menu</li>
<li>Implementasi Dummy Screen</li>
<li>Implementasi Proses Bisnis</li>
<li>Implementasi Akses Database</li>
<li>Implementasi Report</li>
</ul>
<p><a name="pemrograman-4"></a></p>
<h2 id="pemrograman-4--aplikasi-web">Pemrograman 4 : Aplikasi Web</h2>
<h3 id="tujuan-3">Tujuan</h3>
<ul>
<li>Bisa membuat aplikasi web dengan arsitektur REST</li>
<li>Bisa menggunakan penyimpanan data non-relasional</li>
</ul>
<h3 id="referensi-2">Referensi</h3>
<ul>
<li><a href="http://software.endy.muhardin.com/java/development-stack-2014/">Development Stack ArtiVisi</a></li>
</ul>
<h3 id="kebutuhan-software-lab-3">Kebutuhan Software Lab</h3>
<ul>
<li>Sama seperti Pemrograman 2</li>
<li><a href="http://software.endy.muhardin.com/javascript/persiapan-coding-nodejs/">NodeJS dan Yeoman</a></li>
</ul>
<h3 id="materi-kuliah-3">Materi Kuliah</h3>
<ul>
<li>Java Web Application</li>
<li>Servlet, Filter, Listener</li>
<li>JSP & JSTL</li>
<li>Security</li>
<li>REST</li>
<li>AngularJS dan Twitter Bootstrap</li>
<li>Redis</li>
<li>MongoDB</li>
<li>Case Study</li>
</ul>
JavaScript Development Stack2014-02-27T21:11:00+07:00https://software.endy.muhardin.com/javascript/javascript-development-stack<p>Pada <a href="http://software.endy.muhardin.com/javascript/persiapan-coding-nodejs/">artikel sebelumnya</a>, kita telah sukses menyiapkan kombinasi framework di sisi server. Nah sekarang kita akan siapkan persenjataan untuk aplikasi clientnya.</p>
<p>Artikel bagian kedua ini berselang waktu agak lama dari artikel sebelumnya karena saya bingung :D
Nah saya yakin kebingungan ini juga akan dialami oleh rekan-rekan yang baru belajar server-side-javascript. Ada beberapa hal yang membuat saya bingung, yaitu:</p>
<ul>
<li>arsitektur aplikasi dan bahasa pemrograman</li>
<li>development tools</li>
<li>integrasi aplikasi pada saat development</li>
<li>integrasi aplikasi pada saat production</li>
</ul>
<p>Sebagai gambaran, bentuk akhir dari aplikasi yang akan dibuat seperti ini</p>
<p><a href="https://lh4.googleusercontent.com/-a6DbvzDCjUA/Uw7xgeieBQI/AAAAAAAAFR0/fIac8wOIBGg/w844-h597-no/mean-stack.png"><img src="https://lh4.googleusercontent.com/-a6DbvzDCjUA/Uw7xgeieBQI/AAAAAAAAFR0/fIac8wOIBGg/w844-h597-no/mean-stack.png" alt="Foto" /></a></p>
<!--more-->
<h2 id="arsitektur-aplikasi">Arsitektur Aplikasi</h2>
<p>Biasanya kalau kita membuat aplikasi web modern dengan menggunakan AJAX, begini arsitekturnya:</p>
<p><a href="https://lh3.googleusercontent.com/-5tGzUVc2lk4/UwFrrT20hmI/AAAAAAAAFJk/zRxIxumf_O4/w800-h566-no/restful-architecture.png"><img src="https://lh3.googleusercontent.com/-5tGzUVc2lk4/UwFrrT20hmI/AAAAAAAAFJk/zRxIxumf_O4/w800-h566-no/restful-architecture.png" alt="Foto" /></a></p>
<p>Aplikasi akan terbagi menjadi dua bagian (client side dan server side) yang jelas batasannya, yaitu:</p>
<ul>
<li>server side dibuat menggunakan PHP/Java/Ruby/Python/dsb</li>
<li>client side dibuat menggunakan framework JavaScript (AngularJS, EmberJS, jQuery, Dojo, ExtJS, dsb)</li>
</ul>
<p>Kalau kita membuat aplikasi menggunakan NodeJS, maka semuanya dibuat menggunakan bahasa pemrograman JavaScript. Ini bisa membuat kita bingung, mana client side mana server side, karena semuanya JS. Supaya tidak bingung, kita anggap saja NodeJS ini adalah PHP. Dia hanya bisa jalan di server saja, tidak bisa jalan di browser. Dengan cara berpikir seperti ini, skema berikut ini</p>
<p><a href="https://lh3.googleusercontent.com/-rdH4DqYrios/Uw7xQi5JTUI/AAAAAAAAFRk/U1AOrcjH4yM/w522-h216-no/lamp-stack.png"><img src="https://lh3.googleusercontent.com/-rdH4DqYrios/Uw7xQi5JTUI/AAAAAAAAFRk/U1AOrcjH4yM/w522-h216-no/lamp-stack.png" alt="Foto" /></a></p>
<p>sama saja dengan ini</p>
<p><a href="https://lh3.googleusercontent.com/-AQfudbu3qXo/Uw7xRPvnQYI/AAAAAAAAFRs/G1pkS6S3uvM/w522-h216-no/mean-stack.png"><img src="https://lh3.googleusercontent.com/-AQfudbu3qXo/Uw7xRPvnQYI/AAAAAAAAFRs/G1pkS6S3uvM/w522-h216-no/mean-stack.png" alt="Foto" /></a></p>
<p>Kita juga harus membedakan framework JavaScript client-side seperti:</p>
<ul>
<li><a href="http://angularjs.org/">AngularJS</a></li>
<li><a href="http://emberjs.com">EmberJS</a></li>
<li><a href="http://www.sencha.com/products/extjs">ExtJS</a></li>
<li><a href="http://dojotoolkit.org">Dojo</a></li>
<li>dsb</li>
</ul>
<p>dan framework JavaScript server-side seperti:</p>
<ul>
<li><a href="http://expressjs.com">ExpressJS</a></li>
<li><a href="http://sailsjs.org">SailsJS</a></li>
<li><a href="http://towerjs.org">TowerJS</a></li>
<li><a href="http://locomotivejs.org">Locomotive</a></li>
<li>dsb</li>
</ul>
<p>Setelah jelas mengenai arsitektur, kebingungan selanjutnya adalah masalah tools</p>
<h2 id="development-tools">Development Tools</h2>
<p>Urusan tools ini cukup membuat saya pusing beberapa hari, padahal saya sudah ada pengalaman sebelumnya dengan Maven, Jenkins, Subversion, Git, JUnit, dan kawan-kawannya. Di dunia JavaScript ada banyak tools yang digunakan, yaitu:</p>
<ul>
<li><a href="https://www.npmjs.org/">npm</a></li>
<li><a href="http://yeoman.io/">yeoman</a></li>
<li><a href="http://bower.io/">bower</a></li>
<li><a href="http://gruntjs.com/">grunt</a></li>
</ul>
<p>Mari kita bahas satu persatu.</p>
<h3 id="npm">NPM</h3>
<p>NPM adalah singkatan dari NodeJS Package Manager. Agar lebih mudah dipahami, kita lihat padanannya di platform lain:</p>
<ul>
<li>Ruby : gem</li>
<li>Java : Maven Repository</li>
<li>Ubuntu : apt-get</li>
<li>Python : pip</li>
<li>Android : Play Store</li>
</ul>
<p>Kita menginstal library JavaScript dengan perintah <code class="language-plaintext highlighter-rouge">npm install</code>. Instalasi ini ada yang bersifat global di seluruh komputer, ataupun local di masing-masing project. Untuk menginstal secara global, kita membutuhkan hak akses penuh, sehingga harus menggunakan <code class="language-plaintext highlighter-rouge">sudo</code> di Ubuntu.</p>
<p>Contohnya, untuk menginstal framework SailsJS, kita jalankan <code class="language-plaintext highlighter-rouge">sudo npm install -g sails</code>. Untuk menginstal Yeoman, perintahnya adalah <code class="language-plaintext highlighter-rouge">sudo npm install -g yo</code>.</p>
<blockquote>
<p>Warning: siapkan koneksi internet yang mumpuni pada saat menjalankan <code class="language-plaintext highlighter-rouge">npm install</code>.
Dia akan mengunduh paket yang kita minta dari internet berikut semua paket lain yang dibutuhkan.</p>
</blockquote>
<h3 id="yeoman">Yeoman</h3>
<p>Yeoman adalah generator aplikasi. Dia menyediakan template project dengan berbagai kombinasi library. Di Java, padanannya adalah Maven Archetype, Appfuse, atau Spring Roo. Ruby on Rails dan Groovy juga memiliki fitur generator ini.</p>
<p>Contohnya, bila kita ingin membuat aplikasi web dengan AngularJS dan Twitter Bootstrap, kita jalankan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir aplikasi-saya
cd aplikasi-saya
yo angular
</code></pre></div></div>
<p>Yeoman akan membuatkan struktur folder dan beberapa contoh file sesuai dengan <em>best practices</em> masing-masing teknologi yang dipilih.</p>
<p>Yeoman bekerja sama dengan dua tools lain, yaitu Bower dan Grunt.</p>
<h3 id="bower">Bower</h3>
<p>Bower mirip dengan NPM, yaitu bertugas menginstal sesuatu.</p>
<blockquote>
<p>Waduh, kenapa ada dua tools yang fungsinya sama?</p>
</blockquote>
<p>Jangan khawatir, banyak orang menanyakan hal yang sama. Diskusinya bisa dibaca di thread SO <a href="http://stackoverflow.com/questions/15092345/javascript-dependency-management-npm-vs-bower-vs-volo">ini</a> dan <a href="http://stackoverflow.com/questions/18641899/difference-between-bower-and-npm">ini</a>. Ada juga <a href="http://tech.pro/tutorial/1190/package-managers-an-introductory-guide-for-the-uninitiated-front-end-developer">penjelasan panjang lebar tentang penggunaan keduanya</a>.</p>
<p>Untuk menyederhanakan masalah, berikut kesimpulan saya</p>
<blockquote>
<p>NPM digunakan untuk mengelola library javascript sedangkan Bower digunakan untuk mengelola kelengkapan aplikasi web (js, css, png).</p>
</blockquote>
<h3 id="grunt">Grunt</h3>
<p>Grunt adalah library scripting untuk melakukan build process. Di Java padanannya kira-kira adalah <code class="language-plaintext highlighter-rouge">ant</code>. Di Ruby padanannya <code class="language-plaintext highlighter-rouge">rake</code>.</p>
<p>Grunt memiliki fitur antara lain:</p>
<ul>
<li>compile/minify/compress kode program JavaScript, baik yang kita tulis sendiri maupun yang dibuat orang lain dan kita gunakan dalam aplikasi kita (seperti jQuery, underscore.js, dsb)</li>
<li>menjalankan test otomatis</li>
<li>menjalankan server process, proxy, dan sejenisnya</li>
</ul>
<h2 id="kombinasi-stack">Kombinasi Stack</h2>
<p>Nah sekarang setelah peta situasi sudah jelas, tiba saatnya kita memilih framework.</p>
<p>Di sisi client, saya akan tetap menggunakan kombinasi andalan, yaitu AngularJS dan Bootstrap. Bagaimana dengan di sisi server?</p>
<p>Hasil blusukan di Google mengarahkan saya ke framework paling populer di dunia NodeJS, yaitu ExpressJS. Bagi yang suka minimalis, ExpressJS ini sudah cukup. Walaupun demikian, saya ketemu pengembangan dari ExpressJS yang sudah ditambahi segala macam fitur validasi, REST API, scaffolding, dan fitur-fitur lain untuk mempercepat development, yaitu SailsJS. Dia juga sudah dilengkapi dengan ORM untuk berinteraksi dengan database.</p>
<p>Agar tidak berlama-lama, mari kita pilih saja SailsJS untuk keperluan belajar ini. Nanti kalau sudah jadi satu aplikasi, sudah tahu manis-pahitnya SailsJS, baru kita punya patokan untuk mengevaluasi framework kompetitornya.</p>
<h3 id="integrasi-client---server">Integrasi Client - Server</h3>
<p>Di sisi client ada Yeoman dan di sisi server ada Sails yang juga memiliki fitur generator. Kedua tools ini memiliki persamaan fitur dalam kaitannya dengan proses/workflow development, yaitu:</p>
<ul>
<li>generate template file</li>
<li>kompresi aset (js, css)</li>
<li>menjalankan tes otomatis</li>
<li>embedded web server untuk keperluan testing di local</li>
<li>dependency management untuk add/remove/upgrade library</li>
</ul>
<blockquote>
<p>Mau pakai yang mana?</p>
</blockquote>
<p>Untuk menjawab pertanyaan ini, kita harus sedikit <a href="http://sethgodin.typepad.com/seths_blog/2005/03/dont_shave_that.html">yak-shaving</a> dulu melihat bagaimana nantinya aplikasi kita akan dideploy ke lingkungan production. Dari sini, kita akan membuat lingkungan development semirip mungkin. Tentunya walaupun diusahakan mirip, kita ingin proses coding seefisien mungkin supaya siklus feedbacknya cepat.</p>
<p>Ada beberapa tujuan yang ingin saya capai:</p>
<ul>
<li>aplikasi server (SailJS) dan aplikasi client (Yeoman) ingin disimpan dalam satu repository</li>
<li>kedua aplikasi akan dideploy sebagai satu kesatuan pada waktu production. Kedua aplikasi harus berada di satu folder</li>
<li>masing-masing aplikasi harus bisa dikerjakan dan dites secara independen</li>
<li>kita menggunakan tools terbaik untuk masing-masing aplikasi. Artinya, untuk aplikasi client tetap menggunakan Yeoman dan untuk aplikasi server tetap menggunakan SailJS.</li>
</ul>
<p>Blusukan lagi di google, ketemu dua artikel bagus. Yang satu membahas <a href="http://www.slideshare.net/BenLin2/webconf-nodejsproductionarchitecture">deployment production dan tahapan skalabilitas</a> mulai dari aplikasinya dipakai sedikit orang hingga jutaan orang.</p>
<p>Satu lagi membahas tentang <a href="http://www.emmanueloga.com/2013/07/23/Using-AngularJS-with-a-Rails-backend.html">skema workflow selama development</a>. Skema development ini sebetulnya tidak terlalu mirip dengan kombinasi stack yang kita pakai, karena dia menggunakan AngularJS dan Rails sedangkan kita AngularJS dan NodeJS. Walaupun demikian, struktur aplikasinya sama dan skema deploymentnya konsisten dengan yang dijelaskan di artikel pertama.</p>
<p>Berikut adalah arsitektur deployment di lingkungan production yang kita tuju</p>
<p><a href="https://lh5.googleusercontent.com/-45qcTjWcens/Uw8ehKdmLOI/AAAAAAAAFSo/ZQiJqKzeZrk/w734-h550-no/deployment-production.png"><img src="https://lh5.googleusercontent.com/-45qcTjWcens/Uw8ehKdmLOI/AAAAAAAAFSo/ZQiJqKzeZrk/w734-h550-no/deployment-production.png" alt="Foto" /></a></p>
<p>Dan ini adalah skema deployment di laptop masing-masing programmer</p>
<p><a href="https://lh4.googleusercontent.com/-LvEC29ahKfs/Uw8ehH3uMbI/AAAAAAAAFSs/QzxyjTUSWQ8/w386-h550-no/deployment-development.png"><img src="https://lh4.googleusercontent.com/-LvEC29ahKfs/Uw8ehH3uMbI/AAAAAAAAFSs/QzxyjTUSWQ8/w386-h550-no/deployment-development.png" alt="Foto" /></a></p>
<blockquote>
<p>Cukup mirip kan?</p>
</blockquote>
<p>Segala riset dan artikel di atas menghasilkan kesimpulan berikut:</p>
<ul>
<li>struktur folder project</li>
<li>strategi integrasi</li>
<li>deployment baik di development maupun production.</li>
</ul>
<p>Berikut adalah kesimpulan saya untuk struktur folder project:</p>
<ul>
<li>aplikasi akan terdiri dari dua folder : xxx-server (berisi SailsJS) dan xxx-client (berisi AngularJS dkk dimanage dengan Yeoman)</li>
<li>untuk keperluan test di laptop, aplikasi client akan dijalankan terpisah dengan server. Aplikasi client dijalankan dengan perintah <code class="language-plaintext highlighter-rouge">grunt serve</code> dan akan standby di port <code class="language-plaintext highlighter-rouge">9000</code>. Aplikasi server dijalankan menggunakan perintah <code class="language-plaintext highlighter-rouge">sails lift</code> dan standby di port <code class="language-plaintext highlighter-rouge">1337</code>. Dengan ini, aplikasi client dan server bisa dikerjakan dan dites secara terpisah</li>
<li>pada saat dibutuhkan integration test (client dan server sudah terhubung), kita konfigurasi Grunt agar request ke url <code class="language-plaintext highlighter-rouge">/api/*</code> diteruskan ke SailsJS di port <code class="language-plaintext highlighter-rouge">1337</code></li>
<li>pada proses build aplikasi client, arahkan hasil build ke folder xxx-server/static</li>
</ul>
<h2 id="menyiapkan-aplikasi">Menyiapkan Aplikasi</h2>
<p>Ada satu prinsip yang saya pegang dalam dunia pemrograman,</p>
<blockquote>
<p>Jangan mulai coding sebelum jelas apa yang mau dibuat.</p>
</blockquote>
<p>Itulah sebabnya saya menghabiskan waktu puluhan jam mencari artikel di Google, membacanya satu persatu, menonton video tutorial dari Youtube, dan mencoba coding sedikit-sedikit agar mendapatkan gambaran. Semua usaha tersebut dilakukan untuk mencari tahu bagaimana best-practices dalam membuat aplikasi. Setelah jelas apa yang kita ingin capai dan strategi untuk tiba di tujuan, barulah kita mulai coding.</p>
<p>Apa yang kita bahas pada bagian ini adalah hal yang mudah. Bagian sulitnya ada di penjelasan arsitektur aplikasi di atas. Tidak percaya? Mari kita mulai.</p>
<h3 id="aplikasi-server">Aplikasi Server</h3>
<p>Kita mulai dari aplikasi server dulu. Langkah-langkahnya sebagai berikut:</p>
<ul>
<li>instalasi framework SailsJS</li>
<li>buat folder aplikasi-membership-server</li>
<li>generate struktur aplikasi menggunakan fitur SailsJS</li>
</ul>
<p>Kita mulai dengan instalasi SailsJS. Ini dilakukan sekali saja dalam satu komputer. Untuk aplikasi kedua dan seterusnya, kita tidak perlu lagi menginstal SailsJS</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo npm install -g sails
</code></pre></div></div>
<p>NPM akan mengunduh segala macam paket yang dibutuhkan dari internet. Siapkan koneksi internet yang mumpuni. Setelah selesai, kita bisa membuat project baru.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sails new aplikasi-membership-server
cd aplikasi-membership-server
sails lift
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sails lift
info:
info:
info: Sails.js <|
info: v0.9.9 |\
info: /|.\
info: / || \
info: ,' |' \
info: .-'.-==|/_--'
info: `--'-------'
info: __---___--___---___--___---___--___
info: ____---___--___---___--___---___--___-__
info:
info: Server lifted in `aplikasi-membership-server`
info: To see your app, visit http://localhost:1337
info: To shut down Sails, press <CTRL> + C at any time.
debug: --------------------------------------------------------
debug: :: Thu Feb 27 2014 19:06:32 GMT+0700 (WIB)
debug:
debug: Environment : development
debug: Port : 1337
debug: --------------------------------------------------------
</code></pre></div></div>
<p>Kita bisa browse ke <code class="language-plaintext highlighter-rouge">http://localhost:1337</code></p>
<p><a href="https://lh6.googleusercontent.com/-lQ4xghosjJY/Uw9HAoW29FI/AAAAAAAAFTY/bitPnyw7nBk/w909-h573-no/01-test-sails-ok.png"><img src="https://lh6.googleusercontent.com/-lQ4xghosjJY/Uw9HAoW29FI/AAAAAAAAFTY/bitPnyw7nBk/w909-h573-no/01-test-sails-ok.png" alt="Foto" /></a></p>
<p>Selesai sudah pembuatan aplikasi server. Mudah bukan?</p>
<p>Kita bisa lihat isi folder <code class="language-plaintext highlighter-rouge">aplikasi-membership-server</code>, di sana sudah banyak file dan folder yang dibuatkan oleh SailsJS. Apa fungsi dan kegunaan masing-masingnya akan kita bahas di artikel terpisah.</p>
<p>Sekarang kita lanjutkan dengan aplikasi client.</p>
<h3 id="aplikasi-client">Aplikasi Client</h3>
<p>Kita akan gunakan Yeoman untuk membuatkan aplikasi client. Karena kita ingin menggunakan AngularJS dan Bootstrap, kita instal dulu generator yang sesuai. Sama seperti SailsJS, instalasi ini cukup sekali saja dalam satu komputer.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo npm install -g generator-angular
</code></pre></div></div>
<p>Berikutnya, kita buat foldernya dan generate struktur project AngularJS dan Bootstrap</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir aplikasi-membership-ui
cd aplikasi-membership-ui
yo angular
_-----_
| |
|--(o)--| .--------------------------.
`---------´ | Welcome to Yeoman, |
( _´U`_ ) | ladies and gentlemen! |
/___A___\ '__________________________'
| ~ |
__'.___.'__
´ ` |° ´ Y `
Out of the box I include Bootstrap and some AngularJS recommended modules.
[?] Would you like to use Sass (with Compass)? No
[?] Would you like to include Twitter Bootstrap? Yes
[?] Which modules would you like to include? (Press <space> to select)
‣⬢ angular-resource.js
⬢ angular-cookies.js
⬢ angular-sanitize.js
⬢ angular-route.js
</code></pre></div></div>
<p>Yeoman akan menanyakan apakah kita ingin menggunakan Sass dan Bootstrap. Saya tidak ingin menggunakan Sass, karena dia mengharuskan kita untuk menginstal Ruby on Rails. Nah, Ruby on Rails ini <em>yak shaving</em> lagi, jadi tidak usah saja.</p>
<p>Kita juga akan disodori pilihan modul angular mana yang ingin dipakai. Saya pilih saja semuanya. Tekan Enter, dan Yeoman akan mengunduh segala macam hal dari internet. Pastikan koneksi internet Anda memadai.</p>
<p>Setelah selesai, kita bisa coba menjalankan Grunt server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grunt serve
Running "serve" task
Running "clean:server" (clean) task
Cleaning .tmp...OK
Running "bower-install:app" (bower-install) task
Running "concurrent:server" (concurrent) task
Running "copy:styles" (copy) task
Copied 1 files
Done, without errors.
Execution Time (2014-02-27 12:14:54 UTC)
loading tasks 4ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 29%
copy:styles 9ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 64%
Total 14ms
Running "autoprefixer:dist" (autoprefixer) task
Prefixed file ".tmp/styles/main.css" created.
Running "connect:livereload" (connect) task
Started connect web server on 127.0.0.1:9000.
Running "watch" task
Waiting...
</code></pre></div></div>
<p>Browser akan terbuka dan mengarah ke <code class="language-plaintext highlighter-rouge">http://127.0.0.1:9000</code>. Ini merupakan fitur LiveReload dari Grunt. Semua perubahan yang kita buat di source code akan langsung tampil di browser tanpa perlu refresh ataupun restart.</p>
<p><a href="https://lh3.googleusercontent.com/-BIe-5q4gFYA/Uw9HAoUTHAI/AAAAAAAAFTc/wRPt39Kk9Bs/w909-h573-no/02-test-grunt-ok.png"><img src="https://lh3.googleusercontent.com/-BIe-5q4gFYA/Uw9HAoUTHAI/AAAAAAAAFTc/wRPt39Kk9Bs/w909-h573-no/02-test-grunt-ok.png" alt="Foto" /></a></p>
<p>Untuk mematikannya, gunakan Ctrl-C</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>^C
Execution Time (2014-02-27 12:14:52 UTC)
concurrent:server 1.7s ▇▇▇ 5%
watch 33.2s ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 95%
Total 35s
</code></pre></div></div>
<p>Selesai sudah template aplikasi client. Selanjutnya tinggal rapi-rapi sedikit.</p>
<h3 id="integrasi-client---server-1">Integrasi Client - Server</h3>
<p>Sekarang kita sudah punya dua folder dalam project:</p>
<ul>
<li>aplikasi-membership-server</li>
<li>aplikasi-membership-client</li>
</ul>
<p>Masing-masing aplikasi memiliki <code class="language-plaintext highlighter-rouge">.gitignore</code> sendiri. Sebaiknya kita gabungkan menjadi satu. Buat file <code class="language-plaintext highlighter-rouge">.gitignore</code> sejajar dengan kedua folder tersebut. Copy-paste isi dari file <code class="language-plaintext highlighter-rouge">.gitignore</code> di folder client dan server. Hapus yang duplikat, dan perbaiki referensi lokasi absolut.</p>
<p>Berikutnya, kita arahkan hasil kompilasi project client ke dalam folder <code class="language-plaintext highlighter-rouge">static</code> dalam project server, supaya seisi project server bisa langsung dideploy ke production. Ini kita lakukan dengan cara mengedit konfigurasi Yeoman dalam <code class="language-plaintext highlighter-rouge">Gruntfile.js</code>, yang awalnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yeoman: {
// configurable paths
app: require('./bower.json').appPath || 'app',
dist: 'dist'
},
</code></pre></div></div>
<p>menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yeoman: {
// configurable paths
app: require('./bower.json').appPath || 'app',
dist: '../aplikasi-membership-server/static'
},
</code></pre></div></div>
<p>Jangan lupa menambahkan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aplikasi-membership-server/static
</code></pre></div></div>
<p>dalam <code class="language-plaintext highlighter-rouge">.gitignore</code>, agar hasil kompilasi tidak ikut dicommit ke repository Git.</p>
<p>Kita bisa mengetes hasil kompilasi Grunt. Harusnya dia akan membuat folder static dalam aplikasi server. Jalankan perintah berikut dalam folder aplikasi client</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CHROME_BIN=chromium-browser grunt
</code></pre></div></div>
<p>Dia akan melakukan kompilasi, menjalankan test, kompresi js dan css, kemudian menaruh hasilnya di folder tujuan. Selama test dijalankan, kita akan melihat ada window Chrome yang terbuka dan kemudian tertutup lagi. Bila kita tidak ingin tesnya menggunakan browser betulan, kita bisa pakai <code class="language-plaintext highlighter-rouge">PhantomJS</code> untuk menjalankan semua kode HTML dan JavaScript tanpa browser.</p>
<p>PhantomJS bisa diaktifkan dalam file konfigurasi <code class="language-plaintext highlighter-rouge">karma.conf.js</code>. Edit baris berikut</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Start these browsers, currently available:</span>
<span class="c1">// - Chrome</span>
<span class="c1">// - ChromeCanary</span>
<span class="c1">// - Firefox</span>
<span class="c1">// - Opera</span>
<span class="c1">// - Safari (only Mac)</span>
<span class="c1">// - PhantomJS</span>
<span class="c1">// - IE (only Windows)</span>
<span class="nx">browsers</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">Chrome</span><span class="dl">'</span><span class="p">],</span>
</code></pre></div></div>
<p>Menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
</code></pre></div></div>
<p>Bila PhantomJS belum ada, instal dulu menggunakan perintah <code class="language-plaintext highlighter-rouge">sudo npm install -g phantomjs</code>.</p>
<p>Setelah rangkaian kegiatan yang dilakukan Grunt selesai, pastikan hasilnya ada dalam aplikasi server</p>
<p><a href="https://lh3.googleusercontent.com/-kZ4ijjT4TAk/Uw9HB0thj3I/AAAAAAAAFTk/aKJJLsMglMk/w609-h597-no/04-test-compile-ok.png"><img src="https://lh3.googleusercontent.com/-kZ4ijjT4TAk/Uw9HB0thj3I/AAAAAAAAFTk/aKJJLsMglMk/w609-h597-no/04-test-compile-ok.png" alt="Foto" /></a></p>
<p>Terakhir, kita aktifkan proxy agar url <code class="language-plaintext highlighter-rouge">api/*</code> yang mengarah ke server Grunt diteruskan ke SailsJS di port <code class="language-plaintext highlighter-rouge">1337</code>. Proxy ini membutuhkan tambahan package <code class="language-plaintext highlighter-rouge">grunt-connect-proxy</code>. Install menggunakan <code class="language-plaintext highlighter-rouge">npm</code> dalam aplikasi client</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd aplikasi-membership-client
npm install grunt-connect-proxy --save-dev
</code></pre></div></div>
<p>Perintah di atas akan mengunduh package <code class="language-plaintext highlighter-rouge">grunt-connect-proxy</code> dari internet dan menambahkan dependensinya ke <code class="language-plaintext highlighter-rouge">package.json</code>.</p>
<p>Selanjutnya kita edit file <code class="language-plaintext highlighter-rouge">Gruntfile.js</code>. Ada beberapa bagian yang harus kita ubah:</p>
<ul>
<li>
<p>aktifasi modul <code class="language-plaintext highlighter-rouge">grunt-connect-proxy</code>. Tambahkan baris berikut di bawah <code class="language-plaintext highlighter-rouge">use strict</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest;
</code></pre></div> </div>
</li>
<li>
<p>konfigurasi URL yang akan diproxy. Lokasinya di dalam blok <code class="language-plaintext highlighter-rouge">connect</code> sejajar dengan blok <code class="language-plaintext highlighter-rouge">options</code>.</p>
</li>
</ul>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">options</span><span class="p">:</span> <span class="p">{</span>
<span class="nl">port</span><span class="p">:</span> <span class="mi">9000</span><span class="p">,</span>
<span class="c1">// Change this to '0.0.0.0' to access the server from outside.</span>
<span class="nx">hostname</span><span class="p">:</span> <span class="dl">'</span><span class="s1">localhost</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">livereload</span><span class="p">:</span> <span class="mi">35729</span>
<span class="p">},</span>
<span class="nx">proxies</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">context</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/api</span><span class="dl">'</span><span class="p">,</span>
<span class="na">host</span><span class="p">:</span> <span class="dl">'</span><span class="s1">localhost</span><span class="dl">'</span><span class="p">,</span>
<span class="na">port</span><span class="p">:</span> <span class="mi">1337</span>
<span class="p">}</span>
<span class="p">],</span>
</code></pre></div></div>
<ul>
<li>Aktivasi proxy dalam <code class="language-plaintext highlighter-rouge">middleware</code></li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>livereload: {
options: {
open: true,
base: [
'.tmp',
'<%= yeoman.app %>'
],
middleware: function (connect) {
return [
proxySnippet,
connect.static(require('path').resolve('app'))
];
}
}
},
</code></pre></div></div>
<ul>
<li>Panggil konfigurasi proxy pada saat server Grunt dijalankan</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grunt.task.run([
'clean:server',
'bower-install',
'configureProxies',
'concurrent:server',
'autoprefixer',
'connect:livereload',
'watch'
]);
</code></pre></div></div>
<p>Sekarang kita tes konfigurasi proxy dengan cara browse ke <code class="language-plaintext highlighter-rouge">http://localhost:9000/api</code>. Request ini akan diterima server Grunt dan akan diteruskan ke server Sails. Jadi harusnya request ini akan dilayani oleh Sails.</p>
<p><a href="https://lh4.googleusercontent.com/-h-d7K0XO2x8/Uw9HAo4Ey4I/AAAAAAAAFTU/VRgJIDvUqxM/w909-h573-no/03-test-proxy-ok.png"><img src="https://lh4.googleusercontent.com/-h-d7K0XO2x8/Uw9HAo4Ey4I/AAAAAAAAFTU/VRgJIDvUqxM/w909-h573-no/03-test-proxy-ok.png" alt="Foto" /></a></p>
<p>Tampak pesan error disana. Tidak apa-apa, kita akan perbaiki nanti. Yang penting sudah jelas bahwa request tersebut dilayani oleh Sails, bukan oleh Grunt.</p>
<p>Untuk lebih jelas mengenai skema proxy ini, silahkan baca <a href="http://rockyj.in/2013/10/24/angular_rails.html">penjelasan om Rocky Jaiswal</a></p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Wow, cukup panjang dan melelahkan juga ya. Beberapa poin kesimpulan:</p>
<ul>
<li>
<p>teknologi yang kita gunakan dibagi dua: sisi client dan sisi server</p>
</li>
<li>
<p>library di sisi client:</p>
<ul>
<li>AngularJS</li>
<li>Twitter Bootstrap</li>
</ul>
</li>
<li>
<p>tools untuk membuat aplikasi client:</p>
<ul>
<li>yeoman</li>
<li>bower</li>
<li>grunt</li>
</ul>
</li>
<li>
<p>library di sisi server:</p>
<ul>
<li>sails</li>
<li>redis dan hiredis</li>
</ul>
</li>
<li>
<p>tools untuk membuat aplikasi server:</p>
<ul>
<li>sails</li>
<li>foreman (untuk mengetes deployment ke heroku)</li>
<li>heroku</li>
</ul>
</li>
<li>untuk menghubungkan aplikasi client dan server di development : <a href="http://fettblog.eu/blog/2013/09/20/using-grunt-connect-proxy/">pasang proxy di Grunt server</a></li>
<li>untuk menghubungkan aplikasi client dan server di production : gunakan konfigurasi <code class="language-plaintext highlighter-rouge">proxy_pass</code> yang ada di Nginx</li>
<li>untuk menggabungkan deployment client dan server : arahkan hasil kompilasi Grunt ke folder <code class="language-plaintext highlighter-rouge">static</code> dalam aplikasi server</li>
</ul>
<p>Beberapa link tutorial untuk deployment berskala production</p>
<ul>
<li><a href="http://blog.argteam.com/coding/hardening-node-js-for-production-part-2-using-nginx-to-avoid-node-js-load/">Konfigurasi Nginx: proxy, SSL, cache, gzip</a></li>
<li><a href="http://blog.dealspotapp.com/post/40184153657/node-js-production-deployment-with-nginx-varnish">Deployment Nginx, Varnish, Upstart, dan Monit</a></li>
<li><a href="http://www.devopsdiary.com/blog/2013/05/16/deploying-node-dot-js-plus-nginx-plus-upstart-plus-monit-on-ec2-with-puppet-and-vagrant/">Deployment Nginx, Upstart, Monit, Redis di Amazon EC2 dengan Puppet dan Vagrant</a></li>
</ul>
Persiapan Coding NodeJS2014-02-21T15:43:00+07:00https://software.endy.muhardin.com/javascript/persiapan-coding-nodejs<p>Menyambung dari pengantar di <a href="http://software.endy.muhardin.com/life/ongoing-learning/">artikel sebelumnya</a>, kali ini kita akan mempersiapkan development environment untuk membuat aplikasi dengan NodeJS dan Redis. Kita juga akan mendeploy aplikasi kita di Heroku agar bisa diakses masyarakat umum.</p>
<!--more-->
<h2 id="instalasi">Instalasi</h2>
<h3 id="node-js">Node JS</h3>
<p>NodeJS adalah yang pertama kita install karena dia memiliki Node Package Manager (NPM) yang nantinya akan digunakan oleh framework/library lainnya.</p>
<p>Cara instalasi termudah di Ubuntu adalah dengan menambahkan <a href="https://launchpad.net/~chris-lea/+archive/node.js">PPA (Personal Package Archive) NodeJS milik Chris Lea</a>. Jalankan perintah berikut di command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs
</code></pre></div></div>
<h3 id="heroku">Heroku</h3>
<p><a href="http://www.heroku.com/">Heroku</a> adalah penyedia layanan cloud untuk menjalankan aplikasi. Generasi jadul seperti saya menyebutnya perusahaan hosting ;) Heroku menyediakan hosting gratis untuk aplikasi kecil yang bisa kita gunakan untuk bermain-main dengan teknologinya. Silahkan <a href="https://id.heroku.com/signup/devcenter">daftar dulu</a>, kemudian <a href="https://toolbelt.heroku.com/">unduh aplikasi clientnya</a>.</p>
<p>Setelah terinstal, kita harus melakukan login menggunakan aplikasi client agar bisa membuat dan mendeploy aplikasi. Kita juga akan diminta untuk mengatur SSH key yang akan digunakan untuk otentikasi dengan Heroku. Bila Anda belum paham apa itu SSH key dan bagaimana cara membuatnya, silahkan baca dulu <a href="http://software.endy.muhardin.com/linux/login-ssh-dengan-private-key/">artikel ini</a>.</p>
<h3 id="redis">Redis</h3>
<p>Sama seperti instalasi NodeJS, untuk menginstal Redis kita perlu menambahkan PPA dulu. Berikut perintahnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo add-apt-repository ppa:rwky/redis
sudo apt-get update
sudo apt-get install redis-server
</code></pre></div></div>
<h3 id="library-lain">Library Lain</h3>
<p>Nantinya kita akan menggunakan library lain seperti misalnya:</p>
<ul>
<li>ExpressJS</li>
<li>Yeoman</li>
<li>dsb</li>
</ul>
<p>Tapi untuk sementara ini kita belum membutuhkannya. Jadi petunjuk instalasinya akan kita bahas nanti pada waktu diperlukan.</p>
<h2 id="hello-world">Hello World</h2>
<p>Mengikuti tradisi yang telah diwariskan turun temurun oleh leluhur kita, langkah pertama dalam mempelajari suatu bahasa pemrograman adalah menampilkan tulisan <code class="language-plaintext highlighter-rouge">Hello World</code>.</p>
<h3 id="clone-repository">Clone Repository</h3>
<p>Kita clone dulu repository yang telah kita buat di artikel sebelumnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:endymuhardin/aplikasi-membership.git
</code></pre></div></div>
<h3 id="inisialisasi-aplikasi-nodejs">Inisialisasi Aplikasi NodeJS</h3>
<p>Setelah itu, masuk ke foldernya dan lakukan inisialisasi project NodeJS dengan perintah <code class="language-plaintext highlighter-rouge">npm init</code>. Perintah ini akan menghasilkan file <code class="language-plaintext highlighter-rouge">package.json</code> yang merupakan deskripsi mengenai aplikasi yang akan kita buat. Bagi yang mempunyai latar belakang Maven, <code class="language-plaintext highlighter-rouge">package.json</code> ini kira-kira mirip dengan <code class="language-plaintext highlighter-rouge">pom.xml</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd aplikasi-membership
npm init
</code></pre></div></div>
<p>Berikut output dari perintah <code class="language-plaintext highlighter-rouge">npm init</code>. Kita akan ditanya-tanyai mengenai informasi aplikasi yang kita buat.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (aplikasi-membership)
version: (0.0.0)
description: Aplikasi Membership
entry point: (index.js)
test command:
git repository: (git://github.com/endymuhardin/aplikasi-membership.git)
keywords:
author: Endy Muhardin
license: (ISC) GPL-3.0
About to write to /home/endy/workspace/git-clones/aplikasi-membership/package.json:
{
"name": "aplikasi-membership",
"version": "0.0.0",
"description": "Aplikasi Membership",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git://github.com/endymuhardin/aplikasi-membership.git"
},
"author": "Endy Muhardin",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/endymuhardin/aplikasi-membership/issues"
},
"homepage": "https://github.com/endymuhardin/aplikasi-membership"
}
Is this ok? (yes) yes
</code></pre></div></div>
<p>Selanjutnya, kita akan memiliki file <a href="https://github.com/endymuhardin/aplikasi-membership/blob/master/package.json"><code class="language-plaintext highlighter-rouge">package.json</code></a> dalam folder aplikasi kita.</p>
<h3 id="menjalankan-aplikasi">Menjalankan Aplikasi</h3>
<p>Dalam file <code class="language-plaintext highlighter-rouge">package.json</code> dinyatakan bahwa aplikasi kita bisa dijalankan dengan mengeksekusi file <code class="language-plaintext highlighter-rouge">index.js</code>. Mari kita buat file tersebut</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">http</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">http</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">port</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span> <span class="o">||</span> <span class="mi">5000</span><span class="p">);</span>
<span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">response</span><span class="p">){</span>
<span class="nx">response</span><span class="p">.</span><span class="nx">writeHead</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="p">{</span><span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">text/plain</span><span class="dl">"</span><span class="p">});</span>
<span class="nx">response</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello World</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">response</span><span class="p">.</span><span class="nx">end</span><span class="p">();</span>
<span class="p">}).</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">App ready at port </span><span class="dl">"</span><span class="o">+</span><span class="nx">port</span><span class="p">);</span>
</code></pre></div></div>
<p>File <code class="language-plaintext highlighter-rouge">index.js</code> bisa kita jalankan dari command line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node index.js
</code></pre></div></div>
<p>Kita bisa lihat aplikasinya di browser dengan mengakses <code class="language-plaintext highlighter-rouge">http://localhost:5000</code>.</p>
<p><a href="https://lh3.googleusercontent.com/-zh_cAl6EUkk/UwXFnVvVpiI/AAAAAAAAFMY/NH8Z8kdekhY/w704-h465-no/01-hello-nodejs.png"><img src="https://lh3.googleusercontent.com/-zh_cAl6EUkk/UwXFnVvVpiI/AAAAAAAAFMY/NH8Z8kdekhY/w704-h465-no/01-hello-nodejs.png" alt="Foto" /></a></p>
<h2 id="deployment-ke-heroku">Deployment ke Heroku</h2>
<p>Untuk bisa dideploy ke Heroku, kita harus membuat file konfigurasi yang bernama <code class="language-plaintext highlighter-rouge">Procfile</code>. Berikut isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>web: node index.js
</code></pre></div></div>
<p>Artinya:</p>
<ul>
<li>Aplikasi kita adalah aplikasi web</li>
<li>jenis aplikasi webnya adalah NodeJS. Jenis aplikasi ini bisa bermacam-macam, misalnya Java, Python, dsb</li>
<li>File yang harus dijalankan NodeJS adalah <code class="language-plaintext highlighter-rouge">index.js</code></li>
</ul>
<p>Kita bisa tes konfigurasi tersebut dengan menggunakan aplikasi client Heroku yang bernama <code class="language-plaintext highlighter-rouge">foreman</code>. Jalankan perintah berikut di command line.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>foreman start
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>14:41:18 web.1 | started with pid 17022
14:41:18 web.1 | App ready at port 5000
</code></pre></div></div>
<p>Sama seperti perintah <code class="language-plaintext highlighter-rouge">node index.js</code>, hasilnya bisa kita lihat menggunakan browser dengan mengakses <code class="language-plaintext highlighter-rouge">http://localhost:5000</code>.
Untuk mematikannya, tekan <code class="language-plaintext highlighter-rouge">Ctrl-C</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>^CSIGINT received
14:41:46 system | sending SIGTERM to all processes
14:41:46 web.1 | terminated by SIGTERM
</code></pre></div></div>
<p>Selanjutnya, kita inisialisasi aplikasi kita di Heroku.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku apps:create ysq
</code></pre></div></div>
<p>Perintah ini akan membuat aplikasi bernama <code class="language-plaintext highlighter-rouge">ysq</code> di server Heroku. Dia juga akan menambahkan remote-url git agar kita bisa melakukan deployment. Berikut output dari perintah di atas</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Creating ysq... done, stack is cedar
http://ysq.herokuapp.com/ | git@heroku.com:ysq.git
Git remote heroku added
</code></pre></div></div>
<p>Untuk mendeploy ke Heroku, cukup dengan melakukan <code class="language-plaintext highlighter-rouge">git push</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push heroku master
</code></pre></div></div>
<p>Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Initializing repository, done.
Counting objects: 16, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (16/16), 2.10 KiB | 0 bytes/s, done.
Total 16 (delta 3), reused 4 (delta 0)
-----> Node.js app detected
PRO TIP: Specify a node version in package.json
See https://devcenter.heroku.com/articles/nodejs-support
-----> Defaulting to latest stable node: 0.10.26
-----> Downloading and installing node
-----> Installing dependencies
-----> Cleaning up node-gyp and npm artifacts
-----> Building runtime environment
-----> Discovering process types
Procfile declares types -> web
-----> Compressing... done, 5.1MB
-----> Launching... done, v3
http://ysq.herokuapp.com deployed to Heroku
To git@heroku.com:ysq.git
* [new branch] master -> master
</code></pre></div></div>
<p>Kita bisa browse aplikasi tersebut di alamat <code class="language-plaintext highlighter-rouge">http://ysq.herokuapp.com</code> seperti ini</p>
<p><a href="https://lh5.googleusercontent.com/-dTD4Lcoj2jY/UwXFnlTeAVI/AAAAAAAAFMc/qUlNM_zCBvc/w704-h465-no/02-heroku-deployment.png"><img src="https://lh5.googleusercontent.com/-dTD4Lcoj2jY/UwXFnlTeAVI/AAAAAAAAFMc/qUlNM_zCBvc/w704-h465-no/02-heroku-deployment.png" alt="Foto" /></a></p>
<h2 id="vertical-slice">Vertical Slice</h2>
<p>Vertical Slice adalah potongan aplikasi yang sudah tersambung end-to-end. Maksudnya adalah sebagian kecil aplikasi mulai dari tampilan yang dilihat user hingga ke penyimpanan data di database sudah tersambung dengan baik. Saya biasanya membuat vertical slice untuk menguji apakah kombinasi framework/library yang digunakan sudah terintegrasi dengan benar. Untuk itu, kita akan membuat kode program sederhana untuk menyimpan dan mengambil data dari NodeJS ke Redis. Kita juga akan tes apakah integrasi tersebut juga berjalan lancar di Heroku.</p>
<h3 id="verifikasi-instalasi-redis">Verifikasi Instalasi Redis</h3>
<p>Setelah sukses dengan <code class="language-plaintext highlighter-rouge">Hello World</code> tampilan, kita juga harus membuat <code class="language-plaintext highlighter-rouge">Hello World</code> untuk akses ke database Redis. Sebelum mulai, kita tes dulu instalasi Redis kita apakah dia sudah berjalan lancar dengan menggunakan perintah <code class="language-plaintext highlighter-rouge">redis-cli ping</code>. Kalau instalasinya benar, dia akan menjawab</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PONG
</code></pre></div></div>
<p>Selanjutnya, kita masuk ke command line redis, menyimpan variabel bernama email, kemudian menampilkan isi variabel tersebut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ redis-cli
127.0.0.1:6379> set email endy.muhardin@gmail.com
OK
127.0.0.1:6379> get email
"endy.muhardin@gmail.com"
</code></pre></div></div>
<h3 id="simpan-data-di-redis-dari-nodejs">Simpan data di Redis dari NodeJS</h3>
<p>Kita akan memodifikasi <code class="language-plaintext highlighter-rouge">index.js</code> agar menyimpan dan mengambil data dari Redis. Berikut source codenya</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">redis</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">redis</span><span class="dl">"</span><span class="p">).</span><span class="nx">createClient</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">displayContent</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span>
<span class="nx">redis</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">nama</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">reply</span><span class="p">){</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Nama : </span><span class="dl">"</span><span class="o">+</span><span class="nx">reply</span><span class="p">);</span>
<span class="nx">redis</span><span class="p">.</span><span class="nx">expire</span><span class="p">(</span><span class="dl">"</span><span class="s2">nama</span><span class="dl">"</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">redis</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">email</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">reply</span><span class="p">){</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Email : </span><span class="dl">"</span><span class="o">+</span><span class="nx">reply</span><span class="p">);</span>
<span class="nx">redis</span><span class="p">.</span><span class="nx">expire</span><span class="p">(</span><span class="dl">"</span><span class="s2">email</span><span class="dl">"</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">};</span>
<span class="nx">redis</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">connect</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(){</span>
<span class="nx">redis</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">nama</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Endy Muhardin</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">redis</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">email</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">endy.muhardin@gmail.com</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">displayContent</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Kalau langsung kita jalankan, maka kita akan mendapat error berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Cannot find module 'redis'
at Function.Module._resolveFilename (module.js:338:15)
at Function.Module._load (module.js:280:25)
at Module.require (module.js:364:17)
at require (module.js:380:17)
at Object.<anonymous> (/home/endy/workspace/git-clones/aplikasi-membership/index.js:13:13)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
exited with code 8
16:33:26 system | sending SIGTERM to all processes
</code></pre></div></div>
<p>Untuk mengatasi error tersebut, kita membutuhkan library <a href="https://github.com/mranney/node_redis">node_redis</a> untuk menghubungkan NodeJS dengan Redis. Install dulu menggunakan NPM dengan cara menjalankan perintah berikut dalam folder aplikasi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install redis --save
</code></pre></div></div>
<p>Kita gunakan opsi <code class="language-plaintext highlighter-rouge">--save</code> agar instalasi tersebut terdaftar di <code class="language-plaintext highlighter-rouge">package.json</code>.</p>
<p>Di website asalnya ada petunjuk mengenai penggunaan <code class="language-plaintext highlighter-rouge">hiredis</code>, yaitu binding terhadap library C untuk meningkatkan performance. Karena belum paham apa manfaat dan konsekuensinya, dan juga supaya tutorialnya lebih sederhana, kita tidak akan gunakan <code class="language-plaintext highlighter-rouge">hiredis</code>.</p>
<p>Berikut output dari perintah di atas</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm http GET https://registry.npmjs.org/redis
npm http 200 https://registry.npmjs.org/redis
npm http GET https://registry.npmjs.org/redis/-/redis-0.10.1.tgz
npm http 200 https://registry.npmjs.org/redis/-/redis-0.10.1.tgz
redis@0.10.1 node_modules/redis
</code></pre></div></div>
<p>Setelah itu, kita bisa tes menggunakan <code class="language-plaintext highlighter-rouge">foreman</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>foreman start
16:48:37 web.1 | started with pid 19028
16:48:37 web.1 | App ready at port 5000
16:48:37 web.1 | Nama : Endy Muhardin
16:48:37 web.1 | Email : endy.muhardin@gmail.com
^CSIGINT received
16:48:39 system | sending SIGTERM to all processes
16:48:39 web.1 | terminated by SIGTERM
</code></pre></div></div>
<h3 id="menggunakan-redis-di-heroku">Menggunakan Redis di Heroku</h3>
<p>Agar bisa digunakan di Heroku, kita harus menginstal dependensi tambahan, yaitu <code class="language-plaintext highlighter-rouge">redis-url</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install redis-url --save
</code></pre></div></div>
<p>Ada sedikit perubahan pada metode koneksi database Redis agar menggunakan database yang ada di Heroku.</p>
<p>Edit baris ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">redis</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">redis</span><span class="dl">"</span><span class="p">).</span><span class="nx">createClient</span><span class="p">();</span>
</code></pre></div></div>
<p>menjadi seperti ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">REDISTOGO_URL</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">rtg</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">url</span><span class="dl">"</span><span class="p">).</span><span class="nx">parse</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">REDISTOGO_URL</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">redis</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">redis</span><span class="dl">"</span><span class="p">).</span><span class="nx">createClient</span><span class="p">(</span><span class="nx">rtg</span><span class="p">.</span><span class="nx">port</span><span class="p">,</span> <span class="nx">rtg</span><span class="p">.</span><span class="nx">hostname</span><span class="p">);</span>
<span class="nx">redis</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nx">auth</span><span class="p">(</span><span class="nx">rtg</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="s2">:</span><span class="dl">"</span><span class="p">)[</span><span class="mi">1</span><span class="p">]);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">redis</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">redis</span><span class="dl">"</span><span class="p">).</span><span class="nx">createClient</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Di sisi heroku, kita harus terlebih dulu menginstal database Redis. Kita bisa gunakan add-ons <code class="language-plaintext highlighter-rouge">Redis To Go</code> yang gratisan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>heroku addons:add redistogo
</code></pre></div></div>
<p>Setelah itu kita bisa commit dan push</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git push heroku master
Fetching repository, done.
Counting objects: 7, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 590 bytes | 0 bytes/s, done.
Total 4 (delta 3), reused 0 (delta 0)
-----> Node.js app detected
PRO TIP: Specify a node version in package.json
See https://devcenter.heroku.com/articles/nodejs-support
-----> Defaulting to latest stable node: 0.10.26
-----> Downloading and installing node
-----> Restoring node_modules directory from cache
-----> Pruning cached dependencies not specified in package.json
-----> Installing dependencies
npm WARN package.json redis-url@0.2.0 No repository field.
npm http GET https://registry.npmjs.org/redis
npm http 200 https://registry.npmjs.org/redis
npm http GET https://registry.npmjs.org/redis/-/redis-0.10.1.tgz
npm http 200 https://registry.npmjs.org/redis/-/redis-0.10.1.tgz
redis@0.10.1 node_modules/redis
-----> Caching node_modules directory for future builds
-----> Cleaning up node-gyp and npm artifacts
-----> Building runtime environment
-----> Discovering process types
Procfile declares types -> web
-----> Compressing... done, 5.3MB
-----> Launching... done, v5
http://ysq.herokuapp.com deployed to Heroku
To git@heroku.com:ysq.git
84e7abd..cecc87f master -> master
</code></pre></div></div>
<p>Kita bisa cek apakah aplikasinya sukses dideploy dengan browse ke Heroku. Bila ada error, kita bisa lihat errornya dengan menggunakan perintah <code class="language-plaintext highlighter-rouge">heroku logs</code></p>
<h3 id="financial-problem">Financial Problem</h3>
<p>Setelah saya coba, ternyata untuk bisa menambahkan add-ons RedisToGo di Heroku kita harus memasukkan informasi kartu kredit. Padahal saya cuma mau pakai paket Free.</p>
<p>Untung ada jalan keluarnya. Kita bisa daftar langsung di <a href="https://redistogo.com">website RedisToGo</a> dan memilih paket Free. Setelah itu kita akan mendapatkan satu database seperti bisa dilihat pada screenshot berikut:</p>
<p><a href="https://lh6.googleusercontent.com/-lq4p7uGi63Y/UwYMOsT-95I/AAAAAAAAFNA/TpqPA7qA1Qk/w826-h330-no/03-redistogo.png"><img src="https://lh6.googleusercontent.com/-lq4p7uGi63Y/UwYMOsT-95I/AAAAAAAAFNA/TpqPA7qA1Qk/w826-h330-no/03-redistogo.png" alt="Foto" /></a></p>
<p>Kita sebaiknya memodifikasi kode program kita agar selalu menggunakan database RedisToGo tersebut walaupun pada saat menjalankan aplikasi di lokal. Ini untuk menyederhanakan kode program kita. Namanya juga aplikasi belajaran, jadi diusahakan sesederhana mungkin.</p>
<p>Kode program yang tadinya seperti ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">REDISTOGO_URL</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">rtg</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">url</span><span class="dl">"</span><span class="p">).</span><span class="nx">parse</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">REDISTOGO_URL</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">redis</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">redis</span><span class="dl">"</span><span class="p">).</span><span class="nx">createClient</span><span class="p">(</span><span class="nx">rtg</span><span class="p">.</span><span class="nx">port</span><span class="p">,</span> <span class="nx">rtg</span><span class="p">.</span><span class="nx">hostname</span><span class="p">);</span>
<span class="nx">redis</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nx">auth</span><span class="p">(</span><span class="nx">rtg</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="s2">:</span><span class="dl">"</span><span class="p">)[</span><span class="mi">1</span><span class="p">]);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">redis</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">redis</span><span class="dl">"</span><span class="p">).</span><span class="nx">createClient</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Kita ubah menjadi seperti ini</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">REDISTOGO_URL</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">redis://redistogo:8adc4536655ce179e2645ae4abd70eb2@pearlfish.redistogo.com:9930/</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">rtg</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">url</span><span class="dl">"</span><span class="p">).</span><span class="nx">parse</span><span class="p">(</span><span class="nx">REDISTOGO_URL</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">redis</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">redis</span><span class="dl">"</span><span class="p">).</span><span class="nx">createClient</span><span class="p">(</span><span class="nx">rtg</span><span class="p">.</span><span class="nx">port</span><span class="p">,</span> <span class="nx">rtg</span><span class="p">.</span><span class="nx">hostname</span><span class="p">);</span>
<span class="nx">redis</span><span class="p">.</span><span class="nx">auth</span><span class="p">(</span><span class="nx">rtg</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="s2">:</span><span class="dl">"</span><span class="p">)[</span><span class="mi">1</span><span class="p">]);</span>
</code></pre></div></div>
<p>Isi variabel <code class="language-plaintext highlighter-rouge">REDISTOGO_URL</code> diambil dari informasi di website RedisToGo seperti pada screenshot di atas.</p>
<p>Aplikasi bisa kita deploy ke Heroku dan harusnya bisa berjalan dengan sukses. Dengan suksesnya deployment tersebut, persiapan kita sudah selesai. Selanjutnya kita bisa mulai desain dan coding aplikasi kita yang sebenarnya.</p>
Ongoing Learning2014-02-20T12:48:00+07:00https://software.endy.muhardin.com/life/ongoing-learning<p>Hidup sebagai programmer bisa diibaratkan seperti nyemplung di laut. Untuk bisa survive, kita harus terus bergerak dan berenang. Begitu kita diam, langsung deh tenggelam.</p>
<p>Ini juga berlaku buat saya yang sudah 12 tahun jadi programmer. Teknologi baru bermunculan dengan cepat dan kita harus <em>keep up to date</em>. Artikel ini adalah bagian pertama dari catatan perjalanan saya belajar teknologi baru. Seperti biasa, source code yang dihasilkan akan saya share di Github.</p>
<p>Mari kita mulai.</p>
<!--more-->
<h2 id="pemilihan-teknologi">Pemilihan Teknologi</h2>
<p>Pertama, saya tentukan dulu apa yang mau dipelajari. Sebetulnya ada banyak teknologi yang belum saya kuasai, misalnya:</p>
<ul>
<li>Grails</li>
<li>Spring Roo</li>
<li>Spring Batch</li>
<li>Pentaho</li>
<li>NoSQL</li>
<li>NodeJS</li>
<li>Android</li>
<li>iOS Programming</li>
<li>Advanced Functional Programming (LISP, Clojure, Scala)</li>
<li>Amazon Cloud Services</li>
<li>dan masih banyak lagi</li>
</ul>
<p>Tapi kita harus memilih salah satu yang akan didahulukan. Tidak mungkin belajar semua.</p>
<p>Dari sekian banyak dalam list, saya akhirnya pilih NodeJS dan NoSQL.</p>
<blockquote>
<p>Kenapa?</p>
</blockquote>
<p>Karena kedua hal itu memiliki ekosistem yang sangat berbeda dari yang selama ini saya tekuni. Kalau selama ini terbiasa dengan <a href="http://software.endy.muhardin.com/java/development-stack-2014/">ekosistem Java</a> dan <a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">database relasional</a>, kali ini kita akan coba dunia di luar Java dan pemodelan data non-relasional.</p>
<p>Selanjutnya, seperti sering saya sarankan di berbagai forum, untuk belajar programming kita harus bikin aplikasi. Cuma baca-baca saja tidak akan banyak hasilnya.</p>
<h2 id="contoh-aplikasi">Contoh Aplikasi</h2>
<p>Nah kebetulan istri saya dapat amanah jadi sekretaris di pengajiannya, dia butuh aplikasi untuk menyimpan data member. Tidak rumit-rumit, berikut daftar fitur yang dibutuhkan:</p>
<h3 id="fitur-administrator">Fitur Administrator</h3>
<ul>
<li>Entri data member</li>
<li>Entri data pembayaran iuran</li>
<li>Entri pengumuman</li>
</ul>
<h3 id="fitur-member">Fitur Member</h3>
<ul>
<li>Lihat pengumuman</li>
<li>Lihat data pembayaran iuran</li>
</ul>
<p>Paling penting di sini adalah mulai dari hal yang sederhana dulu. Jangan terlalu ambisius ingin fitur canggih seperti:</p>
<ul>
<li>Mobile Interface</li>
<li>Pemberian tugas/target individu</li>
<li>Tracking progress penyelesaian tugas/target</li>
</ul>
<p>Kalaupun mau fitur tersebut, bisa kita tunda di rilis versi selanjutnya.</p>
<h2 id="langkah-pertama">Langkah Pertama</h2>
<p>Di jaman sekarang, jarang sekali aplikasi bisa dibangun hanya dengan satu library/framework saja. Beda dengan jaman VB 6 dulu. Sekali buka Visual Studio, beres langsung aplikasinya. Di jaman sekarang, kita harus pakai kombinasi teknologi atau yang biasa disebut <em>Development Stack</em>.</p>
<p>Karena ini dunia yang baru bagi saya, kombinasi stacknya masih belum saya pahami. Untuk itu perlu googling dulu. Beberapa link yang berhasil saya kumpulkan antara lain:</p>
<ul>
<li>Untuk deployment, kita bisa pakai <a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs">gratisan dari Heroku</a></li>
<li>Framework MVC untuk di sisi server ada dua kandidat kuat yaitu <a href="http://expressjs.com/">ExpressJS</a> dan <a href="http://sailsjs.org/#!documentation">SailJS</a>.</li>
<li>Workflow tools (ala Maven) bisa pakai <a href="http://yeoman.io/">Yeoman</a>. Di dalamnya dia include <a href="http://bower.io/">Bower</a> dan <a href="http://gruntjs.com/">Grunt</a>. Sekilas baca, Bower adalah dependency management (bertugas untuk mencarikan library yang kita butuhkan) dan Grunt adalah tools untuk build (compile, minify, optimize, dsb)</li>
<li><a href="http://blog.modulus.io/nodejs-and-express-sessions">Integrasi antara NodeJS, ExpressJS, dan Redis</a></li>
<li><a href="http://blog.semmy.me/post/46247962979/storing-simple-data-with-redis-and-node-js">Simpan data ke Redis dengan NodeJS</a></li>
<li><a href="https://devcenter.heroku.com/articles/redistogo">Deployment NodeJS dan Redis di Heroku</a></li>
</ul>
<p>Perlu diketahui, NodeJS adalah JavaScript untuk di sisi server. Di sisi client, kita akan menggunakan AngularJS dan Twitter Bootstrap. Untuk itu, kita cari tahu dulu <a href="https://www.google.com/search?q=sample+application+nodejs+angularjs">bagaimana menyambungkan AngularJS dan NodeJS</a>. Terutama, bagaimana mengatur struktur folder aplikasinya.</p>
<p>Dari hasil pencarian di atas, saya menemukan beberapa artikel, yaitu:</p>
<ul>
<li><a href="http://www.ibm.com/developerworks/library/wa-nodejs-polling-app/">Tutorial dari IBM, menggunakan ExpressJS, AngularJS, dan MongoDB</a></li>
<li><a href="https://www.twilio.com/blog/2013/12/votr-part-5-angularjs-crud-restful-apis.html">Membuat CRUD dengan AngularJS dan NodeJS</a></li>
<li><a href="http://bardevblog.wordpress.com/2013/08/14/understanding-angularjs-simple-example/">Aplikasi Sederhana dengan AngularJS dan NodeJS</a></li>
</ul>
<h2 id="langkah-selanjutnya">Langkah Selanjutnya</h2>
<p>Setelah materi dan referensi terkumpul, saatnya kita mulai bekerja. Pertama tentu kita <a href="http://askubuntu.com/a/83290">install dulu NodeJS</a>. Setelah itu, kita buat <a href="https://github.com/endymuhardin/aplikasi-membership">repository Githubnya</a>.</p>
<p>Lalu, kita mulai coding. Follow terus repositorynya dan nantikan artikel lanjutannya ;)</p>
Development Stack 20142014-02-17T07:29:00+07:00https://software.endy.muhardin.com/java/development-stack-2014<p>Tiga tahun lalu, saya memposting artikel tentang <a href="http://software.endy.muhardin.com/java/development-stack-2011/">stack development yang digunakan di ArtiVisi</a>. Artikel kali ini adalah update dari stack yang digunakan tiga tahun yang lalu.</p>
<p>Pilihan stack ini saya presentasikan pada pertemuan Java Meet Up (JaMU) Januari 2014 yang diadakan di kantor <a href="http://blibli.com">blibli.com</a>. Materinya bisa diunduh pada link berikut:</p>
<ul>
<li>Slide Presentasi bisa <a href="http://software.endy.muhardin.com/files/slide-presentasi/artivisi-stack-2014.html">dilihat online</a> atau <a href="http://www.4shared.com/zip/Mo47v94uba/presentasi-jamu-01-2014.html">diunduh dalam format ZIP</a></li>
<li><a href="http://www.youtube.com/watch?v=4312GuJVvxs">Rekaman Video dan Screencast</a></li>
</ul>
<p>Berikut rangkuman dari isi presentasi saya tersebut</p>
<p><a href="https://lh3.googleusercontent.com/-5tGzUVc2lk4/UwFrrT20hmI/AAAAAAAAFJk/zRxIxumf_O4/w800-h566-no/restful-architecture.png"><img src="https://lh3.googleusercontent.com/-5tGzUVc2lk4/UwFrrT20hmI/AAAAAAAAFJk/zRxIxumf_O4/w800-h566-no/restful-architecture.png" alt="Foto" /></a></p>
<!--more-->
<h2 id="presentation-layer">Presentation Layer</h2>
<p>Di sisi tampilan, ArtiVisi menggunakan <a href="http://getbootstrap.com/">Twitter Bootstrap</a> untuk memperindah tampilan dan <a href="http://angularjs.org/">AngularJS</a> untuk mengisi dynamic content. Kita <strong>tidak lagi menggunakan</strong> teknologi server side seperti JSF, ZK, JSP, dan sejenisnya. Untuk menyediakan server side service, kita menggunakan <a href="http://docs.spring.io/spring/docs/4.0.1.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-responsebody">fitur REST dari Spring MVC</a> yang menerima request HTTP dan mengembalikan response berupa <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>.</p>
<p>Kenapa tidak menggunakan server side rendering? Ada beberapa alasan:</p>
<ul>
<li>mencari skill CSS dan JS jauh lebih mudah daripada mencari skill server side dengan Java. Kita bisa rekrut programmer PHP untuk mengerjakan Twitter Bootstrap dan AngularJS. Ketersediaan programmer PHP jauh lebih banyak daripada programmer Java.</li>
<li>service dari server side yang menerima HTTP request dan mengembalikan JSON bisa digunakan oleh tampilan selain web. Saat ini kita sudah memiliki aplikasi client desktop dan mobile tanpa mengubah aplikasi di sisi server. Bahkan salah satu aplikasi client desktop dibangun dengan cepat menggunakan Delphi oleh customer hanya dengan bermodalkan <a href="http://software.endy.muhardin.com/java/mendebug-aplikasi-ajax/">pengetahuan tentang format request dan response</a>. Customer kami yang lain menambahkan sendiri fitur transaksi melalui SMS menggunakan <a href="http://playsms.org/">playSMS</a> juga dengan teknik yang sama. Semua ini mereka lakukan sendiri tanpa ada perubahan di sisi server. Ini sulit dilakukan kalau kita menggunakan framework server side.</li>
</ul>
<h2 id="business-service-layer">Business Service Layer</h2>
<p>Sebetulnya fitur yang penting di business layer hanya satu, yaitu <strong>declarative transaction management</strong>. Untuk keperluan itu, kita tetap percayakan pada framework yang sudah proven yaitu <a href="http://projects.spring.io/spring-framework/">Spring Framework</a>.</p>
<blockquote>
<p>Kenapa tidak pakai EJB? Kan sudah tersedia di application server JEE sehingga tidak perlu tambahan JAR?</p>
</blockquote>
<p>Ada beberapa alasan:</p>
<ul>
<li>Dengan Spring Framework, aplikasi bisa dideploy di application server mana saja. Bisa Tomcat, Jetty, Heroku, Google App Engine, dan sebagainya. Requirement yang dibutuhkan hanyalah Java VM saja.</li>
<li>Kalau kita menggunakan EJB, kita harus memahami berbagai model programming. Untuk aplikasi web menggunakan EJB, aplikasi konsol (misalnya ISO-8583 gateway) lain lagi, aplikasi desktop beda lagi, aplikasi mobile beda lagi. Kalau kita menggunakan Spring Framework, model pemrogramannya bisa digunakan di berbagai macam aplikasi tadi.</li>
</ul>
<h2 id="data-access-layer">Data Access Layer</h2>
<p>Untuk data access layer, sebetulnya kita masih menggunakan <a href="http://hibernate.org/orm/">Hibernate ORM</a>. Tapi kita tidak lagi menggunakannya secara langsung, melainkan dibungkus dengan Spring Data JPA. Tujuannya cuma satu, yaitu <a href="https://github.com/endymuhardin/belajar-restful/commit/972aae9f4363e151a654e3602ea32ef7e704c369">mengurangi jumlah kode program yang harus ditulis</a>.</p>
<p>Manajemen versioning untuk skema database kita gunakan <a href="http://www.liquibase.org/">Liquibase</a>. Tools ini memungkinkan kita untuk:</p>
<ul>
<li>mencatat versi skema database</li>
<li>membuatkan script untuk upgrade/rollback antar versi aplikasi. Misalnya aplikasi kita versi 2.0 mau diupgrade ke versi 2.1. Atau sebaliknya, kita sudah deploy versi 3.2 ternyata ada critical bug sehingga harus balik ke versi 3.1.</li>
</ul>
<h2 id="development-tools">Development Tools</h2>
<p>Berikut adalah tools development yang kita gunakan:</p>
<ul>
<li>Project Management : <a href="https://trello.com">Trello</a></li>
<li>Version Control : <a href="http://git-scm.org/">Git</a>. Di sisi server dimanage dengan <a href="http://gitlab.org/gitlab-ce/">Gitlab</a></li>
<li>Build Tools : <a href="http://maven.apache.org/">Maven 3</a></li>
<li>Functional Test untuk REST : <a href="https://code.google.com/p/rest-assured/">Rest Assured</a></li>
</ul>
<h2 id="workflow">Workflow</h2>
<p>Secara garis besar, berikut adalah gambaran workflow cara kerja sehari-hari di tim development kita.</p>
<p><a href="https://lh3.googleusercontent.com/-Ne0pSRdBBgk/UwFrsAA5KYI/AAAAAAAAFJs/6IRdUeLGvFA/w800-h566-no/workflow.png"><img src="https://lh3.googleusercontent.com/-Ne0pSRdBBgk/UwFrsAA5KYI/AAAAAAAAFJs/6IRdUeLGvFA/w800-h566-no/workflow.png" alt="Foto" /></a></p>
<h2 id="proof-of-concept">Proof of Concept</h2>
<p>Di ArtiVisi kami memiliki aplikasi kecil sebagai <em>proof of concept</em> dari teknologi yang kami gunakan. Kalau ada penambahan/pengurangan/perubahan terhadap stack, yang pertama diupdate adalah aplikasi kecil ini. Aplikasinya disediakan gratis <a href="https://github.com/endymuhardin/belajar-restful">di github</a>. Jadi, aplikasi ini akan selalu memuat kombinasi stack terkini yang sedang kami gunakan, walaupun belum sempat diumumkan di posting blog.</p>
<p>Beberapa fitur dari aplikasi ini antara lain:</p>
<ul>
<li>CRUD</li>
<li>relasi one-to-many dan many-to-many</li>
<li>Popup dialog</li>
<li>Automated Testing untuk query database dan REST url</li>
<li>dan fitur-fitur lain yang bisa dieksplorasi sendiri ;)</li>
</ul>
<p>Dari aplikasi kecil ini, kami membuat template project berupa Maven Archetype yang juga disediakan gratis <a href="https://github.com/artivisi/standard-webapp-archetype">di github</a>. Bagi yang belum familiar dengan Maven, archetype adalah semacam wizard untuk membuat project baru.</p>
<h2 id="catatan-penting-soal-performance">Catatan penting soal performance</h2>
<blockquote>
<p>Saya ingin membuat aplikasi Java. Framework mana yang performance-nya paling cepat?</p>
</blockquote>
<p>Bila Anda menyimak presentasi saya di JaMU, khususnya di slide nomer 5 dan 6, saya tidak menyebut-nyebut masalah performance sebagai salah satu kriteria pemilihan framework/teknologi. Kenapa begitu?</p>
<p>Di jaman sekarang ini, semua teknologi yang umum digunakan orang sudah bisa dioptimasi performancenya. Mau pakai Java, PHP, Ruby, Python, Perl, semua sudah punya portofolio terkenal. Misalnya:</p>
<ul>
<li>Java punya Amazon dan Google</li>
<li>PHP punya Facebook</li>
<li>Ruby punya Github</li>
<li>Python punya Google</li>
<li>Perl punya BBC iPlayer</li>
<li>dan sebagainya</li>
</ul>
<p>Demikian juga mengenai Linux vs Windows, MySQL vs Oracle, dan sebagainya. Bagaimana cara meningkatkan performance sudah dirangkum di <a href="http://highscalability.com/start-here/">website ini</a>.</p>
<p>Intinya, performance bukan lagi faktor yang harus dipertimbangkan dalam proses pemilihan. Kalau aplikasi yang kita buat ternyata lemot, maka berikut urutan tersangkanya:</p>
<ul>
<li>teknik coding yang salah (biasanya disebabkan karena tidak menguasai <a href="http://software.endy.muhardin.com/life/lan-na-zha/">dasar-dasar pemrograman</a> seperti misalnya melakukan <code class="language-plaintext highlighter-rouge">select * from tabel_transaksi</code> dimana isi tabel berisi jutaan record transaksi sejak 10 tahun yang lalu)</li>
<li>programmernya tidak tahu cara pakai framework/teknologi tersebut</li>
<li>teknologi tidak sesuai peruntukannya</li>
<li>belum mengetahui / menerapkan tips dan trik tuning performance untuk teknologi yang dimaksud</li>
</ul>
<p>Jadi, kalau ada masalah performance, tersangka utama bukanlah teknologinya, tapi usernya.</p>
<blockquote>
<p>Logikanya sederhana saja, jutaan orang pakai teknologi tersebut. Kalau memang ada masalah besar masa iya tidak muncul di peringkat pertama pada waktu kita <a href="http://software.endy.muhardin.com/aplikasi/teknik-menggunakan-google/">search di Google</a>?</p>
</blockquote>
<p>Demikianlah stack development ArtiVisi di tahun 2014. Untuk lebih jelasnya, silahkan tonton video presentasi JaMU di atas.</p>
Dasar-dasar Aplikasi Web Java2014-01-20T20:12:00+07:00https://software.endy.muhardin.com/java/dasar-dasar-aplikasi-web-java<p>Pada artikel terdahulu, kita telah membahas tentang <a href="http://software.endy.muhardin.com/life/lan-na-zha/">pentingnya penguasaan terhadap konsep dasar</a>. Kali ini, kita akan membahas tentang konsep dasar aplikasi web di Java. Ini juga akan menjawab pertanyaan yang sering ditanyakan di forum yaitu</p>
<blockquote>
<p>Saya ingin belajar Java Enterprise Edition (Java EE), apa yang pertama harus saya pelajari?</p>
</blockquote>
<!--more-->
<p>Java EE sangat luas cakupannya, di antaranya adalah:</p>
<ul>
<li>Java Servlet</li>
<li>JavaServer Faces</li>
<li>JavaServer Pages</li>
<li>Java Persistence</li>
<li><a href="http://www.oracle.com/technetwork/java/javaee/tech/index.html">dan masih banyak lagi</a></li>
</ul>
<p>Untuk bisa memahami semuanya, tentu kita harus memulai dari yang paling sederhana dulu, yaitu Java Servlet. Teknologi ini adalah dasar dari semua aplikasi web di Java. Tanpa pemahaman terhadap teknologi ini, sulit bagi kita untuk mempelajari framework atau library yang lebih canggih seperti Spring MVC, Struts, JavaServer Faces, dan berbagai framework web lainnya. Kenapa sulit? Karena semua framework tersebut dibangun di atas Java Servlet. Kalau pondasinya saja tidak paham, bagaimana mau belajar yang lain?</p>
<p>Ada beberapa hal penting yang harus dipahami dalam Java Servlet, yaitu:</p>
<ul>
<li>Struktur Folder Aplikasi Web Java</li>
<li>Alur eksekusi HTTP Request sampai menghasilkan HTTP Response</li>
<li>
<p>Tiga komponen Java Servlet yaitu:</p>
<ul>
<li>Servlet</li>
<li>Filter</li>
<li>Listener</li>
</ul>
</li>
</ul>
<p>Mari kita bahas satu persatu.</p>
<h1 id="struktur-folder">Struktur Folder</h1>
<p>Aplikasi web Java struktur foldernya harus mengikuti kaidah berikut</p>
<p><a href="https://github.com/endymuhardin/materi-kuliah-java-web/blob/master/materi-kuliah/img/struktur-folder-web-java.png?raw=true"><img src="https://github.com/endymuhardin/materi-kuliah-java-web/blob/master/materi-kuliah/img/struktur-folder-web-java.png?raw=true" alt="Struktur Folder Aplikasi Web Java" /></a></p>
<p>Gambar di atas adalah struktur folder aplikasi yang siap dijalankan. Oleh karena itu kita tidak melihat adanya source code Java di sana.</p>
<p>Setelah kita memiliki struktur folder seperti di atas, aplikasi siap dipaketkan (package) supaya bisa dideploy di application server. Packaging dilakukan dengan membungkus folder di atas dengan algoritma <code class="language-plaintext highlighter-rouge">zip</code>. Kita bisa menggunakan aplikasi kompresi seperti WinZip ataupun WinRAR. Bila kita menggunakan WinRAR, harus diingat bahwa algoritma yang digunakan haruslah <code class="language-plaintext highlighter-rouge">zip</code>, bukan <code class="language-plaintext highlighter-rouge">rar</code></p>
<p>Setelah di-zip, rename extension file menjadi <code class="language-plaintext highlighter-rouge">war</code> agar dikenali oleh application server. <code class="language-plaintext highlighter-rouge">war</code> merupakan singkatan dari Web Archive.</p>
<p>Untuk menjalankan aplikasi di application server (misalnya Tomcat), berikut langkah-langkah deployment:</p>
<ul>
<li>masukkan file <code class="language-plaintext highlighter-rouge">war</code> ke folder deployment sesuai merek application server yang digunakan</li>
<li>contoh: untuk Tomcat, folder deploymentnya adalah <code class="language-plaintext highlighter-rouge">webapps</code></li>
<li>Tomcat akan meng-extract file <code class="language-plaintext highlighter-rouge">war</code> tersebut</li>
<li>Setelah deployment selesai (ditandai dengan file telah diextract dan tidak ada error di log) aplikasi bisa dibrowse di <code class="language-plaintext highlighter-rouge">http://ip-server:port/nama-file-war/index.html</code></li>
</ul>
<p>Bila aplikasi tidak lagi digunakan, kita bisa melakukan undeploy. Caranya:</p>
<ul>
<li>hapus file <code class="language-plaintext highlighter-rouge">war</code> dari folder deployment</li>
<li>setelah folder aplikasi terhapus, undeploy berarti sukses</li>
</ul>
<p>Ada beberapa hal yang harus diperhatikan dalam struktur folder di atas, yaitu:</p>
<ul>
<li>Folder <code class="language-plaintext highlighter-rouge">WEB-INF</code></li>
<li>File <code class="language-plaintext highlighter-rouge">web.xml</code></li>
</ul>
<h2 id="folder-web-inf">Folder WEB-INF</h2>
<p>Folder ini adalah folder khusus. Dia tidak bisa dibrowse oleh user aplikasi. Maksudnya adalah, user aplikasi tidak bisa mengetik <code class="language-plaintext highlighter-rouge">http://localhost:8080/belajar/WEB-INF</code> di browser untuk menampilkan isi folder <code class="language-plaintext highlighter-rouge">WEB-INF</code>. Jadi bila kita memiliki file yang tidak boleh dilihat user (misalnya konfigurasi, logfile, file temporary, dan sebagainya), kita bisa letakkan file-file tersebut dalam folder <code class="language-plaintext highlighter-rouge">WEB-INF</code>.</p>
<p>Di dalam folder ini biasanya ada dua folder khusus lain, yaitu</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">classes</code> : berisi file <code class="language-plaintext highlighter-rouge">*.class</code> hasil kompilasi dari source code <code class="language-plaintext highlighter-rouge">*.java</code> kita</li>
<li><code class="language-plaintext highlighter-rouge">lib</code> : berisi file <code class="language-plaintext highlighter-rouge">*.jar</code>, library tambahan yang kita gunakan seperti driver JDBC, JSON processor, pengolah tanggal, dan lainnya</li>
</ul>
<h2 id="file-webxml">File web.xml</h2>
<p>File ini berisi konfigurasi dari aplikasi web kita. Di Java EE versi terbaru, file ini tidak harus ada. Tapi di Java EE versi terdahulu, dia wajib disertakan. Untuk amannya, agar aplikasi kita kompatibel dengan application server jadul, ada baiknya kita sertakan saja. Toh file ini juga akan kita buat kalau kita menggunakan framework seperti Spring MVC, JSF, Struts, dan sebagainya. Berikut isi file <code class="language-plaintext highlighter-rouge">web.xml</code> yang paling sederhana, yaitu hanya berisi <em>root tag</em> <code class="language-plaintext highlighter-rouge">webapp</code> kosong.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><web-app</span> <span class="na">xmlns=</span><span class="s">"http://java.sun.com/xml/ns/javaee"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"</span>
<span class="na">version=</span><span class="s">"3.0"</span><span class="nt">></span>
<span class="nt"></web-app></span>
</code></pre></div></div>
<p>Nantinya, file ini bisa berisi konfigurasi:</p>
<ul>
<li>Servlet</li>
<li>Filter</li>
<li>Listener</li>
<li>Welcome Page : halaman yang dibuka kalau user tidak menyebutkan apa-apa, misalnya <code class="language-plaintext highlighter-rouge">http://aplikasi-saya.com/</code></li>
<li>Error Page : halaman error bila URL yang diminta tidak ada (404), user belum login (401), ijin akses tidak memadai (403), error aplikasi (500), dan sebagainya</li>
<li>Session timeout : rentang waktu user tidak mengakses aplikasi hingga session-nya dinyatakan kadaluarsa</li>
</ul>
<h1 id="alur-eksekusi-http">Alur Eksekusi HTTP</h1>
<blockquote>
<p>Apa yang terjadi bila kita mengetik <code class="language-plaintext highlighter-rouge">http://localhost:8080/aplikasi-web-sederhana/halo.php?nama=endy</code> di browser?</p>
</blockquote>
<ol>
<li>Browser akan mengakses port 8080 di localhost, yaitu komputer kita sendiri.</li>
<li>Kita asumsikan bahwa Tomcat berjalan di port 8080, maka request ini akan diterima Tomcat</li>
<li>Tomcat akan mencari aplikasi bernama <code class="language-plaintext highlighter-rouge">aplikasi-web-sederhana</code>. Biasanya aplikasi ini ditandai dengan adanya folder <code class="language-plaintext highlighter-rouge">aplikasi-web-sederhana</code> dalam folder <code class="language-plaintext highlighter-rouge"><lokasi instalasi tomcat>/webapps</code>. Folder ini akan terbentuk bila kita mendeploy file bernama <code class="language-plaintext highlighter-rouge">aplikasi-web-sederhana.war</code> seperti telah dijelaskan di atas.</li>
<li>Tomcat akan mencari kode program yang bertanggung jawab menerima request <code class="language-plaintext highlighter-rouge">halo.php</code>. Biasanya penanggung jawab ini adalah servlet yang dideklarasikan dalam <code class="language-plaintext highlighter-rouge">aplikasi-web-sederhana/WEB-INF/web.xml</code></li>
<li>Bila penanggung jawab tidak ditemukan, Tomcat akan mengembalikan kode error 404, artinya tidak ditemukan. Bila penanggung jawab ada, maka Tomcat akan memberikan data-data request kepada penanggung jawab ini untuk ditangani. Data request yang tersedia diantaranya adalah request parameter, HTTP header, dan request body</li>
<li>Servlet yang bertanggung jawab akan memproses request, kemudian menghasilkan response. Response dikirim ke browser yang mengaksesnya.</li>
</ol>
<p>Sebagai ilustrasi, untuk menangani request <code class="language-plaintext highlighter-rouge">http://localhost:8080/aplikasi-web-sederhana/halo.php?nama=endy</code>, kita harus membuat deklarasi servlet di <code class="language-plaintext highlighter-rouge">web.xml</code> sebagai berikut:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><servlet></span>
<span class="nt"><servlet-name></span>halo<span class="nt"></servlet-name></span>
<span class="nt"><servlet-class></span>com.muhardin.endy.training.web.HaloServlet<span class="nt"></servlet-class></span>
<span class="nt"></servlet></span>
<span class="nt"><servlet-mapping></span>
<span class="nt"><servlet-name></span>halo<span class="nt"></servlet-name></span>
<span class="nt"><url-pattern></span>/halo.php<span class="nt"></url-pattern></span>
<span class="nt"></servlet-mapping></span>
</code></pre></div></div>
<p>Kemudian membuat class <code class="language-plaintext highlighter-rouge">HaloServlet</code> dalam package <code class="language-plaintext highlighter-rouge">com.muhardin.endy.training.web</code> yang isinya sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HaloServlet</span> <span class="kd">extends</span> <span class="nc">HttpServlet</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doGet</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">asal</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getRemoteAddr</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">nama</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"nama"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">output</span> <span class="o">=</span> <span class="s">"<html>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"<body>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"<h1>Halo "</span><span class="o">+</span><span class="n">nama</span><span class="o">+</span><span class="s">"</h1>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"<h2>Anda datang dari "</span><span class="o">+</span><span class="n">asal</span><span class="o">+</span><span class="s">"</h2>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"</body>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"</html>"</span><span class="o">;</span>
<span class="c1">// content type = html</span>
<span class="n">response</span><span class="o">.</span><span class="na">setContentType</span><span class="o">(</span><span class="s">"text/html"</span><span class="o">);</span>
<span class="n">response</span><span class="o">.</span><span class="na">getWriter</span><span class="o">().</span><span class="na">print</span><span class="o">(</span><span class="n">output</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Logger</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">HaloServlet</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">()).</span><span class="na">log</span><span class="o">(</span><span class="nc">Level</span><span class="o">.</span><span class="na">SEVERE</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pembaca yang teliti akan merasa heran</p>
<blockquote>
<p>Lho kok ekstensi requestnya <code class="language-plaintext highlighter-rouge">.php</code>?</p>
</blockquote>
<p>Aplikasi web Java tidak peduli terhadap ekstensi request. Yang penting sesuai dengan mapping dalam <code class="language-plaintext highlighter-rouge">web.xml</code>. Jadi kita bisa pasang <code class="language-plaintext highlighter-rouge">halo.php</code>, <code class="language-plaintext highlighter-rouge">halo.jsp</code>, <code class="language-plaintext highlighter-rouge">halo.html</code>, <code class="language-plaintext highlighter-rouge">halo.asp</code>, bahkan <code class="language-plaintext highlighter-rouge">halo.halo</code>. Selama URL tersebut terdaftar dalam <code class="language-plaintext highlighter-rouge">web.xml</code>, maka request akan diproses dengan baik.</p>
<p>Source code lengkap bisa diakses <a href="https://github.com/endymuhardin/materi-kuliah-java-web/tree/master/sample-code/sesi-01/aplikasi-web-sederhana">di sini</a></p>
<h1 id="komponen-aplikasi-web-java">Komponen Aplikasi Web Java</h1>
<h2 id="servlet">Servlet</h2>
<p>Digunakan untuk menerima HTTP request dan membuat HTTP response</p>
<p>Cara membuat:</p>
<ol>
<li>Buat class yang extends <code class="language-plaintext highlighter-rouge">HttpServlet</code></li>
<li>
<p>Implement method sesuai HTTP method yang akan kita gunakan.</p>
<ul>
<li>GET -> doGet(HttpServletRequest req, HttpServletResponse res)</li>
<li>POST -> doPost(HttpServletRequest req, HttpServletResponse res)</li>
<li>dst</li>
</ul>
</li>
<li>Buat mappingnya di <code class="language-plaintext highlighter-rouge">web.xml</code></li>
<li>Compile dan deploy ke application server</li>
<li>Test browse ke <code class="language-plaintext highlighter-rouge">http://localhost:8080/aplikasi-web-sederhana/halo.php?nama=endy</code></li>
</ol>
<h3 id="haloservletjava">HaloServlet.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HaloServlet</span> <span class="kd">extends</span> <span class="nc">HttpServlet</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doGet</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">asal</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getRemoteAddr</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">nama</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"nama"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">output</span> <span class="o">=</span> <span class="s">"<html>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"<body>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"<h1>Halo "</span><span class="o">+</span><span class="n">nama</span><span class="o">+</span><span class="s">"</h1>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"<h2>Anda datang dari "</span><span class="o">+</span><span class="n">asal</span><span class="o">+</span><span class="s">"</h2>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"</body>"</span><span class="o">;</span>
<span class="n">output</span> <span class="o">+=</span> <span class="s">"</html>"</span><span class="o">;</span>
<span class="c1">// content type = html</span>
<span class="n">response</span><span class="o">.</span><span class="na">setContentType</span><span class="o">(</span><span class="s">"text/html"</span><span class="o">);</span>
<span class="n">response</span><span class="o">.</span><span class="na">getWriter</span><span class="o">().</span><span class="na">print</span><span class="o">(</span><span class="n">output</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Logger</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">HaloServlet</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">()).</span><span class="na">log</span><span class="o">(</span><span class="nc">Level</span><span class="o">.</span><span class="na">SEVERE</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="webxml">web.xml</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><servlet></span>
<span class="nt"><servlet-name></span>halo<span class="nt"></servlet-name></span>
<span class="nt"><servlet-class></span>com.muhardin.endy.training.web.HaloServlet<span class="nt"></servlet-class></span>
<span class="nt"></servlet></span>
<span class="nt"><servlet-mapping></span>
<span class="nt"><servlet-name></span>halo<span class="nt"></servlet-name></span>
<span class="nt"><url-pattern></span>/halo.php<span class="nt"></url-pattern></span>
<span class="nt"></servlet-mapping></span>
</code></pre></div></div>
<h2 id="filter">Filter</h2>
<p>Digunakan untuk :</p>
<ul>
<li>mencegat HTTP request sebelum ditangani servlet</li>
<li>mencegat HTTP response sebelum dikirim ke requester</li>
</ul>
<p>Contoh penggunaan :</p>
<ul>
<li>memproteksi URL yang membutuhkan login (Spring Security)</li>
<li>mendekorasi HTML dengan header/footer/sidebar (Sitemesh)</li>
<li>kompresi response</li>
</ul>
<p>Contoh implementasi :</p>
<h3 id="java-kode-program">Java (kode program)</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HaloFilter</span> <span class="kd">implements</span> <span class="nc">Filter</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">(</span><span class="nc">FilterConfig</span> <span class="n">fc</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ServletException</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Filter diinisialisasi"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doFilter</span><span class="o">(</span><span class="nc">ServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">ServletResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">FilterChain</span> <span class="n">fc</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ServletException</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Mencegat request"</span><span class="o">);</span>
<span class="n">fc</span><span class="o">.</span><span class="na">doFilter</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Mencegat response"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">destroy</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Filter di-destroy"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="webxml-konfigurasi">web.xml (konfigurasi)</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><filter></span>
<span class="nt"><filter-name></span>haloFilter<span class="nt"></filter-name></span>
<span class="nt"><filter-class></span>com.muhardin.endy.training.web.HaloFilter<span class="nt"></filter-class></span>
<span class="nt"></filter></span>
<span class="nt"><filter-mapping></span>
<span class="nt"><filter-name></span>haloFilter<span class="nt"></filter-name></span>
<span class="nt"><url-pattern></span>/*<span class="nt"></url-pattern></span>
<span class="nt"></filter-mapping></span>
</code></pre></div></div>
<h2 id="listener">Listener</h2>
<p>Digunakan untuk merespon event dalam aplikasi, misalnya:</p>
<ul>
<li>aplikasi distart / deploy (context created)</li>
<li>aplikasi distop / undeploy (context destroyed)</li>
<li>session dibuat</li>
<li>session timeout</li>
<li>session dihapus (invalidation)</li>
</ul>
<p>Contoh implementasi:</p>
<h3 id="java-kode-program-1">Java (kode program)</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HaloListener</span> <span class="kd">implements</span> <span class="nc">ServletContextListener</span><span class="o">,</span> <span class="nc">HttpSessionListener</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">contextInitialized</span><span class="o">(</span><span class="nc">ServletContextEvent</span> <span class="n">sce</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Aplikasi start"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">contextDestroyed</span><span class="o">(</span><span class="nc">ServletContextEvent</span> <span class="n">sce</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Aplikasi stop"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">sessionCreated</span><span class="o">(</span><span class="nc">HttpSessionEvent</span> <span class="n">hse</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Ada session baru"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">sessionDestroyed</span><span class="o">(</span><span class="nc">HttpSessionEvent</span> <span class="n">hse</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Session di-destroy"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="webxml-konfigurasi-1">web.xml (konfigurasi)</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><listener></span>
<span class="nt"><listener-class></span>com.muhardin.endy.training.web.HaloListener<span class="nt"></listener-class></span>
<span class="nt"></listener></span>
</code></pre></div></div>
<p>Demikianlah konsep dasar aplikasi web di Java. Untuk bisa memahami, tentu kita harus sering berlatih membuat kode program, tidak hanya membaca tutorial saja. Contoh kode program lainnya bisa dilihat <a href="https://github.com/endymuhardin/materi-kuliah-java-web/tree/master/sample-code">di sini</a>. Silahkan dipraktekkan dan dilihat hasilnya.</p>
Source Code Milik Siapa ?2014-01-08T21:31:00+07:00https://software.endy.muhardin.com/life/source-code-milik-siapa<p>Malam ini, saya mendapatkan pertanyaan menarik di milis komunitas programmer. Berikut isinya.</p>
<blockquote>
<p>hello all,</p>
</blockquote>
<blockquote>
<p>saya mau tanya, ketika kita bekerja di suatu perusahaan base on project single programmer, yang semua dikerjakan sendiri, dari mulai analis sampai coding, apakah source code hasil kerja kita adalah milik perusahaan? atau milik programmer ybs?</p>
</blockquote>
<blockquote>
<p>karena source code itu kita yang buat dari awal hingga menjadi suatu sistem aplikasi, programmer dikecewakan dianggap tidak memenuhi target atau alasan lain, saat tiba waktu deadline pimpinan perusahaan marah2 bentak2 kemudian dengan gampang perusahaan meminta source yang kita kerjakan? padahal programmer tsb sudah menyelesaikan pekerjaannya</p>
</blockquote>
<blockquote>
<p>sebagai programmer akhibat dikecewakan demi mempertahankan harga diri, akhirnya resign dan rela keluar dari perusahaan alias nganggur, programmer tidak rela memberikan source yg mjd hasil karyanya sendiri begitu saja</p>
</blockquote>
<blockquote>
<p>apakah ada hukum kemudian terjadi tuntutan perusahaan ke programmer untuk meminta source code tersebut ?</p>
</blockquote>
<blockquote>
<p>apakah source code hasil coding programmer adalah termasuk Hak Cipta Programmer?</p>
</blockquote>
<blockquote>
<p>mohon sharingnya, thanks</p>
</blockquote>
<p>Berikut tanggapan saya</p>
<!--more-->
<p>Pertama harus kita pisahkan dulu mana yang relevan mana yang tidak. Bahwa si penanya resign <em>tidak secara baik-baik</em> tidak relevan dengan kepemilikan source code. Bahkan sekalipun si penanya resign dengan terhormat, diberikan farewell party yang meriah, dapat suvenir yang ditandatangani seluruh karyawan, tidak akan mengubah kondisi kepemilikan source code.</p>
<p>Kalo mau jawaban yang lebih panjang dan berbagai kemungkinannya, bisa baca jawabannya Joel Spolsky <a href="http://web.archive.org/web/20130121231716/http://answers.onstartups.com/questions/19422/if-im-working-at-a-company-do-they-have-intellectual-property-rights-to-the-st/20136">di sini</a>. Interesting read, tapi harus diperhatikan bahwa itu membahas hukum di Amerika sana, belum tentu sesuai untuk Indonesia.</p>
<p>Sebenarnya sih masalah ini sangat bisa diperdebatkan, malah mungkin bisa dibahas Karni Ilyas di ILC saking beragamnya pendapat. Tapi kalo mau terima saran saya, sudahlah move on dan lupakan saja. Tidak perlu kita pusingkan milik siapa dan apakah bisa dituntut atau tidak. Kenapa begitu?</p>
<ol>
<li>
<p>Dunia IT sempit. Di Indonesia sini, semua orang IT saling mengenal. Sekali kamu dapat reputasi ‘pencuri source code’, tamat sudah masa depan kamu. Terlepas dari benar/salah, tidak banyak orang yang mau repot verifikasi kebenarannya. Selain ‘pencuri source code’, satu lagi yang harus diwaspadai adalah ‘pencuri data’. Kalau nanti sempat dipercayakan akses ke data production, harus dijaga baik-baik.</p>
</li>
<li>
<p>Pertengkaran memperebutkan source code hanya akan menghabiskan energi kita, menambah musuh, dan memperkaya Ruhut, Farhat, dan yang sejenisnya. Hidup cuma sebentar, kita harus pintar-pintar memilih pertempuran yang akan dijalani.</p>
</li>
<li>
<p>Source code kamu tidak seberharga yang kamu bayangkan. Source code tanpa bisnis yang mendasarinya gak ada gunanya. Waktu saya kerja di perusahaan terdahulu, banyak beredar kabar tentang si anu dan si anu yang mencuri source code aplikasi core banking. Tapi di mana mereka sekarang? Belum ada terdengar mereka buka perusahaan penyedia aplikasi core banking dan sukses. Client bukan cuma butuh source code, tapi juga:</p>
<ul>
<li>keyakinan bahwa vendornya bisa solve kalau ada problem</li>
<li>keyakinan bahwa vendornya akan terus support kalau ada perubahan atau bug</li>
<li>keyakinan bahwa vendornya tidak akan bangkrut dalam waktu dekat sehingga merepotkan mereka</li>
</ul>
</li>
<li>
<p>Apa yang sulit kamu bikin, belum tentu sulit bagi orang lain. Mahasiswa saya di kampus sering sekali mengeluh disuruh bikin tugas barang 4-5 tabel database saja. Dikasi waktu satu semester, masih juga gak selesai. Susah katanya. Mereka gak tau bahwa untuk programmer dengan jam terbang 1 tahun ke atas bisa mengerjakan 2 tabel database <strong>hanya dalam waktu 1 hari saja</strong>. So, apa yang mereka anggap berat dan gak selesai 1 semester, sebenarnya hanyalah kerjaan 3 hari belaka.</p>
</li>
<li>
<p>Sebagai pekerja kreatif, kita seharusnya selalu merasa bahwa kita belum membuat hasil karya terbaik kita. Artinya, sehebat apapun hasil kerja kita di masa lalu, kita masih bisa membuat yang lebih canggih lagi. Dengan mentalitas seperti ini, kita akan merasa lebih ringan untuk merelakan apapun yang kita sudah pernah kerjakan.</p>
</li>
</ol>
<blockquote>
<p>Tidak apa-apalah mereka mengambil aplikasi saya yang itu. Toh saya masih punya yang jauh lebih baik. Mana dia? Ini sedang saya kerjakan.</p>
</blockquote>
<p>Begitulah sikap yang lebih baik.</p>
<p>Lagipula, sekedar source code saja apa hebatnya. Source code Linux saja yang sedemikian hebatnya dibagi-bagikan gratis.</p>
Prasyarat Integrasi Aplikasi2014-01-03T10:43:00+07:00https://software.endy.muhardin.com/java/prasyarat-integrasi-aplikasi<p>Posting kali ini jawaban dari pertanyaan yang masuk melalui email:</p>
<blockquote>
<p>Saya membaca tulisan anda <a href="http://software.endy.muhardin.com/java/integrasi-aplikasi/">di sini</a>
Cukup menarik, dan saya ingin bertanya jika berkenan, sehubungan dengan payment gateway.</p>
</blockquote>
<blockquote>
<p>Apakah kita bisa mengimplementasikan sendiri ISO-8583 pada aplikasi kita untuk terhubung ke bank di Indonesia ?
Misal kita bisa cek saldo tabungan melalui aplikasi kita sendiri ?</p>
</blockquote>
<blockquote>
<p>Atas sharing pengetahuannya saya ucapkan terima kasih.</p>
</blockquote>
<p>Berikut jawaban saya</p>
<!--more-->
<p>Untuk bisa connect ke aplikasi lain (misalnya aplikasi bank), ada beberapa prasyarat yang harus dipenuhi <strong>secara berurutan</strong>:</p>
<ol>
<li>aksesnya harus dibuka (ip address, port, firewall, dsb). Biasanya ini melibatkan faktor non-teknis seperti deal bisnis, non-disclosure agreement, perjanjian berkekuatan hukum, dan sejenisnya.</li>
<li>protokol komunikasinya harus diketahui (http, iso8583, dsb)</li>
<li>kita harus bikin aplikasi yg bisa berkomunikasi dengan aplikasi yang dituju menggunakan protokol yang ditentukan di poin #2. Jadi harus tahu fitur apa saja yang tersedia, bagaimana memanggilnya, apa inputnya, bagaimana format outputnya, apa tipe datanya, dsb.</li>
</ol>
<p>Kalau tiga faktor di atas terpenuhi, jangankan cuma aplikasi bank di Indonesia, aplikasinya Snowden juga bisa kita akses ;)</p>
<p>Oh, sebelum artikel ini diakhiri, saya harus bikin disclaimer dulu.</p>
<blockquote>
<p>Saya tidak bertanggung jawab kalau ada <em>smarta$$</em> yang setelah baca artikel ini lalu nekat mengakses aplikasi orang lain hanya bermodalkan #2 dan #3 tanpa punya #1.</p>
</blockquote>
Meningkatkan performance Android Emulator2013-12-10T17:39:00+07:00https://software.endy.muhardin.com/java/meningkatkan-performance-android-emulator<p>Setelah kemarin kita mempersiapkan persenjataan untuk coding Android, langkah selanjutnya tentu saja membuat aplikasi <code class="language-plaintext highlighter-rouge">Hello World</code> dan kemudian mencoba menjalankannya. Ada dua pilihan cara menjalankan aplikasi Android, yaitu langsung di <em>device</em> atau menggunakan emulator.</p>
<p>Sayangnya, secara default emulator Android sangat lemot. Sedangkan kecepatan kita dalam membangun aplikasi sangat ditentukan oleh kecepatan kita melakukan siklus <em>edit-test-fix</em>. Kalau langkah <em>test</em> butuh waktu lama, dengan sendirinya proses development kita juga akan butuh waktu lama.</p>
<p>Untungnya ada teknik yang tersedia untuk meningkatkan kecepatan emulator Android, yaitu dengan <a href="http://software.intel.com/en-us/articles/speeding-up-the-android-emulator-on-intel-architecture">menggunakan emulator berbasis Intel platform</a>. Kita akan bahas cara-caranya di artikel ini.</p>
<p><a href="http://lh5.googleusercontent.com/-ynU8SU2Efd0/Uqa45WE4K_I/AAAAAAAAC7w/umEdkuO7wqA/w467-h623-no/20131210_134625.jpg"><img src="http://lh5.googleusercontent.com/-ynU8SU2Efd0/Uqa45WE4K_I/AAAAAAAAC7w/umEdkuO7wqA/w467-h623-no/20131210_134625.jpg" alt="Foto" /></a></p>
<!--more-->
<p>Prinsip dasarnya adalah, kita memanfaatkan fitur virtualisasi yang sudah tersedia di prosesor Intel generasi saat ini. Dengan fitur ini, emulator Android bisa berjalan dengan lebih cepat karena ada dukungan di prosesor. Tentunya terlebih dulu kita harus melakukan pengecekan apakah prosesor di komputer kita sudah mendukung fitur ini atau belum.</p>
<p>Di Windows dan Mac, fitur ini diaktifkan dengan driver khusus dari Intel yang disebut dengan Intel® Hardware Accelerated Execution Manager (Intel® HAXM). Sedangkan pada Linux, fitur ini tersedia dalam aplikasi virtualisasi KVM.</p>
<p>Mari kita bahas cara pengecekannya satu persatu dengan Linux maupun dengan Windows.</p>
<h2 id="instalasi-di-linux">Instalasi di Linux</h2>
<p>Berikut adalah langkah-langkahnya:</p>
<ol>
<li>Verifikasi apakah prosesor kita sudah mendukung</li>
<li>Instalasi KVM</li>
<li>Membuat emulator Intel di Android Virtual Device (AVD) Manager</li>
</ol>
<h3 id="verifikasi-dukungan-prosesor">Verifikasi Dukungan Prosesor</h3>
<p>Di Ubuntu, kita bisa menjalankan perintah berikut di terminal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>egrep -c '(vmx|svm)' /proc/cpuinfo
</code></pre></div></div>
<p>Kalau outputnya <code class="language-plaintext highlighter-rouge">0</code>, berarti komputer kita tidak support. Angka <code class="language-plaintext highlighter-rouge">1</code> atau lebih menunjukkan bahwa komputer kita mendukung KVM.</p>
<h3 id="instalasi-kvm">Instalasi KVM</h3>
<p>Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils
</code></pre></div></div>
<p>Setelah selesai, kita harus logout dan login lagi, karena Ubuntu menambahkan user kita ke grup <code class="language-plaintext highlighter-rouge">libvirtd</code>. Penambahan ke grup ini baru dibaca pada saat kita login.</p>
<p>Kita bisa tes kesuksesan proses instalasi, jalankan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>virsh -c qemu:///system list
</code></pre></div></div>
<p>Bila sukses, outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Id Name State
----------------------------------
</code></pre></div></div>
<p>Bila gagal, outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>libvir: Remote error : Permission denied
error: failed to connect to the hypervisor
</code></pre></div></div>
<p>Selanjutnya, kita tinggal membuat Android Virtual Device (AVD) yang menggunakan platform Intel. Langkah ini akan kita bahas setelah penjelasan tentang cara instalasi Intel® HAXM di Windows.</p>
<h2 id="instalasi-di-windows">Instalasi di Windows</h2>
<p>Instalasi Intel® HAXM dilakukan melalui Android SDK Manager. Opsinya ada di folder Extra di paling bawah.
Jangan lupa instal juga emulator Intel sekaligus.</p>
<p><a href="http://lh4.googleusercontent.com/-ZitwHacTB50/UqcZWbt1alI/AAAAAAAAC8Y/T_rChtMx4m0/w746-h576-no/haxm01.png"><img src="http://lh4.googleusercontent.com/-ZitwHacTB50/UqcZWbt1alI/AAAAAAAAC8Y/T_rChtMx4m0/w746-h576-no/haxm01.png" alt="Foto" /></a></p>
<p>Instalasi melalui Android SDK Manager ini ternyata hanya mengunduh Intel® HAXM saja. Kita tetap harus menginstalnya sendiri. Cari filenya dalam folder <code class="language-plaintext highlighter-rouge">extras</code> dalam Android SDK.</p>
<p><a href="http://lh6.googleusercontent.com/-vyyFTq3bx_4/UqcZVfev3QI/AAAAAAAAC8Q/fAcHpy2KJMo/w233-h122-no/haxm02.png"><img src="http://lh6.googleusercontent.com/-vyyFTq3bx_4/UqcZVfev3QI/AAAAAAAAC8Q/fAcHpy2KJMo/w233-h122-no/haxm02.png" alt="Foto" /></a></p>
<p>Jalankan saja installer seperti biasa. Klik Next sampai selesai.</p>
<h2 id="membuat-emulator-intel">Membuat Emulator Intel</h2>
<p>Selanjutnya, kita tinggal membuat AVD seperti biasa. Pastikan kita pilih opsi platform Intel.</p>
<p><a href="http://lh3.googleusercontent.com/-phoDzK--s64/Uqcbh-dQWzI/AAAAAAAAC8s/2WWFtnFY1TE/w572-h623-no/Edit+Android+Virtual+Device+%2528AVD%2529+_001.png"><img src="http://lh3.googleusercontent.com/-phoDzK--s64/Uqcbh-dQWzI/AAAAAAAAC8s/2WWFtnFY1TE/w572-h623-no/Edit+Android+Virtual+Device+%2528AVD%2529+_001.png" alt="Foto" /></a></p>
<p>Jangan lupa untuk mencentang juga opsi <code class="language-plaintext highlighter-rouge">Use Host GPU</code> untuk mengalihkan beban pemrosesan tampilan dari emulator ke komputer kita.</p>
<p>Selamat mencoba, seharusnya sekarang emulator bisa dijalankan dengan lebih cepat. Oh iya, ada sedikit keterbatasan. Pada saat artikel ini ditulis, Intel belum merilis emulator untuk Android versi terbaru (KitKat / API level 19). Jadi kita terpaksa coding menggunakan API level 18.</p>
<p>Semoga bermanfaat</p>
Cara Mengunduh Website2013-11-28T10:04:00+07:00https://software.endy.muhardin.com/linux/cara-mengunduh-website<p>Banyak pembaca blog saya yang bertanya</p>
<blockquote>
<p>Mas/Om/Pak/Bos/whatever, bolehkah isi blognya saya download?</p>
</blockquote>
<p>Jawabannya tentu saja boleh. Kalau tidak boleh buat apa saya publish ;)</p>
<p>Pertanyaan selanjutnya</p>
<blockquote>
<p>Bagaimana cara mengunduh seluruh isi website supaya bisa dibuka tanpa internet?</p>
</blockquote>
<p>Gampang, begini caranya.</p>
<!--more-->
<p>Linux sudah mempunyai aplikasi pengunduh yang sangat sakti, namanya <code class="language-plaintext highlighter-rouge">wget</code>. Kita tinggal jalankan di command line dengan opsi-opsi yang sesuai, maka dia akan mengunduh seluruh website untuk kita. Aplikasi <code class="language-plaintext highlighter-rouge">wget</code> ini umumnya sudah tersedia di semua distro linux populer, sehingga tidak dibutuhkan langkah instalasi tambahan.</p>
<blockquote>
<p>Kok command line? Apa tidak ada yang GUI?</p>
</blockquote>
<p>Sebetulnya ada banyak. Tapi untuk keperluan download, aplikasi command line jauh lebih baik. Kita bisa login melalui <code class="language-plaintext highlighter-rouge">ssh</code> ke mesin lain yang memiliki koneksi internet bagus dan menjalankan <code class="language-plaintext highlighter-rouge">wget</code> disana. Tinggalkan beberapa hari, setelah itu dicek apakah sudah selesai. Begitu selesai, kita tinggal copy ke laptop kita. Hal ini sulit dilakukan dengan aplikasi berbasis GUI.</p>
<p>Tanpa banyak basa-basi, inilah perintah <code class="language-plaintext highlighter-rouge">wget</code> beserta opsi-opsi yang dibutuhkan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget \
--recursive \
--convert-links \
--adjust-extension \
--page-requisites \
--span-hosts \
-e robots=off \
--timestamping \
--domains=software.endy.muhardin.com \
http://software.endy.muhardin.com
</code></pre></div></div>
<p>Penjelasan opsi:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">recursive</code> : tiap ada link di halaman web, link tersebut ikut diunduh</li>
<li><code class="language-plaintext highlighter-rouge">convert-links</code> : ubah link (halaman lain, gambar, javascript, dsb) menjadi link lokal, supaya dia tidak mengarah ke internet</li>
<li><code class="language-plaintext highlighter-rouge">adjust-extension</code> : bila ada link yang dinamis seperti <code class="language-plaintext highlighter-rouge">cgi</code>, <code class="language-plaintext highlighter-rouge">asp</code>, <code class="language-plaintext highlighter-rouge">php</code>, konversi menjadi extension statis seperti <code class="language-plaintext highlighter-rouge">html</code></li>
<li><code class="language-plaintext highlighter-rouge">page-requisites</code> : unduh semua kebutuhan halaman supaya tampilannya sesuai asli (misalnya gambar, stylesheet, dsb)</li>
<li><code class="language-plaintext highlighter-rouge">span-hosts</code> : banyak orang (termasuk saya) menghosting gambar di layanan publik seperti flickr, google plus, dan lainnya. Opsi ini menyuruh <code class="language-plaintext highlighter-rouge">wget</code> supaya link di luar website saya juga diunduh</li>
<li><code class="language-plaintext highlighter-rouge">robots=off</code> : ada beberapa website yang melarang halaman dibuka selain oleh browser. Opsi ini perlu supaya larangan tersebut diabaikan</li>
<li><code class="language-plaintext highlighter-rouge">timestamping</code> : timestamp tiap file diset sesuai di server. Ini memungkinkan kita untuk menjalankan lagi wget di lain hari untuk mengupdate website yang sudah pernah kita unduh. Dengan opsi ini, file yang sudah diunduh tidak akan diunduh ulang. Menghemat bandwidth dan waktu</li>
<li><code class="language-plaintext highlighter-rouge">domains</code> : membatasi halaman yang diunduh hanya untuk domain tertentu</li>
<li>URL website yang ingin diambil. Pada contoh di atas adalah <code class="language-plaintext highlighter-rouge">http://software.endy.muhardin.com</code></li>
</ul>
<p>Demikianlah cara mengunduh website ini. Semoga bermanfaat.</p>
<blockquote>
<p>Tunggu … tunggu … Saya pakai Windows, bagaimana caranya mengunduh di Windows?</p>
</blockquote>
<p>Gampang, <a href="http://software.endy.muhardin.com/linux/upgrade-ubuntu/">instal saja Ubuntu</a> :D</p>
Persiapan Coding Android2013-11-27T15:04:00+07:00https://software.endy.muhardin.com/android/persiapan-coding-android<p>Jaman sekarang ini, hidup orang sudah berpindah ke smartphone. PC sudah ditinggalkan, laptop juga semakin mengecil porsi pemakaiannya. Sebagai programmer, kitapun harus ikut menyesuaikan skill agar bisa ikut membuat aplikasi mobile.</p>
<p>Hal pertama yang kita lakukan sebelum mulai coding tentunya adalah mempersiapkan persenjataan. Dalam artikel ini, kita akan membahas persiapan yang perlu dilakukan untuk memulai membuat aplikasi Android.</p>
<p>Sesuai <a href="http://software.endy.muhardin.com/life/lan-na-zha/">prinsip Lan-Na-Zha</a>, yang dibahas di sini adalah dasar-dasar pemrograman Android. Supaya bisa paham, kita tidak akan menggunakan alat bantu yang disediakan IDE. Kita akan lakukan dengan command line dan text editor.</p>
<!--more-->
<p>Secara garis besar, berikut ini adalah hal-hal yang perlu kita siapkan:</p>
<ol>
<li>Instalasi Java dan Maven. Caranya bisa dibaca <a href="http://software.endy.muhardin.com/java/persiapan-coding-java/">di artikel ini</a>.</li>
<li>Instalasi <a href="http://developer.android.com/sdk/index.html">Android SDK</a></li>
<li>Instalasi IDE. Ada dua pilihan di sini: <a href="http://developer.android.com/sdk/index.html">Eclipse ADT</a> atau <a href="http://developer.android.com/sdk/installing/studio.html">Android Studio</a> yang berbasis IntelliJ IDEA.</li>
</ol>
<p>Sebelum bisa digunakan, ada beberapa hal yang perlu kita lakukan agar persenjataan tersebut siap dipakai:</p>
<ol>
<li><a href="#environment-variable-android-sdk">Konfigurasi <code class="language-plaintext highlighter-rouge">environment variable</code></a></li>
<li><a href="#update-android-sdk">Update Android SDK</a></li>
<li><a href="#konfigurasi-repository-maven-android">Konfigurasi repository Maven</a></li>
</ol>
<p>Setelah semua dilakukan, kita bisa coba membuat project Hello World:</p>
<ol>
<li><a href="#create-project-android">Create Android Project</a></li>
<li><a href="#konversi-project-android-maven">Konversi Android Project menjadi Maven Project</a></li>
<li>Buka project di IDE</li>
<li>Modifikasi sedikit</li>
<li><a href="#deploy-emulator">Deploy di emulator</a></li>
<li><a href="#deploy-handset">Deploy di handset</a></li>
</ol>
<p><a name="struktur-project-android"></a></p>
<h2 id="struktur-project-android">Struktur Project Android</h2>
<p>Sebelum masuk ke teknis coding, kita pahami dulu beberapa konsep. Dalam project Java biasa, kita mengenal ada beberapa <em>build-tools</em> yang umum digunakan orang, diantaranya:</p>
<ul>
<li>Format bebas</li>
<li>Ant</li>
<li>Maven</li>
<li>Gradle</li>
</ul>
<p><em>Build-tools</em> ini akan menentukan bagaimana struktur folder dan penempatan file-file di dalamnya. Ini yang kita sebut dengan struktur project. Selain yang ditentukan oleh <em>build-tools</em> ada juga struktur project yang ditentukan oleh IDE yang digunakan, misalnya format project:</p>
<ul>
<li>Eclipse</li>
<li>Netbeans</li>
<li>IntelliJ IDEA</li>
</ul>
<p>Hal ini juga terjadi di aplikasi Android. Setidaknya ada tiga jenis struktur project:</p>
<ul>
<li>Maven Project</li>
<li>Eclipse ADT</li>
<li>Android Studio (berbasis Gradle)</li>
</ul>
<blockquote>
<p>Kenapa kita pusingkan hal ini?</p>
</blockquote>
<p>Saya biasanya tidak mau terikat ke salah satu IDE saja. Contohnya, bila kita <em>Create New Project</em> dengan Eclipse, belum tentu bisa dibuka dengan sempurna di Netbeans. Begitu juga sebaliknya.</p>
<p>Agar lebih fleksibel, saya mencari format project yang universal, bisa dikenali dan dibuka dengan baik di semua IDE. Bahkan juga bisa diedit menggunakan text editor. Untuk itu kita pilih <em>Maven</em>, karena didukung oleh semua IDE yang ada di dunia.</p>
<p>Pemilihan <em>build-tools</em> akan menentukan struktur project, dan pada akhirnya menentukan cara kerja tim dalam membuat aplikasi. Dengan menggunakan Maven, kita mendapatkan keuntungan sebagai berikut:</p>
<ul>
<li>portability: Semua orang bisa download source code, kemudian buka di IDE apapun yang dia suka menggunakan sistem operasi apapun yang dia suka.</li>
<li>Setting laptop masing-masing bebas, tidak seperti struktur project Netbeans atau Eclipse yang mengharuskan anggota tim sepakat dimana lokasi file <code class="language-plaintext highlighter-rouge">jar</code> yang dibutuhkan (akan lebih sulit kalau ada yang pakai Linux dan ada yang pakai Windows).</li>
<li>File <code class="language-plaintext highlighter-rouge">jar</code> yang dibutuhkan tidak harus dicommit bersama source code. Jadi repository Git kita hanya berisi source code saja, tidak dikotori file-file dependensi</li>
</ul>
<h2 id="instalasi-aplikasi">Instalasi Aplikasi</h2>
<p>Ada beberapa hal yang kita butuhkan di sini:</p>
<ul>
<li>Android SDK</li>
<li>IDE, boleh pilih Eclipse ADT ataupun Android Studio. Bebas saja</li>
</ul>
<p>Instalasi Java SDK dan Maven bisa dilihat <a href="http://software.endy.muhardin.com/java/persiapan-coding-java/">di artikel ini</a>. Android SDK, dan IDE manapun juga tidak dibahas instalasinya, karena cukup di_extract_ saja file <code class="language-plaintext highlighter-rouge">zip</code>nya sudah selesai ;)</p>
<h2 id="konfigurasi-dan-update">Konfigurasi dan Update</h2>
<p><a name="environment-variable-android-sdk"></a></p>
<h3 id="environment-variable">Environment Variable</h3>
<p>Kita perlu membuat environment variable baru sebagai berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">ANDROID_HOME</code> : lokasi extract Android SDK</li>
</ul>
<p>Dan mengedit environment variable bernama <code class="language-plaintext highlighter-rouge">PATH</code>. Tambahkan:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">ANDROID_HOME/tools</code> dan <code class="language-plaintext highlighter-rouge">ANDROID_HOME/platform-tools</code></li>
</ul>
<p>Di Ubuntu, buat file <code class="language-plaintext highlighter-rouge">/etc/profile.d/java-env.sh</code> yang isinya seperti ini</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">JAVA_HOME</span><span class="o">=</span>/usr/lib/jvm/java-7-openjdk-amd64
<span class="nb">export </span><span class="nv">M2_HOME</span><span class="o">=</span>/opt/apache-maven-3.2.3
<span class="nb">export </span><span class="nv">ANDROID_HOME</span><span class="o">=</span>/home/endy/Apps/android-sdk-linux
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$JAVA_HOME</span>/bin:<span class="nv">$M2_HOME</span>/bin:<span class="nv">$ANDROID_HOME</span>/tools:<span class="nv">$ANDROID_HOME</span>/platform-tools
</code></pre></div></div>
<p>Ganti isi <code class="language-plaintext highlighter-rouge">JAVA_HOME</code>, <code class="language-plaintext highlighter-rouge">M2_HOME</code>, dan <code class="language-plaintext highlighter-rouge">ANDROID_HOME</code> sesuai dengan lokasi di komputer Anda masing-masing.</p>
<p>Setelah itu, restart komputer supaya isi file tersebut dibaca semua aplikasi.</p>
<p>Sekarang kita bahas untuk Windows. Cara mengedit environment variable sama seperti yang dijelaskan di sini.</p>
<p>Buat variabel baru di System Variable untuk <code class="language-plaintext highlighter-rouge">ANDROID_HOME</code>.</p>
<p><a href="http://lh4.googleusercontent.com/-O7f60nY5hdg/UpVtYN-9DGI/AAAAAAAACe8/sKR8b7FLBqE/s600/07.+ANDROID_HOME.png"><img src="http://lh4.googleusercontent.com/-O7f60nY5hdg/UpVtYN-9DGI/AAAAAAAACe8/sKR8b7FLBqE/s600/07.+ANDROID_HOME.png" alt="Foto" /></a></p>
<p>Kemudian edit variabel <code class="language-plaintext highlighter-rouge">PATH</code>. Tambahkan:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>whatever-isi-path-yang-lama;%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools
</code></pre></div></div>
<p>Klik OK untuk menutup semua dialog. Setelah itu kita test. Buka command prompt baru. Kalau sebelumnya sudah membuka command prompt, harus ditutup dulu dan buka yang baru. Jika konfigurasinya benar, kita bisa menjalankan Android SDK Manager dari command prompt seperti ini</p>
<p><a href="http://lh6.googleusercontent.com/-rij7T-pjn68/UpVtZ4POW-I/AAAAAAAACfQ/djaLB5KDbwo/s600/10.+Test+Android+SDK.png"><img src="http://lh6.googleusercontent.com/-rij7T-pjn68/UpVtZ4POW-I/AAAAAAAACfQ/djaLB5KDbwo/s600/10.+Test+Android+SDK.png" alt="Foto" /></a></p>
<p><a name="update-android-sdk"></a></p>
<h2 id="update-android-sdk">Update Android SDK</h2>
<p>Secara default, Android SDK yang baru saja kita extract hanya menyertakan library terbaru saja, pada waktu artikel ini ditulis versinya <code class="language-plaintext highlighter-rouge">4.4</code> atau <em>Kitkat</em>.</p>
<p><a href="http://lh5.googleusercontent.com/-vczCzhCpYws/UpVjYYmGRKI/AAAAAAAACdg/5p0aXCK_j0k/s600/01.+Android+SDK+Default.png"><img src="http://lh5.googleusercontent.com/-vczCzhCpYws/UpVjYYmGRKI/AAAAAAAACdg/5p0aXCK_j0k/s600/01.+Android+SDK+Default.png" alt="Foto" /></a></p>
<p>Seperti kita lihat pada screenshot, yang statusnya <code class="language-plaintext highlighter-rouge">Installed</code> cuma versi terakhir saja.</p>
<p>Kita perlu mengunduh library versi lain jika ingin membuat aplikasi yang bisa berjalan di versi tersebut. Misalnya kita ingin aplikasi yang kita buat bisa dijalankan di Android versi <em>Ice Cream Sandwich (ICS)</em>, maka kita perlu mengupdate Android SDK agar punya versi tersebut.</p>
<p>Berdasarkan dokumentasi di website resmi Android, kita <a href="http://developer.android.com/training/basics/supporting-devices/platforms.html">cukup menginstal versi terkecil dan terbesar yang mau didukung</a>. Tidak perlu semua versi di antaranya. Sebagai contoh, <a href="http://source.android.com/source/build-numbers.html">versi-versi Android</a> adalah:</p>
<ul>
<li>Donut (1.6)</li>
<li>Eclair (2.0 - 2.1)</li>
<li>Froyo (2.2)</li>
<li>Gingerbread (2.3)</li>
<li>Honeycomb (3.x)</li>
<li>Ice Cream Sandwich (4.0)</li>
<li>Jelly Bean (4.1 - 4.3)</li>
<li>Kitkat (4.4)</li>
</ul>
<p>Kalau kita mau mendukung perangkat mulai dari Honeycomb sampai Kitkat, maka kita cukup punya versi <code class="language-plaintext highlighter-rouge">3.0</code> dan <code class="language-plaintext highlighter-rouge">4.4</code> saja. Versi <code class="language-plaintext highlighter-rouge">3.0</code> dikenal juga dengan sebutan <code class="language-plaintext highlighter-rouge">API level 11</code> dan <code class="language-plaintext highlighter-rouge">4.4</code> dikenal dengan sebutan <code class="language-plaintext highlighter-rouge">API level 19</code>.</p>
<p>Selanjutnya, kita centang <code class="language-plaintext highlighter-rouge">API</code> yang ingin kita instal, kemudian tekan tombol Install Package</p>
<p><a href="http://lh3.googleusercontent.com/-fUnAXSB8xes/UpVjYe8lu4I/AAAAAAAACdk/8i7PtYMkJI4/s600/02.+Additional+Package.png"><img src="http://lh3.googleusercontent.com/-fUnAXSB8xes/UpVjYe8lu4I/AAAAAAAACdk/8i7PtYMkJI4/s600/02.+Additional+Package.png" alt="Foto" /></a></p>
<p>Paket yang diinstal cukup besar. Dengan koneksi internet saya yang berkecepatan 60 KB/s, butuh waktu 60-90 menit untuk mengunduh versi Honeycomb.</p>
<p><a name="konfigurasi-repository-maven-android"></a></p>
<h2 id="konfigurasi-repository-maven">Konfigurasi Repository Maven</h2>
<p>Penggunaan Maven untuk project Android dimungkinkan dengan plugin <a href="https://code.google.com/p/maven-android-plugin/">Android Maven Plugin</a>. Plugin ini tidak perlu diinstal, karena seperti halnya plugin maven pada umumnya, kita tinggal deklarasikan saja dan Maven akan mengunduhnya dari internet.</p>
<p>Sebetulnya pluginnya sendiri tidak bermasalah dan bekerja dengan sangat baik. Masalahnya ada di <code class="language-plaintext highlighter-rouge">dependency</code> untuk library Android. Entah karena sebab apa, <code class="language-plaintext highlighter-rouge">jar</code> yang kita butuhkan tidak tersedia di <a href="http://search.maven.org">repository Maven Central</a>. Yang ada di sana hanyalah versi lama. Pada waktu artikel ini ditulis, <a href="http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.google.android%22%20AND%20a%3A%22android%22">versi Android terakhir yang tersedia</a> adalah versi <code class="language-plaintext highlighter-rouge">4.1.1.4</code>. Padahal kita butuh versi <code class="language-plaintext highlighter-rouge">4.4</code>.</p>
<p>Untungnya sudah ada solusinya, yaitu <a href="https://github.com/mosabua/maven-android-sdk-deployer">Maven Android SDK Deployer</a>. Cara kerjanya sebagai berikut:</p>
<ul>
<li>Kita harus sudah memiliki Android SDK yang sudah terupdate seperti pada langkah sebelumnya</li>
<li>Maven Android SDK Deployer akan mengkonversi <code class="language-plaintext highlighter-rouge">jar</code> dalam Android SDK menjadi Maven artifact</li>
<li>Artifact ini akan diinstal di repository local, yaitu dalam folder <code class="language-plaintext highlighter-rouge">.m2</code> dalam <code class="language-plaintext highlighter-rouge">home</code> kita.</li>
<li>Bila dibutuhkan, kita juga bisa memasangnya di repository kantor (semacam <a href="http://www.sonatype.org/nexus/">Nexus</a> atau <a href="http://www.jfrog.com/home/v_artifactory_opensource_overview">Artifactory</a>)</li>
<li>Untuk membedakan dengan yang ada di Maven Central, <code class="language-plaintext highlighter-rouge">groupId</code> dari <code class="language-plaintext highlighter-rouge">jar</code> hasil konversi akan diberi nama <code class="language-plaintext highlighter-rouge">android</code>. Jadi kita harus menyesuaikan dependensi di <code class="language-plaintext highlighter-rouge">pom.xml</code> kita</li>
</ul>
<p>Cara pakainya mudah, pertama kita clone dulu dari Github</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:mosabua/maven-android-sdk-deployer.git
</code></pre></div></div>
<p>Masuk ke foldernya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd maven-android-sdk-deployer
</code></pre></div></div>
<p>Install versi Android yang kita inginkan. Misalnya untuk menginstal versi <code class="language-plaintext highlighter-rouge">4.4</code> (Kitkat)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn install -P 4.4
</code></pre></div></div>
<p>Kalau sukses, outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] Maven Android SDK Deployer ........................ SUCCESS [0.534s]
[INFO] Android Platforms ................................. SUCCESS [0.006s]
[INFO] Android Platform 4.4 API 19 ....................... SUCCESS [0.463s]
[INFO] Android Add-Ons ................................... SUCCESS [0.006s]
[INFO] Android Add-On Google Platform 4.4 API 19 (Maps and USB) SUCCESS [0.026s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.559s
[INFO] Finished at: Tue Nov 26 21:41:56 WIT 2013
[INFO] Final Memory: 13M/211M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<p>Agar bisa dijalankan dengan baik, Maven Android SDK Deployer membutuhkan:</p>
<ul>
<li>Environment variable <code class="language-plaintext highlighter-rouge">ANDROID_HOME</code> seperti yang telah kita konfigurasi di atas</li>
<li>
<p>Android SDK yang sudah diupdate sehingga memiliki versi yang ingin kita instal. Untuk tiap versi yang dibutuhkan, kita harus menginstal</p>
<ul>
<li>SDK Platform</li>
<li>Google APIs</li>
</ul>
</li>
</ul>
<p>Coba perhatikan apa-apa yang saya centang untuk diinstal di screenshot ini</p>
<p><a href="http://lh3.googleusercontent.com/-fUnAXSB8xes/UpVjYe8lu4I/AAAAAAAACdk/8i7PtYMkJI4/s600/02.+Additional+Package.png"><img src="http://lh3.googleusercontent.com/-fUnAXSB8xes/UpVjYe8lu4I/AAAAAAAACdk/8i7PtYMkJI4/s600/02.+Additional+Package.png" alt="Foto" /></a></p>
<p>Selanjutnya, kita deploy juga Maven artifact untuk versi <code class="language-plaintext highlighter-rouge">3.0</code> (Honeycomb)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn install -P 3.0
</code></pre></div></div>
<p>Hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] Reactor Summary:
[INFO]
[INFO] Maven Android SDK Deployer ........................ SUCCESS [0.578s]
[INFO] Android Platforms ................................. SUCCESS [0.008s]
[INFO] Android Platform 3.0 API 11 ....................... SUCCESS [0.154s]
[INFO] Android Add-Ons ................................... SUCCESS [0.009s]
[INFO] Android Add-On Google Platform 3.0 API 11 (Maps) .. SUCCESS [0.020s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.313s
[INFO] Finished at: Tue Nov 26 22:22:20 WIT 2013
[INFO] Final Memory: 8M/149M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<p>Persiapan selesai. Selanjutnya kita akan buat project pertama.</p>
<p><a name="create-project-android"></a></p>
<h2 id="create-android-project">Create Android Project</h2>
<p>Agar tahu cara kerjanya, kita lakukan prosedur <em>create project</em> secara manual menggunakan command line. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android create project --target 3 --name AplikasiAndroid \
--path /home/endy/tmp/AplikasiAndroid --activity HelloWorldActivity \
--package com.muhardin.endy.belajar.android
</code></pre></div></div>
<p>Perintah <code class="language-plaintext highlighter-rouge">android create project</code> ini memerlukan beberapa opsi yaitu:</p>
<ul>
<li>
<p>target : adalah versi Android yang akan didukung aplikasi kita. <a href="http://developer.android.com/training/basics/firstapp/creating-project.html">Gunakan versi tertinggi/terbaru</a>. Nilai target ini berbeda-beda tergantung versi Android yang sudah terpasang di Android SDK. Untuk mengetahui nilai target yang tersedia, jalankan perintah <code class="language-plaintext highlighter-rouge">android list target</code>. Hasilnya di laptop saya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Available Android targets:
----------
id: 1 or "android-11"
Name: Android 3.0
Type: Platform
API level: 11
Revision: 2
Skins: WXGA (default)
ABIs : armeabi
----------
id: 2 or "Google Inc.:Google APIs:11"
Name: Google APIs
Type: Add-On
Vendor: Google Inc.
Revision: 1
Description: Android + Google APIs
Based on Android 3.0 (API level 11)
Libraries:
* com.google.android.maps (maps.jar)
API for Google Maps
Skins: WXGA (default)
ABIs : armeabi
----------
id: 3 or "android-19"
Name: Android 4.4
Type: Platform
API level: 19
Revision: 1
Skins: WSVGA, WVGA800 (default), WXGA800, QVGA, WQVGA400, WXGA720, WVGA854, HVGA, WXGA800-7in, WQVGA432
ABIs : armeabi-v7a
----------
id: 4 or "Google Inc.:Google APIs:19"
Name: Google APIs
Type: Add-On
Vendor: Google Inc.
Revision: 1
Description: Android + Google APIs
Based on Android 4.4 (API level 19)
Libraries:
* com.google.android.media.effects (effects.jar)
Collection of video effects
* com.android.future.usb.accessory (usb.jar)
API for USB Accessories
* com.google.android.maps (maps.jar)
API for Google Maps
Skins: WVGA854, WQVGA400, WSVGA, WXGA800-7in, WXGA720, HVGA, WQVGA432, QVGA, WVGA800 (default), WXGA800
ABIs : armeabi-v7a
</code></pre></div> </div>
</li>
<li>name : Nama aplikasi.</li>
<li>path : lokasi folder tempat aplikasi akan dibuat.</li>
<li>activity : Activity yang akan dibuatkan otomatis. Apa itu activity akan kita bahas di artikel lain</li>
<li>package : <code class="language-plaintext highlighter-rouge">package</code> untuk meletakkan source code activity yang dibuat. Sama seperti <code class="language-plaintext highlighter-rouge">package</code> Java pada umumnya.</li>
</ul>
<p>Berikut output dari <code class="language-plaintext highlighter-rouge">android create project</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Created project directory: /home/endy/tmp/AplikasiAndroid
Created directory /home/endy/tmp/AplikasiAndroid/src/com/muhardin/endy/belajar/android
Added file /home/endy/tmp/AplikasiAndroid/src/com/muhardin/endy/belajar/android/HelloWorldActivity.java
Created directory /home/endy/tmp/AplikasiAndroid/res
Created directory /home/endy/tmp/AplikasiAndroid/bin
Created directory /home/endy/tmp/AplikasiAndroid/libs
Created directory /home/endy/tmp/AplikasiAndroid/res/values
Added file /home/endy/tmp/AplikasiAndroid/res/values/strings.xml
Created directory /home/endy/tmp/AplikasiAndroid/res/layout
Added file /home/endy/tmp/AplikasiAndroid/res/layout/main.xml
Created directory /home/endy/tmp/AplikasiAndroid/res/drawable-xhdpi
Created directory /home/endy/tmp/AplikasiAndroid/res/drawable-hdpi
Created directory /home/endy/tmp/AplikasiAndroid/res/drawable-mdpi
Created directory /home/endy/tmp/AplikasiAndroid/res/drawable-ldpi
Added file /home/endy/tmp/AplikasiAndroid/AndroidManifest.xml
Added file /home/endy/tmp/AplikasiAndroid/build.xml
Added file /home/endy/tmp/AplikasiAndroid/proguard-project.txt
</code></pre></div></div>
<p>Masih ingat <a href="#struktur-project-android">penjelasan struktur project di atas</a>? Project yang dibuatkan Android SDK ini mengikuti struktur Eclipse ADT. Kita perlu mengubahnya menjadi struktur project Maven supaya bisa dibuka di Eclipse ADT maupun Android Studio.</p>
<p><a name="konversi-project-android-maven"></a></p>
<h2 id="konversi-project-android-menjadi-maven">Konversi Project Android menjadi Maven</h2>
<p>Petunjuk lengkapnya <a href="https://code.google.com/p/maven-android-plugin/wiki/GettingStarted">ada di website Android Maven Plugin</a>, di sini saya berikan rangkumannya:</p>
<ul>
<li>
<p>Buat file <code class="language-plaintext highlighter-rouge">pom.xml</code>. Kita bisa copy paste dari <a href="https://github.com/jayway/maven-android-plugin-samples/blob/master/helloflashlight/pom.xml">contoh <code class="language-plaintext highlighter-rouge">pom.xml</code> yang disediakan</a>.</p>
</li>
<li>
<p>Hapus file berikut, karena tidak dibutuhkan lagi</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">build.xml</code></li>
<li><code class="language-plaintext highlighter-rouge">ant.properties</code></li>
<li><code class="language-plaintext highlighter-rouge">local.properties</code></li>
</ul>
</li>
<li>
<p>Sesuaikan dependensi supaya mengarah ke artifact yang <a href="#konfigurasi-repository-maven-android">sudah kita install menggunakan Maven Android SDK Deployer</a>. Berikut dependensi di <code class="language-plaintext highlighter-rouge">pom.xml</code> saya</p>
</li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependencies></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>android<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>android<span class="nt"></artifactId></span>
<span class="nt"><version></span>4.4_r1<span class="nt"></version></span>
<span class="nt"><scope></span>provided<span class="nt"></scope></span>
<span class="nt"></dependency></span>
<span class="nt"></dependencies></span>
</code></pre></div></div>
<ul>
<li>
<p>Coba build dengan perintah <code class="language-plaintext highlighter-rouge">mvn clean install</code>. Kalau lancar, berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] Installing /home/endy/tmp/AplikasiAndroid/target/aplikasi-android.apk to /home/endy/.m2/repository/com/muhardin/endy/belajar/android/aplikasi-android/1.0.0-SNAPSHOT/aplikasi-android-1.0.0-SNAPSHOT.apk
[INFO] Installing /home/endy/tmp/AplikasiAndroid/pom.xml to /home/endy/.m2/repository/com/muhardin/endy/belajar/android/aplikasi-android/1.0.0-SNAPSHOT/aplikasi-android-1.0.0-SNAPSHOT.pom
[INFO] Installing /home/endy/tmp/AplikasiAndroid/target/aplikasi-android.jar to /home/endy/.m2/repository/com/muhardin/endy/belajar/android/aplikasi-android/1.0.0-SNAPSHOT/aplikasi-android-1.0.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.426s
[INFO] Finished at: Wed Nov 27 14:31:53 WIT 2013
[INFO] Final Memory: 24M/218M
[INFO] ------------------------------------------------------------------------
</code></pre></div> </div>
</li>
</ul>
<p>Project kita sudah siap dibuka di IDE. Silahkan dicoba edit dan build lagi.</p>
<p><a name="deploy-emulator"></a></p>
<h2 id="deploy-di-emulator">Deploy di Emulator</h2>
<p>Supaya bisa dideploy di emulator, terlebih dulu kita buat emulatornya di Android SDK. Kita bisa pilih versi Android dan juga ukuran layar yang diinginkan. Jalankan Android SDK, kemudian klik menu <code class="language-plaintext highlighter-rouge">Tools > Manage AVD</code></p>
<p><a href="http://lh5.googleusercontent.com/-Qv1lsyVMLOY/UpWhzrsreXI/AAAAAAAACfs/BSJ91bvXiI0/s600/03.+Konfigurasi+AVD.png"><img src="http://lh5.googleusercontent.com/-Qv1lsyVMLOY/UpWhzrsreXI/AAAAAAAACfs/BSJ91bvXiI0/s600/03.+Konfigurasi+AVD.png" alt="Foto" /></a></p>
<p>Hasilnya bisa dilihat di AVD Manager</p>
<p><a href="http://lh4.googleusercontent.com/-0ZCzrMJ58HE/UpWhzuL1sYI/AAAAAAAACfw/uZLHOeki2Kk/s600/04.+AVD+Manager.png"><img src="http://lh4.googleusercontent.com/-0ZCzrMJ58HE/UpWhzuL1sYI/AAAAAAAACfw/uZLHOeki2Kk/s600/04.+AVD+Manager.png" alt="Foto" /></a></p>
<p>Pada screenshot di atas, nama emulatornya adalah <code class="language-plaintext highlighter-rouge">Nexus4</code>. Pemilihan nama bebas saja.</p>
<p>Tambahkan konfigurasi berikut di <code class="language-plaintext highlighter-rouge">pom.xml</code> untuk mengarahkan deployment ke emulator <code class="language-plaintext highlighter-rouge">Nexus4</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<configuration>
<sdk>
<platform>19</platform>
</sdk>
<emulator>
<avd>Nexus4</avd>
<wait>30000</wait>
</emulator>
</configuration>
</plugin>
</code></pre></div></div>
<p>Konfigurasi yang saya tambahkan adalah blok <code class="language-plaintext highlighter-rouge"><emulator></code>. Sisanya disertakan agar pembaca tahu di mana harus meletakkannya.</p>
<p>Selanjutnya, kita bisa jalankan emulator dari Maven dengan perintah <code class="language-plaintext highlighter-rouge">mvn android:emulator-start</code>. Outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building AplikasiAndroid 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- android-maven-plugin:3.8.0:emulator-start (default-cli) @ aplikasi-android ---
[INFO] Android emulator command: ""/home/endy/Coding/adt-bundle-linux-x86_64-20131030/sdk/tools/emulator"" -avd Nexus4
[INFO] Found 0 devices connected with the Android Debug Bridge
[INFO] Starting android emulator with script: /tmp/android-maven-plugin-emulator-start.sh
[INFO] Waiting for emulator start:30000
[INFO] Emulator is up and running.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 22.736s
[INFO] Finished at: Wed Nov 27 14:45:13 WIT 2013
[INFO] Final Memory: 14M/217M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<p>Emulator segera aktif. Tunggu sampai dia siap.</p>
<p><a href="http://lh4.googleusercontent.com/-2Wg4lTl5IvM/UpWjpNoCAsI/AAAAAAAACgA/9qD6wVry2BQ/s600/05.+Android+Emulator.png"><img src="http://lh4.googleusercontent.com/-2Wg4lTl5IvM/UpWjpNoCAsI/AAAAAAAACgA/9qD6wVry2BQ/s600/05.+Android+Emulator.png" alt="Foto" /></a></p>
<p>Kemudian kita bisa deploy menggunakan perintah <code class="language-plaintext highlighter-rouge">mvn android:deploy</code>. Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building AplikasiAndroid 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- android-maven-plugin:3.8.0:deploy (default-cli) @ aplikasi-android ---
[INFO] Waiting for initial device list from the Android Debug Bridge
[INFO] Found 1 devices connected with the Android Debug Bridge
[INFO] android.device parameter not set, using all attached devices
[INFO] Emulator emulator-5554_Nexus4_unknown_sdk found.
[INFO] emulator-5554_Nexus4_unknown_sdk : Successfully installed /home/endy/tmp/AplikasiAndroid/target/aplikasi-android.apk to emulator-5554_Nexus4_unknown_sdk
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.010s
[INFO] Finished at: Wed Nov 27 14:48:44 WIT 2013
[INFO] Final Memory: 14M/217M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<p>Aplikasi kita sudah terdeploy dengan sukses di emulator.</p>
<p><a href="http://lh3.googleusercontent.com/-EM44OK6A32Y/UpWkehULp1I/AAAAAAAACgQ/TUMZ2MM0XLs/s600/06.+Aplikasi+Terdeploy.png"><img src="http://lh3.googleusercontent.com/-EM44OK6A32Y/UpWkehULp1I/AAAAAAAACgQ/TUMZ2MM0XLs/s600/06.+Aplikasi+Terdeploy.png" alt="Foto" /></a></p>
<p>Selanjutnya, mari kita coba di handset.</p>
<p><a name="deploy-handset"></a></p>
<h2 id="deploy-di-handset">Deploy di Handset</h2>
<p>Handset harus diaktifkan dulu mode developmentnya.
Setelah itu, cukup tancapkan handset melalui USB, lalu jalankan lagi <code class="language-plaintext highlighter-rouge">mvn android:deploy</code>. Berikut outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building AplikasiAndroid 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- android-maven-plugin:3.8.0:deploy (default-cli) @ aplikasi-android ---
[INFO] Waiting for initial device list from the Android Debug Bridge
[INFO] Found 2 devices connected with the Android Debug Bridge
[INFO] android.device parameter not set, using all attached devices
[INFO] Emulator emulator-5554_Nexus4_unknown_sdk found.
[INFO] Device 464119f3ba5d14c_samsung_SPH-D710 found.
[INFO] 464119f3ba5d14c_samsung_SPH-D710 : Successfully installed /home/endy/tmp/AplikasiAndroid/target/aplikasi-android.apk to 464119f3ba5d14c_samsung_SPH-D710
[INFO] emulator-5554_Nexus4_unknown_sdk : Successfully installed /home/endy/tmp/AplikasiAndroid/target/aplikasi-android.apk to emulator-5554_Nexus4_unknown_sdk
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.044s
[INFO] Finished at: Wed Nov 27 14:59:40 WIT 2013
[INFO] Final Memory: 14M/216M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<p>Aplikasipun terpasang di handset.</p>
<p><a href="http://lh6.googleusercontent.com/-74yIG4jiS8U/UpWnVDIWZ1I/AAAAAAAACgo/Gzsz4YGGOLo/s600/07.+Deploy+Handset.png"><img src="http://lh6.googleusercontent.com/-74yIG4jiS8U/UpWnVDIWZ1I/AAAAAAAACgo/Gzsz4YGGOLo/s600/07.+Deploy+Handset.png" alt="Foto" /></a></p>
<p>Kita bisa klik icon-nya untuk menjalankan</p>
<p><a href="http://lh3.googleusercontent.com/-0evtaY9zktE/UpWnRhqFiAI/AAAAAAAACgg/pYaTd__syOY/s600/08.+Run+Application.png"><img src="http://lh3.googleusercontent.com/-0evtaY9zktE/UpWnRhqFiAI/AAAAAAAACgg/pYaTd__syOY/s600/08.+Run+Application.png" alt="Foto" /></a></p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Demikianlah persiapan yang kita butuhkan untuk memulai coding aplikasi Android. Pesan moral dari artikel ini:</p>
<blockquote>
<p>Hal yang sebetulnya sederhana (sebenarnya cukup extract Android SDK berikut Eclipse ADT dan kemudian klik New Project) bisa menjadi panjang kalau kita lakukan dengan benar, memperhitungkan kerja tim, dan potensi perkembangan di masa depan.</p>
</blockquote>
<p><a name="yak-shaving"></a>
Pesan moral kedua:</p>
<blockquote>
<p>Kadang untuk melakukan satu hal (menulis artikel ini), kita harus melakukan hal lain terlebih dulu (menulis <a href="http://software.endy.muhardin.com/java/persiapan-coding-java/">artikel ini</a>). Yang mana kegiatan pendahulu tersebut sering terlihat tidak berguna. Fenomena ini dikenal dengan istilah <a href="http://sethgodin.typepad.com/seths_blog/2005/03/dont_shave_that.html">yak shaving</a></p>
</blockquote>
Persiapan Coding Java2013-11-27T11:10:00+07:00https://software.endy.muhardin.com/java/persiapan-coding-java<p>Sebelum kita mulai coding, langkah pertama tentulah menginstal persenjataan. Perlengkapan minimal yang dibutuhkan untuk coding Java sebenarnya cuma:</p>
<ul>
<li>Java SDK</li>
<li>Text editor. Tidak perlu yang canggih, asal ada warnanya seperti Gedit, Notepad++, dan sejenisnya</li>
</ul>
<p>Kalau sudah pakai library tambahan –seperti driver database, framework, dan lainnya– maka perlu menggunakan <a href="http://maven.apache.org">Maven</a> supaya mudah mengelolanya. Berikut adalah langkah-langkah persiapannya.</p>
<!--more-->
<h2 id="langkah-instalasi">Langkah Instalasi</h2>
<p>Secara garis besar, langkah-langkahnya sebagai berikut:</p>
<ol>
<li>Install Java SDK</li>
<li>Extract Maven</li>
<li>Setting <em>Environment Variable</em></li>
</ol>
<p>Ada 2 <em>environment variable</em> yang harus dibuat karena belum ada, yaitu:</p>
<ul>
<li>JAVA_HOME : lokasi instalasi Java SDK</li>
<li>M2_HOME : lokasi extract Maven</li>
</ul>
<p>Dan satu yang harus diedit, yaitu variabel <code class="language-plaintext highlighter-rouge">PATH</code>. Tambahkan:</p>
<ul>
<li>JAVA_HOME/bin : lokasi compiler java</li>
<li>M2_HOME/bin : lokasi perintah <code class="language-plaintext highlighter-rouge">mvn</code></li>
</ul>
<p>Berikut caranya di Ubuntu dan Windows 8.</p>
<h2 id="instalasi-di-ubuntu">Instalasi di Ubuntu</h2>
<p>Di Ubuntu tidak sulit, tinggal install saja paket <code class="language-plaintext highlighter-rouge">openjdk-7-jdk</code>. Terserah mau pakai GUI atau command line. Setelah itu kita extract <a href="http://maven.apache.org">Maven</a> di folder manapun yang kita suka. Biasanya saya extract di folder <code class="language-plaintext highlighter-rouge">/opt</code>.</p>
<p>Untuk menambahkan environment variable, kita buat file <code class="language-plaintext highlighter-rouge">/etc/profile.d/java-env.sh</code> yang isinya seperti ini</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">JAVA_HOME</span><span class="o">=</span>/usr/lib/jvm/java-7-openjdk-amd64
<span class="nb">export </span><span class="nv">M2_HOME</span><span class="o">=</span>/opt/apache-maven-3.2.3
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$JAVA_HOME</span>/bin:<span class="nv">$M2_HOME</span>/bin
</code></pre></div></div>
<p>Ganti isi <code class="language-plaintext highlighter-rouge">JAVA_HOME</code> dan <code class="language-plaintext highlighter-rouge">M2_HOME</code> sesuai dengan lokasi di komputer Anda masing-masing.</p>
<p>File yang ada di dalam folder <code class="language-plaintext highlighter-rouge">/etc/profile.d</code> akan dijalankan oleh Ubuntu pada waktu komputer dijalankan. Cobalah restart komputer untuk melihat hasilnya.</p>
<p>Mudah bukan? Bandingkan panjang penjelasannya dengan Windows di bawah ini ;p</p>
<h2 id="instalasi-di-windows">Instalasi di Windows</h2>
<h3 id="instalasi-jdk-dan-maven">Instalasi JDK dan Maven</h3>
<p>Java SDK bisa diunduh <a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">di sini</a>. Ambil yang <code class="language-plaintext highlighter-rouge">JDK</code>, bukan <code class="language-plaintext highlighter-rouge">Server JRE</code> ataupun <code class="language-plaintext highlighter-rouge">JRE</code>. Setelah terunduh, jalankan installernya, klik Next-next sampai selesai.</p>
<p>Maven bisa diunduh di <a href="http://maven.apache.org">websitenya</a>. Setelah terunduh, extract ke folder manapun. Biasanya saya taruh di folder Program Files</p>
<p><a href="http://lh4.googleusercontent.com/-88rrGGuOgjU/UpVtUVWgtII/AAAAAAAACeU/6uGPetDKkT0/s600/01.+Folder+Program+Files.png"><img src="http://lh4.googleusercontent.com/-88rrGGuOgjU/UpVtUVWgtII/AAAAAAAACeU/6uGPetDKkT0/s600/01.+Folder+Program+Files.png" alt="Foto" /></a></p>
<p><a name="environment-variable-windows"></a></p>
<h3 id="setting-environment-variable">Setting Environment Variable</h3>
<p>Selanjutnya sama, kita harus tambahkan variabel <code class="language-plaintext highlighter-rouge">JAVA_HOME</code> dan <code class="language-plaintext highlighter-rouge">M2_HOME</code>, kemudian edit variabel <code class="language-plaintext highlighter-rouge">PATH</code>. Pertama, klik kanan My Computer di Windows Explorer.</p>
<p><a href="http://lh4.googleusercontent.com/-8Cih2iGl_pQ/UpVtUzzuPFI/AAAAAAAACeM/aPAT_hokw-Q/s600/03.+My+Computer+-+Properties.png"><img src="http://lh4.googleusercontent.com/-8Cih2iGl_pQ/UpVtUzzuPFI/AAAAAAAACeM/aPAT_hokw-Q/s600/03.+My+Computer+-+Properties.png" alt="Foto" /></a></p>
<p>Kemudian tekan Properties. Muncul layar System Information</p>
<p><a href="http://lh5.googleusercontent.com/-Sx6WUwPghwI/UpVtXZKBDhI/AAAAAAAACek/9mq4DYMJ2HQ/s600/04.+System+Properties.png"><img src="http://lh5.googleusercontent.com/-Sx6WUwPghwI/UpVtXZKBDhI/AAAAAAAACek/9mq4DYMJ2HQ/s600/04.+System+Properties.png" alt="Foto" /></a></p>
<p>Klik Advanced System Setting untuk memunculkan System Properties</p>
<p><a href="http://lh3.googleusercontent.com/-BsUFuPg6rXI/UpVtVzN8ndI/AAAAAAAACeY/WzJtJDNMDEM/s600/04.+System+Properties+-+Environment+Variables.png"><img src="http://lh3.googleusercontent.com/-BsUFuPg6rXI/UpVtVzN8ndI/AAAAAAAACeY/WzJtJDNMDEM/s600/04.+System+Properties+-+Environment+Variables.png" alt="Foto" /></a></p>
<p>Tekan tombol Environment Variables. Setelah muncul layarnya, tekan tombol <code class="language-plaintext highlighter-rouge">New</code> di kotak yang bawah (<code class="language-plaintext highlighter-rouge">System Variables</code>). Kemudian isi nama variable <code class="language-plaintext highlighter-rouge">JAVA_HOME</code> seperti ini</p>
<p><a href="http://lh3.googleusercontent.com/-ErYIcSxokVo/UpVtXlKvfFI/AAAAAAAACes/cf3WGL5toyc/s600/05.+JAVA_HOME.png"><img src="http://lh3.googleusercontent.com/-ErYIcSxokVo/UpVtXlKvfFI/AAAAAAAACes/cf3WGL5toyc/s600/05.+JAVA_HOME.png" alt="Foto" /></a></p>
<p>Nilai <code class="language-plaintext highlighter-rouge">value</code> bisa dicopy-paste dari Windows Explorer. Pastikan dia menunjuk ke lokasi instalasi Java SDK</p>
<p><a href="http://lh6.googleusercontent.com/-5RRmCmNhnQ4/UpVtU7VKGBI/AAAAAAAACeE/KVwPyHsp3a0/s600/02.+Folder+Instalasi+Java.png"><img src="http://lh6.googleusercontent.com/-5RRmCmNhnQ4/UpVtU7VKGBI/AAAAAAAACeE/KVwPyHsp3a0/s600/02.+Folder+Instalasi+Java.png" alt="Foto" /></a></p>
<p>Lakukan hal yang sama untuk variabel <code class="language-plaintext highlighter-rouge">M2_HOME</code></p>
<p><a href="http://lh6.googleusercontent.com/-ikCikrAAF9M/UpVtXuMUf5I/AAAAAAAACe0/N-Qx-fTacBc/s400/06.+M2_HOME.png"><img src="http://lh6.googleusercontent.com/-ikCikrAAF9M/UpVtXuMUf5I/AAAAAAAACe0/N-Qx-fTacBc/s400/06.+M2_HOME.png" alt="Foto" /></a></p>
<p>Cari variabel bernama <code class="language-plaintext highlighter-rouge">Path</code>, kemudian Edit</p>
<p><a href="http://lh4.googleusercontent.com/-JNctbwhxp7I/UpVtYZRQuVI/AAAAAAAACfE/y852SKFSYXM/s400/08.+PATH.png"><img src="http://lh4.googleusercontent.com/-JNctbwhxp7I/UpVtYZRQuVI/AAAAAAAACfE/y852SKFSYXM/s400/08.+PATH.png" alt="Foto" /></a></p>
<p>Isinya adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>whatever-isi-path-yang-lama;%JAVA_HOME%\bin;%M2_HOME%\bin
</code></pre></div></div>
<p>Perhatikan bahwa variabel PATH berisi banyak nilai, di Windows masing-masingnya dipisahkan oleh tanda <code class="language-plaintext highlighter-rouge">;</code>. Sedangkan di Linux, dipisahkan oleh tanda <code class="language-plaintext highlighter-rouge">:</code></p>
<p>Klik OK untuk menutup semua dialog. Setelah itu kita test. Buka command prompt baru. Kalau sebelumnya sudah membuka command prompt, harus ditutup dulu dan buka yang baru. Konfigurasi yang benar hasilnya seperti ini</p>
<p><a href="http://lh3.googleusercontent.com/-t4G5eQTWL4I/UpVtZPYbJ5I/AAAAAAAACfI/LLj_LzB9H2s/s600/09.+Test+JDK+dan+Maven.png"><img src="http://lh3.googleusercontent.com/-t4G5eQTWL4I/UpVtZPYbJ5I/AAAAAAAACfI/LLj_LzB9H2s/s600/09.+Test+JDK+dan+Maven.png" alt="Foto" /></a></p>
<p>Kalau tidak seperti itu, periksa lagi isi <code class="language-plaintext highlighter-rouge">JAVA_HOME</code>, <code class="language-plaintext highlighter-rouge">M2_HOME</code>, dan <code class="language-plaintext highlighter-rouge">PATH</code>.</p>
<p>Selamat coding.</p>
Kumpulan Tutorial Spring Framework2013-11-25T11:40:00+07:00https://software.endy.muhardin.com/java/kumpulan-tutorial-spring-framework<p>Seringkali di milis dan forum banyak yang menanyakan tentang tutorial <a href="http://projects.spring.io/spring-framework/">Spring Framework</a>. Sebetulnya di blog ini banyak, tapi repot juga kalau harus cari-cari di arsip. Jadi, saya buatkan indeks seluruh artikel tentang Spring Framework dalam blog ini. Selamat menikmati :D</p>
<!--more-->
<h3 id="fundamental">Fundamental</h3>
<ul>
<li><a href="http://software.endy.muhardin.com/java/intro-framework/">Alasan menggunakan framework</a></li>
<li><a href="http://software.endy.muhardin.com/java/memahami-dependency-injection/">Konsep Inversion of Control</a></li>
</ul>
<h3 id="konfigurasi-spring-framework">Konfigurasi Spring Framework</h3>
<ul>
<li><a href="http://software.endy.muhardin.com/java/struktur-aplikasi-java-dengan-spring-dan-maven/">Struktur folder project</a></li>
<li><a href="http://software.endy.muhardin.com/java/log4j-spring-mvc/">Konfigurasi Logging</a></li>
<li><a href="http://software.endy.muhardin.com/java/staged-deployment/">Konfigurasi Deployment</a></li>
</ul>
<h3 id="akses-database">Akses Database</h3>
<ul>
<li><a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">Koneksi database</a></li>
<li><a href="http://software.endy.muhardin.com/java/insert-update-delete-dengan-spring-jdbc/">Insert Update Delete</a></li>
<li><a href="http://software.endy.muhardin.com/java/query-dengan-spring-jdbc/">Query</a></li>
<li><a href="http://software.endy.muhardin.com/java/mengetes-akses-database/">Testing Kode Akses Database</a></li>
</ul>
<h3 id="aplikasi-web-dengan-spring-mvc">Aplikasi Web dengan Spring MVC</h3>
<ul>
<li><a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-1/">Membuat halaman web dinamis</a></li>
<li><a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-2/">Membuat form input</a></li>
<li><a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-3/">Membuat template aplikasi</a></li>
<li><a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-4/">Upload File</a></li>
<li><a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-5/">Session Management</a></li>
<li><a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-6/">Internationalization (i18n) dan Localization (l10n)</a></li>
</ul>
<h3 id="integrasi-aplikasi">Integrasi Aplikasi</h3>
<ul>
<li><a href="http://software.endy.muhardin.com/java/integrasi-aplikasi/">Konsep Integrasi Aplikasi</a></li>
<li><a href="http://software.endy.muhardin.com/java/remoting-dengan-spring/">Remoting : Menyediakan akses dan mengakses aplikasi dari aplikasi lain</a></li>
<li><a href="http://software.endy.muhardin.com/java/integrasi-pusat-cabang-1/">Studi Kasus Integrasi Aplikasi</a></li>
<li><a href="http://software.endy.muhardin.com/java/integrasi-pusat-cabang-2/">Integrasi Aplikasi melalui Gmail</a></li>
<li><a href="http://software.endy.muhardin.com/java/integrasi-pusat-cabang-3/">Messaging dan Routing</a></li>
<li><a href="http://software.endy.muhardin.com/java/spring-httpinvoker-sun-jre6-httpserver/">Menjalankan HTTP Server dengan Spring</a></li>
</ul>
<p>Kalau pusing membaca, bisa nonton <a href="https://www.youtube.com/playlist?list=PL9oC_cq7OYbxOX-SxNPsi4r17GOVyHYJI">videonya di Youtube</a>. Source code yang dibahas dalam video bisa diambil <a href="https://github.com/endymuhardin/belajar-spring">di sini</a> dan <a href="https://github.com/endymuhardin/training-2013-01">di sini</a>.</p>
Tugas Kuliah Pemrograman 12013-11-21T19:54:00+07:00https://software.endy.muhardin.com/java/tugas-kuliah-pemrograman-1<p>Pada posting sebelumnya, kita sudah membahas:</p>
<ul>
<li><a href="http://software.endy.muhardin.com/life/otodidak/">bagaimana cara belajar secara otodidak</a></li>
<li><a href="http://software.endy.muhardin.com/life/lan-na-zha/">pentingnya menguasai konsep dasar / fundamental dari segala sesuatu</a></li>
<li>pentingnya latihan <a href="http://software.endy.muhardin.com/life/rtfm/">membaca dokumentasi</a>, <a href="http://software.endy.muhardin.com/life/problem-solving/">menyelesaikan masalah</a>, dan <a href="http://software.endy.muhardin.com/aplikasi/teknik-menggunakan-google/">mencari solusi</a></li>
</ul>
<blockquote>
<p>Lalu bagaimana kalau kita baru menguasai dasar-dasar Java? Bagaimana latihannya?</p>
</blockquote>
<p>Nah, sebetulnya cuma masalah kreativitas saja. Banyak aplikasi yang bisa dibuat dengan teknik minimalis. Sebagai contoh, coba simak tugas kuliah yang saya berikan untuk mata kuliah Pemrograman 1 di Universitas Pancasila. Untuk bisa menyelesaikan tugas ini, skill yang dibutuhkan hanyalah:</p>
<ul>
<li>variabel dan tipe data</li>
<li>looping (for,while) dan percabangan (if-else)</li>
<li>menggunakan class <code class="language-plaintext highlighter-rouge">java.lang.String</code></li>
<li>baca/tulis file</li>
</ul>
<p>Referensi yang dibutuhkan hanyalah:</p>
<ul>
<li><a href="http://docs.oracle.com/javase/tutorial/index.html">Tutorial resmi dari Oracle</a> atau <a href="http://project-template.googlecode.com/files/Java%20Desktop%20-%20Ifnu%20Bima.pdf">buku Java Desktop karangan Ifnu Bima</a></li>
<li><a href="http://docs.oracle.com/javase/7/docs/api/index.html">Dokumentasi API Java (JavaDocs)</a></li>
</ul>
<p>Studi kasus pada tugas ini merupakan aplikasi yang benar-benar digunakan di dunia nyata.</p>
<p>Selamat mencoba.</p>
<blockquote>
<p>Update : tugas ini sudah saya kerjakan. Videonya bisa <a href="http://www.youtube.com/playlist?list=PL9oC_cq7OYbx58s-eCBwP-5NxKnG8SHIr">dilihat di Youtube</a> dan source codenya bisa <a href="https://github.com/endymuhardin/pemrograman-2-2013/tree/master/sample-code/aplikasi-payroll">dilihat di Github</a>.</p>
</blockquote>
<!--more-->
<h1 id="deskripsi-aplikasi">Deskripsi Aplikasi</h1>
<p>Aplikasi ini akan memproses perhitungan take home pay pegawai tiap bulannya. Tidak digunakan antarmuka pengguna (user interface) karena aplikasi bersifat <code class="language-plaintext highlighter-rouge">batch</code>, yaitu berjalan otomatis pada waktu yang ditentukan tanpa membutuhkan intervensi manusia. Input aplikasi berupa file text, dan outputnya juga berupa file text.</p>
<p><a href="http://lh5.googleusercontent.com/-zREpwzUHZVs/Uo4B8sUpjdI/AAAAAAAACT4/3HgNUTbbu1s/w909-h314-no/tugas-kuliah.jpg"><img src="http://lh5.googleusercontent.com/-zREpwzUHZVs/Uo4B8sUpjdI/AAAAAAAACT4/3HgNUTbbu1s/w909-h314-no/tugas-kuliah.jpg" alt="Foto" /></a></p>
<p>Pada pemakaian sebenarnya, data tarif gaji pegawai bisa diambil dari database. Data absensi diambil dari mesin fingerprint. Hasil perhitungan take home pay akan diupload ke bank untuk proses payroll otomatis.</p>
<h1 id="proses-bisnis">Proses Bisnis</h1>
<h2 id="input-dan-output">Input dan Output</h2>
<p>Aplikasi menerima input berupa file text sebagai berikut :</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">tarif.csv</code> : berisi nomer induk, nama, tarif gaji pokok, tarif denda, tarif lembur, tarif tunjangan transport</li>
<li><code class="language-plaintext highlighter-rouge">absen-MM.csv</code> : berisi nomer induk, tanggal, jam datang, jam pulang</li>
</ul>
<p>Dari dua file input tersebut, aplikasi akan mengeluarkan file text sebagai berikut :</p>
<ul>
<li>takehome-MM.csv : berisi nomer induk, nama, gaji pokok, total denda, total lembur, total tunjangan transport, take home pay</li>
</ul>
<p><code class="language-plaintext highlighter-rouge">MM</code> adalah 2 digit bulan. Untuk bulan November, berarti <code class="language-plaintext highlighter-rouge">MM</code> akan terisi <code class="language-plaintext highlighter-rouge">11</code>.</p>
<h2 id="aturan-bisnis">Aturan Bisnis</h2>
<ol>
<li>Hari kerja normal Senin - Jumat. Sabtu dan Minggu libur.</li>
<li>Jam kerja normal 09:00 - 17:00.</li>
<li>Bila hadir kerja pada hari normal, mendapatkan tunjangan transport harian sesuai tarif.</li>
<li>Bila datang terlambat, dikenakan denda per jam sesuai tarif denda per jam.</li>
<li>Bila pulang lebih cepat, dikenakan denda per jam sesuai tarif denda per jam.</li>
<li>Bila datang lebih cepat, tidak mendapatkan bonus.</li>
<li>Bila pulang lebih lambat dari jam normal, mendapatkan lembur sesuai tarif lembur per jam.</li>
<li>Bila masuk di hari libur, mendapatkan tunjangan transport dan bonus lembur dihitung per jam sesuai tarif.</li>
<li>Diasumsikan tidak ada hari libur di luar Sabtu-Minggu.</li>
<li>Rumus : <code class="language-plaintext highlighter-rouge">take home pay = gaji pokok - total denda + total lembur + total tunjangan transport</code></li>
</ol>
<h2 id="contoh-file">Contoh File</h2>
<h3 id="tarifcsv">tarif.csv</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>no,nama,gaji,denda,lembur,transport
1001,Endy,2000,10,15,30
1002,Adi,3000,15,20,50
1003,Jimmy,3500,15,20,50
1004,Doni,3250,10,10,35
</code></pre></div></div>
<h3 id="absen-11csv">absen-11.csv</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>no,tanggal,datang,pulang
1001,2013-11-01,08:55,17:05
1002,2013-11-01,08:57,17:02
1003,2013-11-01,08:55,17:03
1004,2013-11-01,08:55,17:05
1001,2013-11-02,08:58,17:01
1002,2013-11-02,09:01,18:02
1003,2013-11-02,09:15,18:33
1004,2013-11-02,08:54,19:05
</code></pre></div></div>
<h3 id="takehome-11csv">takehome-11.csv</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>no,nama,gaji,denda,lembur,transport,takehome
1001,Endy,2000,100,150,600,2650
1002,Adi,3000,200,300,1000,4100
1003,Jimmy,3500,150,200,950,4500
1004,Doni,3250,250,300,550,3850
</code></pre></div></div>
Symmetric Encryption dengan Java2013-11-21T12:00:00+07:00https://software.endy.muhardin.com/java/symmetric-encryption-dengan-java<p>Beberapa hari terakhir ini, media massa ribut-ribut mengenai telepon si bos yang disadap orang ostrali. Karena ini blog programming, kita tidak akan membahas tentang implikasi politik dari urusan sadap-menyadap tersebut. Kita juga tidak akan membahas kelakuan ABG script-kiddies yang sudah merasa hebat hanya bermodal donlod script dan menjalankannya (di Windows lagi ;p)</p>
<p>Pada artikel ini, kita akan membahas tentang enkripsi dan dekripsi file. Bagaimana konsepnya, apa pilihan-pilihan yang tersedia, dan mana yang sebaiknya digunakan.</p>
<blockquote>
<p>Warning! Topik tingkat advanced. Minimal harus sudah menguasai <code class="language-plaintext highlighter-rouge">java.io</code>.</p>
</blockquote>
<!--more-->
<p>Enkripsi adalah proses mengacak data sehingga tidak bisa dibaca orang yang tidak berhak. Sebelum lebih jauh, kita bahas dulu beberapa istilah:</p>
<ul>
<li>enkripsi : proses mengacak data sehingga sulit dibaca</li>
<li>dekripsi : proses membalik data yang diacak sehingga bisa dibaca lagi</li>
<li>key : serangkaian angka/huruf yang digunakan untuk proses enkripsi dan dekripsi</li>
<li>plaintext : data yang ingin dienkripsi. Biasanya data rahasia dan penting, kalau tidak buat apa repot2 dienkripsi.</li>
<li>ciphertext : data yang sudah dienkripsi. Cipher text ini secara teori tidak bisa dibaca oleh orang yang tidak punya key.</li>
</ul>
<p>Prosesnya sederhana.</p>
<ul>
<li>Untuk mengenkripsi :</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plaintext + key => ciphertext
</code></pre></div></div>
<ul>
<li>Untuk mendekripsi :</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ciphertext + key => plaintext
</code></pre></div></div>
<h2 id="berbagai-jenis-enkripsi">Berbagai Jenis Enkripsi</h2>
<p>Sebenarnya cuma ada dua, jadi tidak perlu pusing ;)</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Symmetric-key_algorithm">Symmetric Encryption</a> : key untuk enkripsi <strong>sama</strong> dengan key untuk dekripsi</li>
<li><a href="http://en.wikipedia.org/wiki/Public-key_cryptography">Asymmetric Encryption</a> : key untuk enkripsi <strong>berbeda</strong> dengan key untuk dekripsi</li>
</ul>
<p>Pada prakteknya, kedua jenis enkripsi ini dipakai secara bersamaan. Jadi bukan pilih yang mana, tapi pakai dua-duanya.</p>
<blockquote>
<p>Kenapa begitu?</p>
</blockquote>
<p>Dengan asymmetric encryption <a href="http://stackoverflow.com/questions/10007147/getting-a-illegalblocksizeexception-data-must-not-be-longer-than-256-bytes-when">sulit untuk mengenkripsi data yang besar</a> dan juga <a href="http://en.wikipedia.org/wiki/RSA_\(algorithm\)#Attacks_against_plain_RSA">tidak aman</a>. Oleh karena itu, biasanya asymmetric encryption hanya digunakan untuk mengenkripsi <code class="language-plaintext highlighter-rouge">key</code> saja untuk kemudian dikirim ke pihak lain yang berkomunikasi dengan kita. <code class="language-plaintext highlighter-rouge">Key</code> inilah yang nantinya akan digunakan untuk mengenkripsi data sebenarnya menggunakan metode symmetric.</p>
<p>Asymmetric encryption akan kita bahas pada kesempatan lain.</p>
<p>Ada dua metode dalam symmetric encryption, yaitu:</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Stream_cipher">stream cipher</a> : mengenkripsi tiap byte data</li>
<li><a href="http://en.wikipedia.org/wiki/Block_cipher">block cipher</a> : membagi data menjadi beberapa blok, kemudian mengenkripsi masing-masing blok.</li>
</ul>
<p>Kali ini kita hanya membahas block cipher saja.</p>
<h2 id="cara-kerja-block-cipher">Cara Kerja Block Cipher</h2>
<p>Pertama, kita bagi dulu data menjadi blok-blok. Satu blok ukurannya berbeda-beda tergantung algoritma yang digunakan. Misalnya, algoritma AES bloknya berisi 128 bit (atau 16 byte) data. Jadi kalau data kita ada 1 KB, maka akan dibagi menjadi 1024 * 16 = 64 blok.</p>
<p>Masing-masing blok lalu dienkripsi secara terpisah, seperti ilustrasi berikut yang <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">diambil dari Wikipedia</a></p>
<p><a href="https://lh3.googleusercontent.com/-4-C-V41PDlM/Uo2LxRnUjOI/AAAAAAAACSw/JzcKfGUMsAQ/w600-h245-no/Ecb_encryption.png"><img src="https://lh3.googleusercontent.com/-4-C-V41PDlM/Uo2LxRnUjOI/AAAAAAAACSw/JzcKfGUMsAQ/w600-h245-no/Ecb_encryption.png" alt="Foto" /></a></p>
<p>Ada berbagai metode dalam mengenkripsi blok-blok data ini. Gambar di atas menunjukkan metode Electronic Codebook yang paling sederhana. Banyak metode lain dengan berbagai plus dan minusnya. Kita tidak akan bahas di sini, silahkan lihat penjelasannya <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">di Wikipedia</a> atau <a href="http://stackoverflow.com/a/1220869">di Stack Overflow</a>.</p>
<p>Setelah memahami cara kerjanya, kita buat dulu rekapitulasi komponen apa saja yang kita butuhkan dalam proses enkripsi ini:</p>
<ul>
<li>plaintext yang ingin dienkripsi</li>
<li>key. Untuk symmetric encryption, key ini harus dirahasiakan. Sebab siapapun yang memiliki key akan bisa melakukan dekripsi</li>
<li><a href="http://en.wikipedia.org/wiki/Initialization_vector">initialization vector</a> : random input supaya hasil enkripsi terlihat berbeda walaupun plain text dan key sama. Initialization vector tidak harus dirahasiakan. Kita bisa kirim IV bersama dengan ciphertext. Walaupun demikian, IV ini harus unik dan sulit ditebak (random). Mengapa? Silahkan baca diskusinya <a href="http://stackoverflow.com/questions/3008139/why-is-using-a-non-random-iv-with-cbc-mode-a-vulnerability">di Stack Overflow</a></li>
<li>algoritma</li>
<li>block cipher mode : metode untuk menggabungkan blok data yang dienkripsi. Block encryption bekerja dengan cara memotong data seukuran blok yang dia mau. Kemudian masing-masing blok dienkripsi. Hasil akhirnya digabungkan menjadi satu lagi. Metode potong-enkripsi-gabung ini <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">memiliki beberapa implementasi berbeda dengan konsekuensi berbeda pula</a>.</li>
<li>padding : Karena ukuran data yang akan dienkripsi bervariasi, kemungkinan besar blok paling akhir tidak terisi penuh. Padahal block encryption mengharuskan semua bloknya terisi. Untuk itu kita harus <em>menambal</em> data jika tidak pas memenuhi ukuran blok. Metode manapun yang dipilih <a href="http://crypto.stackexchange.com/a/1488">tidak berpengaruh terhadap security</a>. Jadi pilih mana saja yang bisa dipakai.</li>
</ul>
<h2 id="memilih-algoritma">Memilih Algoritma</h2>
<p>Ada beberapa pilihan algoritma untuk melakukan enkripsi, diantaranya:</p>
<ul>
<li>DES</li>
<li>Triple Des / 3DES</li>
<li>AES</li>
<li>Blowfish</li>
<li>dsb</li>
</ul>
<p>Masing-masing memiliki plus-minus, untuk menyingkat pembahasan, kita akan menggunakan AES saja. Perbandingan dan alasan pemilihan AES bisa dibaca <a href="http://www.javamex.com/tutorials/cryptography/ciphers.shtml">di artikel ini</a>.</p>
<p>Selain AES, 3DES juga banyak digunakan di industri perbankan untuk mengamankan transaksi ATM, EDC, dan berbagai bentuk transaksi elektronik lainnya. Algoritma manapun yang digunakan tidak masalah, langkah-langkah dan prinsipnya tetap sama. Untuk mengubah dari algoritma satu ke yang lain cuma perlu mengubah satu baris kode program saja.</p>
<p>Pilihan algoritma yang didukung oleh Java bisa dibaca <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher">di daftar ini</a>.</p>
<h2 id="generate-key">Generate Key</h2>
<p>Sebelum membuat key, kita harus menentukan dulu panjang keynya.</p>
<blockquote>
<p>Berapa panjang key yang ideal?</p>
</blockquote>
<p>Menurut NSA, 256 bit. <a href="https://twitter.com/SteveBellovin/status/311545287313330176">Katanya supaya kebal dari serangan quantum computer</a>. Nah, sudah jelas bahwa saya tidak lebih pintar daripada NSA, yang mengajari orang ostrali bagaimana cara menyadap bos kita. Jadi ya kita terima saja sarannya untuk menggunakan 256 bit. Toh ngetik 128 dan 256 sama-sama efisien dari sisi jumlah tombol yang ditekan ;)</p>
<p>Dengan menggunakan 256 bit, kita akan mendapatkan <a href="http://blog.agilebits.com/2013/03/09/guess-why-were-moving-to-256-bit-aes-keys/">keuntungan tambahan</a> jika nantinya kita menggunakan <a href="http://blog.agilebits.com/2013/01/18/authenticated-encryption-and-how-not-to-get-caught-chasing-a-coyote/">message authentication</a> dan <a href="http://blog.agilebits.com/2011/05/05/defending-against-crackers-peanut-butter-keeps-dogs-friendly-too/">password derivation</a>.</p>
<p>Berdasarkan cara membuatnya, kita bedakan menjadi dua macam key:</p>
<ul>
<li>key yang dibuat secara random</li>
<li>key yang ditentukan user (password)</li>
</ul>
<p>Random key biasanya digunakan tanpa user input. Misalnya aplikasi A ingin mengirim data ke aplikasi B. Supaya aman, data ini ingin dienkripsi. Supaya lebih aman, key yang digunakan untuk enkripsi diganti secara periodik, supaya tidak bisa ditebak oleh penyadap. Nah tentunya akan sangat merepotkan kalau key ini harus diinput user tiap kali dia harus diganti. Untuk keperluan ini, kita gunakan random key. Karena tidak perlu dihafalkan manusia, maka key ini bentuknya bebas saja.</p>
<p>Berbeda halnya dengan key yang ditentukan user. Karena key ini harus dihafalkan user, maka biasanya user mencari rangkaian huruf/angka yang mudah dia hafalkan. Untuk itu, perlu ada tindakan tambahan supaya lebih aman. Tindakan tambahan ini disebut dengan istilah <a href="http://en.wikipedia.org/wiki/Key_stretching">key stretching</a>.</p>
<p>Cara kerja key stretching intinya adalah <a href="http://en.wikipedia.org/wiki/Key_derivation_function">key derivation</a>, yaitu mengkonversi key asli menjadi key turunan. Key turunan inilah yang sebenarnya digunakan untuk melakukan enkripsi, bukan key asli. Proses key derivation dirancang sedemikian rupa untuk menghindari <a href="http://en.wikipedia.org/wiki/Weak_key">kelemahan</a> yang biasa ditemukan di password biasa, misalnya:</p>
<ul>
<li>jumlah karakternya kurang</li>
<li>variasi angka/huruf/karakter kurang banyak</li>
<li>biasanya menggunakan kata-kata yang ada di kamus, sehingga mudah ditebak</li>
<li>urutan karakter tidak random (misalnya abcd4321, cobacoba1234)</li>
</ul>
<p>Melalui proses <em>key stretching</em> password yang biasanya tidak memenuhi syarat kriptografi dikonversi sehingga memenuhi syarat. Algoritma yang biasa digunakan antara lain:</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/PBKDF2">PBKDF2</a></li>
<li><a href="http://en.wikipedia.org/wiki/Bcrypt">Bcrypt</a></li>
<li><a href="http://en.wikipedia.org/wiki/Scrypt">Scrypt</a></li>
</ul>
<p>Kita hanya akan bahas PBKDF2 saja karena:</p>
<ul>
<li>populer</li>
<li>tersedia di Java tanpa perlu donlod paket tambahan</li>
<li>kebutuhan resource tidak terlalu besar, sehingga tersedia di berbagai platform (misalnya smartphone dan tablet)</li>
</ul>
<h3 id="generate-random-key">Generate Random Key</h3>
<p>Berikut kode program untuk membuat key yang random.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">KeyGenerator</span> <span class="n">keygen</span> <span class="o">=</span> <span class="nc">KeyGenerator</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="s">"AES"</span><span class="o">);</span>
<span class="n">keygen</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="mi">256</span><span class="o">);</span>
<span class="nc">SecretKey</span> <span class="n">key</span> <span class="o">=</span> <span class="n">keygen</span><span class="o">.</span><span class="na">generateKey</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Generated Key : "</span> <span class="o">+</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">encodeBase64String</span><span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">getEncoded</span><span class="o">()));</span>
</code></pre></div></div>
<p>Key yang digenerate harus sesuai dengan algoritma enkripsi yang digunakan.</p>
<h3 id="key-stretching">Key Stretching</h3>
<p>Berikut kode program untuk key stretching. Kita akan membuat key turunan dari password yang lemah (hanya ada huruf, tidak ada angka dan karakter aneh).</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">password</span> <span class="o">=</span> <span class="s">"sangat rahasia sekali"</span><span class="o">;</span>
<span class="c1">// generate random salt</span>
<span class="nc">SecureRandom</span> <span class="n">randomizer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SecureRandom</span><span class="o">();</span>
<span class="nc">BigInteger</span> <span class="n">random</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigInteger</span><span class="o">(</span><span class="mi">32</span><span class="o">,</span> <span class="n">randomizer</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">salt</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="mi">64</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">perulangan</span> <span class="o">=</span> <span class="mi">1000</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">algoritma</span> <span class="o">=</span> <span class="s">"PBKDF2WithHmacSHA1"</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">panjangKey</span> <span class="o">=</span> <span class="mi">256</span><span class="o">;</span>
<span class="nc">PBEKeySpec</span> <span class="n">keyspec</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PBEKeySpec</span><span class="o">(</span><span class="n">password</span><span class="o">.</span><span class="na">toCharArray</span><span class="o">(),</span> <span class="n">salt</span><span class="o">.</span><span class="na">getBytes</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">),</span> <span class="n">perulangan</span><span class="o">,</span> <span class="n">panjangKey</span><span class="o">);</span>
<span class="nc">SecretKeyFactory</span> <span class="n">keyFactory</span> <span class="o">=</span> <span class="nc">SecretKeyFactory</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">algoritma</span><span class="o">);</span>
<span class="nc">SecretKey</span> <span class="n">key</span> <span class="o">=</span> <span class="n">keyFactory</span><span class="o">.</span><span class="na">generateSecret</span><span class="o">(</span><span class="n">keyspec</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Password asli : "</span><span class="o">+</span><span class="n">password</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Salt : "</span><span class="o">+</span><span class="n">salt</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Generated Key : "</span> <span class="o">+</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">encodeBase64String</span><span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">getEncoded</span><span class="o">()));</span>
</code></pre></div></div>
<p>Secara fungsi, salt mirip dengan initialization vector. Yaitu agar password yang sama hasil hash-nya berbeda. Salt juga tidak perlu dirahasiakan, tapi perlu disimpan supaya bisa dipakai untuk memeriksa password. Jika kita simpan password di database dengan mekanisme <em>key stretching</em> ini, yang harus kita simpan adalah:</p>
<ul>
<li>salt</li>
<li>jumlah perulangan</li>
<li>panjang key</li>
<li>key yang dihasilkan (ciphertext)</li>
</ul>
<blockquote>
<p>Perhatian! Key stretching ini bekerja satu arah. Artinya kita tidak bisa mendapatkan password plaintext dari ciphertextnya.</p>
</blockquote>
<h2 id="contoh-kasus">Contoh Kasus</h2>
<p>Sebagai contoh, kita ingin mengenkripsi file text. Supaya mudah, kita buat saja file text berisi lirik lagu yang bisa kita dapatkan di internet. Sepanjang prosedur enkripsi-dekripsi, ada beberapa file yang kita akan buat:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">lyric.txt</code> : file plaintext yang ingin kita enkripsi</li>
<li><code class="language-plaintext highlighter-rouge">key.txt</code> : file berisi key untuk enkripsi. Key ini bisa dibuat secara random ataupun dari input user yang sudah melalui proses <em>key stretching</em>.</li>
<li><code class="language-plaintext highlighter-rouge">iv.txt</code> : initialization vector</li>
<li><code class="language-plaintext highlighter-rouge">lyric-enc.txt</code> : file ciphertext hasil enkripsi</li>
<li><code class="language-plaintext highlighter-rouge">lyric-dec.txt</code> : file plaintext hasil dekripsi lyric-enc.txt</li>
</ul>
<p>Berikut langkah-langkah enkripsi:</p>
<ol>
<li>Generate key</li>
<li>Inisialisasi object yang akan melakukan enkripsi (<code class="language-plaintext highlighter-rouge">java.crypto.Cipher</code>) menggunakan key</li>
<li>Generate IV</li>
<li>Lakukan prosedur enkripsi, hasilnya tulis ke <code class="language-plaintext highlighter-rouge">lyric-enc.txt</code>.</li>
<li>Tulis juga key ke file <code class="language-plaintext highlighter-rouge">key.txt</code> dan IV ke file <code class="language-plaintext highlighter-rouge">iv.txt</code> untuk digunakan dalam proses dekripsi. Pada kondisi riil, IV bisa kita kirimkan melalui jalur publik, sedangkan key harus kita kirim melalui jalur aman. Biasanya key dienkripsi lagi menggunakan asymmetric encryption agar bisa dikirim dengan aman.</li>
</ol>
<p>Langkah-langkah dekripsi kebalikannya:</p>
<ol>
<li>Baca key dari file <code class="language-plaintext highlighter-rouge">key.txt</code></li>
<li>Baca IV dari file <code class="language-plaintext highlighter-rouge">iv.txt</code></li>
<li>Inisialisasi object yang akan melakukan dekripsi (<code class="language-plaintext highlighter-rouge">java.crypto.Cipher</code>) menggunakan key <strong>dan</strong> IV</li>
<li>Lakukan proses dekripsi</li>
</ol>
<p>Kalau sudah paham langkahnya, mari kita coding. Sebelum masuk ke kriptografi, kita bereskan dulu masalah tampilan.</p>
<h2 id="string-conversion">String Conversion</h2>
<p>Proses enkripsi menghasilkan binary data, dalam Java ini ditampung dalam tipe data <code class="language-plaintext highlighter-rouge">byte[]</code>. Binary data ini tidak bisa kita buka di text editor, sehingga menyulitkan kita untuk debug. Oleh karena itu, kita perlu menggunakan <a href="http://en.wikipedia.org/wiki/Base64">encoding Base 64</a> untuk mengkonversinya menjadi <code class="language-plaintext highlighter-rouge">String</code>. Kalau kita langsung memasukkannya ke dalam constructor String, hasilnya akan kacau karena <a href="http://stackoverflow.com/a/10759422">constructor String bingung ketika menghadapi karakter-karakter aneh</a> (non-printable).</p>
<p>Encoding Base64 kita lakukan dengan <a href="http://commons.apache.org/proper/commons-codec/">library <code class="language-plaintext highlighter-rouge">commons-codec</code> dari Apache</a>.</p>
<h2 id="inisialisasi-cipher-untuk-enkripsi">Inisialisasi Cipher untuk Enkripsi</h2>
<p>Pertama, kita generate dulu key. Untuk enkripsi, object <code class="language-plaintext highlighter-rouge">cipher</code> harus diinisialisasi dengan key.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">algoritmaKey</span> <span class="o">=</span> <span class="s">"AES"</span><span class="o">;</span>
<span class="c1">// generate random key</span>
<span class="nc">KeyGenerator</span> <span class="n">keygen</span> <span class="o">=</span> <span class="nc">KeyGenerator</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">algoritmaKey</span><span class="o">);</span>
<span class="n">keygen</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="mi">256</span><span class="o">);</span>
<span class="nc">SecretKey</span> <span class="n">key</span> <span class="o">=</span> <span class="n">keygen</span><span class="o">.</span><span class="na">generateKey</span><span class="o">();</span>
<span class="c1">// tulis key ke file untuk keperluan dekripsi</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">keyFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">(),</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">encodeBase64String</span><span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">getEncoded</span><span class="o">()).</span><span class="na">getBytes</span><span class="o">(),</span> <span class="nc">StandardOpenOption</span><span class="o">.</span><span class="na">CREATE</span><span class="o">);</span>
</code></pre></div></div>
<p>Pada kode program di atas, kita lihat bahwa sebelum ditulis ke file, kita encode dulu dengan method <code class="language-plaintext highlighter-rouge">Base64.encodeBase64String</code> supaya filenya bisa dibuka di text editor.</p>
<p>Setelah punya key, kita buat object <code class="language-plaintext highlighter-rouge">cipher</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">algoritmaEnkripsi</span> <span class="o">=</span> <span class="s">"AES/GCM/NoPadding"</span><span class="o">;</span>
<span class="nc">Cipher</span> <span class="n">cipher</span> <span class="o">=</span> <span class="nc">Cipher</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">algoritmaEnkripsi</span><span class="o">);</span>
<span class="n">cipher</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="nc">Cipher</span><span class="o">.</span><span class="na">ENCRYPT_MODE</span><span class="o">,</span> <span class="n">key</span><span class="o">);</span>
</code></pre></div></div>
<p>Dari kode program di atas, kita bisa lihat bahwa:</p>
<ul>
<li>algoritma yang digunakan adalah <code class="language-plaintext highlighter-rouge">AES</code></li>
<li>block cipher mode menggunakan <code class="language-plaintext highlighter-rouge">GCM</code></li>
<li>tidak menggunakan padding / <code class="language-plaintext highlighter-rouge">NoPadding</code></li>
</ul>
<p>Penggunaan GCM lebih direkomendasikan daripada ECB seperti bisa dibaca <a href="https://web.cs.ucdavis.edu/~rogaway/ocb/gcm.pdf">di sini</a> dan <a href="https://crypto.stackexchange.com/questions/20941/why-shouldnt-i-use-ecb-encryption">di sini</a>.</p>
<h2 id="generate-initialization-vector">Generate Initialization Vector</h2>
<p>IV kita generate menggunakan object <code class="language-plaintext highlighter-rouge">cipher</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// generate IV</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">iv</span> <span class="o">=</span> <span class="n">cipher</span><span class="o">.</span><span class="na">getParameters</span><span class="o">().</span><span class="na">getParameterSpec</span><span class="o">(</span><span class="nc">IvParameterSpec</span><span class="o">.</span><span class="na">class</span><span class="o">).</span><span class="na">getIV</span><span class="o">();</span>
</code></pre></div></div>
<p>Jangan lupa ditulis ke file karena nanti akan kita butuhkan di proses dekripsi.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">ivFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">(),</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">encodeBase64String</span><span class="o">(</span><span class="n">iv</span><span class="o">).</span><span class="na">getBytes</span><span class="o">(),</span> <span class="nc">StandardOpenOption</span><span class="o">.</span><span class="na">CREATE</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="proses-enkripsi">Proses Enkripsi</h2>
<p>Kelengkapan sudah siap, mari kita lakukan proses enkripsi</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tujuan hasil enkripsi</span>
<span class="nc">CipherOutputStream</span> <span class="n">writer</span> <span class="o">=</span>
<span class="k">new</span> <span class="nf">CipherOutputStream</span><span class="o">(</span>
<span class="k">new</span> <span class="nf">FileOutputStream</span><span class="o">(</span><span class="n">encryptedFile</span><span class="o">),</span> <span class="n">cipher</span><span class="o">);</span>
<span class="c1">// enkripsi isi file</span>
<span class="nc">FileReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileReader</span><span class="o">(</span><span class="n">plain</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">data</span><span class="o">;</span>
<span class="k">while</span><span class="o">((</span><span class="n">data</span> <span class="o">=</span> <span class="n">reader</span><span class="o">.</span><span class="na">read</span><span class="o">())</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span><span class="n">data</span><span class="o">);</span>
<span class="n">writer</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">data</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">reader</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="n">writer</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
</code></pre></div></div>
<p>Seharusnya, pada tahap ini kita sudah memiliki file berikut:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">lyric.txt</code></li>
<li><code class="language-plaintext highlighter-rouge">key.txt</code></li>
<li><code class="language-plaintext highlighter-rouge">iv.txt</code></li>
<li><code class="language-plaintext highlighter-rouge">lyric-enc.txt</code></li>
</ol>
<p>File <code class="language-plaintext highlighter-rouge">lyric-enc.txt</code> dan <code class="language-plaintext highlighter-rouge">iv.txt</code> kita kirimkan ke penerima. Dia harusnya sudah memiliki file <code class="language-plaintext highlighter-rouge">key.txt</code>.</p>
<p>Proses enkripsi selesai. Kode program lengkapnya bisa dilihat <a href="https://github.com/endymuhardin/belajar-enkripsi/blob/master/src/main/java/com/muhardin/endy/belajar/enkripsi/AESEncryptionDemo.java">di Github</a>.</p>
<p>Mari kita lanjutkan dengan proses dekripsi.</p>
<h2 id="membaca-key-dan-iv-dari-file">Membaca key dan IV dari file</h2>
<p>Berikut kode programnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// load key</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">keys</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">readAllLines</span><span class="o">(</span><span class="n">keyFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">(),</span> <span class="nc">Charset</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">));</span>
<span class="k">if</span><span class="o">(</span><span class="n">keys</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">()){</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"File key invalid"</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="n">keys</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Key : "</span><span class="o">+</span><span class="n">key</span><span class="o">);</span>
<span class="nc">SecretKeySpec</span> <span class="n">keySpec</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SecretKeySpec</span><span class="o">(</span><span class="nc">Base64</span><span class="o">.</span><span class="na">decodeBase64</span><span class="o">(</span><span class="n">key</span><span class="o">),</span> <span class="n">algoritmaKey</span><span class="o">);</span>
<span class="c1">// load IV</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">ivs</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">readAllLines</span><span class="o">(</span><span class="n">ivFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">(),</span> <span class="nc">Charset</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">));</span>
<span class="k">if</span><span class="o">(</span><span class="n">ivs</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">()){</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"File IV invalid"</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">String</span> <span class="n">iv</span> <span class="o">=</span> <span class="n">ivs</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"IV : "</span><span class="o">+</span><span class="n">iv</span><span class="o">);</span>
<span class="nc">IvParameterSpec</span> <span class="n">ivSpec</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">IvParameterSpec</span><span class="o">(</span><span class="nc">Base64</span><span class="o">.</span><span class="na">decodeBase64</span><span class="o">(</span><span class="n">iv</span><span class="o">));</span>
</code></pre></div></div>
<h2 id="inisialisasi-cipher">Inisialisasi Cipher</h2>
<p>Untuk proses dekripsi, inisialisasi <code class="language-plaintext highlighter-rouge">cipher</code> membutuhkan key dan IV.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// inisialisasi AES</span>
<span class="nc">Cipher</span> <span class="n">cipher</span> <span class="o">=</span> <span class="nc">Cipher</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">algoritmaEnkripsi</span><span class="o">);</span>
<span class="n">cipher</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="nc">Cipher</span><span class="o">.</span><span class="na">DECRYPT_MODE</span><span class="o">,</span> <span class="n">keySpec</span><span class="o">,</span> <span class="n">ivSpec</span><span class="o">);</span>
</code></pre></div></div>
<p>Jangan lupa gunakan mode <code class="language-plaintext highlighter-rouge">Cipher.DECRYPT_MODE</code>.</p>
<h2 id="proses-dekripsi">Proses Dekripsi</h2>
<p>Tidak sulit, tinggal membalik saja prosedur enkripsi.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// output hasil dekripsi</span>
<span class="nc">FileWriter</span> <span class="n">output</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileWriter</span><span class="o">(</span><span class="n">decryptedFile</span><span class="o">);</span>
<span class="c1">// dekripsi isi file</span>
<span class="nc">CipherInputStream</span> <span class="n">cis</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CipherInputStream</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="n">encryptedFile</span><span class="o">),</span> <span class="n">cipher</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">data</span><span class="o">;</span>
<span class="k">while</span><span class="o">((</span><span class="n">data</span> <span class="o">=</span> <span class="n">cis</span><span class="o">.</span><span class="na">read</span><span class="o">())</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">){</span>
<span class="n">output</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">data</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">cis</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="n">output</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
</code></pre></div></div>
<h2 id="verifikasi">Verifikasi</h2>
<p>Setelah kita punya hasil dekripsi, tentu kita harus verifikasi apakah sama persis dengan aslinya. Caranya adalah dengan membandingkan <code class="language-plaintext highlighter-rouge">hash</code> file asli dan file hasil dekripsi. Kita bisa gunakan berbagai algoritma seperti <code class="language-plaintext highlighter-rouge">MD5</code> atau <code class="language-plaintext highlighter-rouge">SHA</code>. Berikut contohnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// bandingkan apakah hasil dekripsi sama dengan file asli</span>
<span class="nc">String</span> <span class="n">md5sumAsli</span> <span class="o">=</span> <span class="nc">DigestUtils</span><span class="o">.</span><span class="na">md5Hex</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="n">plainFile</span><span class="o">));</span>
<span class="nc">String</span> <span class="n">md5sumHasilDekripsi</span> <span class="o">=</span> <span class="nc">DigestUtils</span><span class="o">.</span><span class="na">md5Hex</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="n">decryptedFile</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MD5 Sum File Asli : "</span><span class="o">+</span><span class="n">md5sumAsli</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MD5 Sum File Hasil Dekripsi : "</span><span class="o">+</span><span class="n">md5sumHasilDekripsi</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">md5sumAsli</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">md5sumHasilDekripsi</span><span class="o">)</span> <span class="o">?</span> <span class="s">"Cocok"</span> <span class="o">:</span> <span class="s">"Tidak cocok"</span><span class="o">);</span>
</code></pre></div></div>
<p>Proses dekripsi selesai. Kode program lengkapnya bisa dilihat <a href="https://github.com/endymuhardin/belajar-enkripsi/blob/master/src/main/java/com/muhardin/endy/belajar/enkripsi/AESDecryptionDemo.java">di Github</a>.</p>
<h3 id="message-authentication">Message Authentication</h3>
<p>Message authentication cara kerjanya kira-kira sama dengan metode verifikasi di atas. Intinya adalah:</p>
<ol>
<li>Generate hash dari file A</li>
<li>Generate hash dari file B</li>
<li>Bandingkan, jika hash sama berarti file A dan B isinya sama</li>
</ol>
<p>Dalam kaitannya dengan enkripsi, kita menambahkan key pada proses hash ini supaya lebih aman. Tanpa key, orang lain bisa mencoba-coba generate hash dari file yang dia miliki. Kalau berhasil membuat hash yang sama, maka dia bisa menebak atau memalsukan data yang dikirim. Dengan menggunakan key yang hanya diketahui pihak yang berkomunikasi, maka orang lain sulit untuk membuat hash yang sama.</p>
<p>Proses ini nama kerennya adalah <a href="http://en.wikipedia.org/wiki/Hash-based_message_authentication_code">HMAC (hash-based message authentication code)</a>.</p>
<p>Berikut kode program untuk menghasilkan hash dari data yang sudah dienkripsi</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// HMAC untuk memastikan integritas data</span>
<span class="nc">String</span> <span class="n">algoritmaHmac</span> <span class="o">=</span> <span class="s">"HmacSHA512"</span><span class="o">;</span>
<span class="nc">KeyGenerator</span> <span class="n">keygenHmac</span> <span class="o">=</span> <span class="nc">KeyGenerator</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">algoritmaHmac</span><span class="o">);</span>
<span class="n">keygenHmac</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="mi">256</span><span class="o">);</span>
<span class="c1">// key untuk HMAC</span>
<span class="nc">SecretKey</span> <span class="n">keyHmac</span> <span class="o">=</span> <span class="n">keygenHmac</span><span class="o">.</span><span class="na">generateKey</span><span class="o">();</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">hmacKeyFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">(),</span>
<span class="nc">Base64</span><span class="o">.</span><span class="na">encodeBase64String</span><span class="o">(</span><span class="n">keyHmac</span><span class="o">.</span><span class="na">getEncoded</span><span class="o">()).</span><span class="na">getBytes</span><span class="o">(),</span>
<span class="nc">StandardOpenOption</span><span class="o">.</span><span class="na">CREATE</span><span class="o">);</span>
<span class="c1">// buat HMAC</span>
<span class="nc">Mac</span> <span class="n">hmacGenerator</span> <span class="o">=</span> <span class="nc">Mac</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">algoritmaHmac</span><span class="o">);</span>
<span class="n">hmacGenerator</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="n">keyHmac</span><span class="o">);</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">hmac</span> <span class="o">=</span> <span class="n">hmacGenerator</span><span class="o">.</span><span class="na">doFinal</span><span class="o">(</span><span class="nc">Files</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(</span><span class="n">encryptedFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">()));</span>
<span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">hmacFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">(),</span> <span class="n">hmac</span><span class="o">,</span> <span class="nc">StandardOpenOption</span><span class="o">.</span><span class="na">CREATE</span><span class="o">);</span>
</code></pre></div></div>
<p>Perhatikan bahwa kita terlebih dulu menghasilkan key untuk membuat hash. Key ini harus dimiliki juga oleh pihak penerima data.</p>
<p>Sekarang, kita lakukan verifikasi di sisi penerima.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// sebelum dekripsi, cek dulu HMAC apakah datanya otentik</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">hmacKeys</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">readAllLines</span><span class="o">(</span><span class="n">hmacKeyFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">(),</span> <span class="nc">Charset</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">));</span>
<span class="k">if</span><span class="o">(</span><span class="n">hmacKeys</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"No mac key"</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">String</span> <span class="n">hmacKey</span> <span class="o">=</span> <span class="n">hmacKeys</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="nc">SecretKeySpec</span> <span class="n">hmacKeySpec</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SecretKeySpec</span><span class="o">(</span><span class="nc">Base64</span><span class="o">.</span><span class="na">decodeBase64</span><span class="o">(</span><span class="n">hmacKey</span><span class="o">),</span> <span class="n">algoritmaHmac</span><span class="o">);</span>
<span class="nc">Mac</span> <span class="n">hmacGenerator</span> <span class="o">=</span> <span class="nc">Mac</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">algoritmaHmac</span><span class="o">);</span>
<span class="n">hmacGenerator</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="n">hmacKeySpec</span><span class="o">);</span>
<span class="c1">// generate hmac dari data yang diterima</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">hmacDiterima</span> <span class="o">=</span> <span class="n">hmacGenerator</span><span class="o">.</span><span class="na">doFinal</span><span class="o">(</span><span class="nc">Files</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(</span><span class="n">encryptedFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">()));</span>
<span class="c1">// baca hmac yang disertakan bersama data</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">hmacSeharusnya</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(</span><span class="n">hmacFile</span><span class="o">.</span><span class="na">toPath</span><span class="o">());</span>
<span class="kt">boolean</span> <span class="n">cocok</span> <span class="o">=</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">hmacDiterima</span><span class="o">,</span> <span class="n">hmacSeharusnya</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">cocok</span> <span class="o">?</span> <span class="s">"HMAC Cocok"</span> <span class="o">:</span> <span class="s">"HMAC Tidak Cocok"</span><span class="o">);</span>
<span class="k">if</span><span class="o">(!</span><span class="n">cocok</span><span class="o">){</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Data tidak otentik, mungkin sudah dioprek orang ditengah jalan"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Verifikasi HMAC ini kita lakukan <strong>sebelum</strong> melakukan dekripsi. Tidak ada gunanya dan menambah resiko kalau kita melakukan proses dekripsi (di mana kita membuka dan menggunakan key rahasia) terhadap data yang invalid.</p>
<h1 id="kesimpulan">Kesimpulan</h1>
<p>Pengirim dan penerima harus terlebih dulu memiliki:</p>
<ul>
<li>key enkripsi</li>
<li>key HMAC</li>
</ul>
<p>Kedua file ini harus dirahasiakan dari pihak yang tidak berkepentingan. Distribusinya bisa dilakukan dengan enkripsi menggunakan asymmetric encryption.</p>
<p>Pada saat berkomunikasi, pengirim akan memberikan:</p>
<ul>
<li>initialization vector</li>
<li>data yang terenkripsi</li>
<li>HMAC</li>
</ul>
<p>Ketiga data di atas boleh dikirim melalui jalur publik.</p>
<p>Setelah menerima ketiga file tersebut, penerima akan melakukan:</p>
<ol>
<li>Generate HMAC dari data terenkripsi</li>
<li>Bandingkan HMAC hasil generate dengan HMAC yang dikirim bersama data. Kalau tidak cocok, jangan diteruskan.</li>
<li>Kalau cocok, barulah lakukan dekripsi menggunakan key dan IV.</li>
</ol>
<blockquote>
<p>Bagaimana, pusing?</p>
</blockquote>
<p>Ya tinggal pilih, pusing belajar atau kena sadap :D</p>
<p>Kode program selengkapnya (seperti biasa) ada <a href="https://github.com/endymuhardin/belajar-enkripsi">di Github</a></p>
Tips Upgrade Library2013-11-19T10:13:00+07:00https://software.endy.muhardin.com/aplikasi/tips-upgrade-library<p>Dalam membuat aplikasi, kita pasti menggunakan library pihak ketiga. Dan library yang bagus tentunya sering diupgrade oleh pembuatnya. Sisi positifnya, library yang kita pakai akan bertambah fiturnya dan berkurang bugnya. Sisi negatifnya, kita harus menyesuaikan kode program kita supaya tidak error karena perubahan di library.</p>
<p>Pada artikel ini, kita akan bahas tips singkat untuk memudahkan kita mengupgrade library pihak ketiga.</p>
<!--more-->
<p>Sebagai contoh, berikut <a href="http://getbootstrap.com/getting-started/#migration">panduan upgrade library CSS Twitter Bootstrap</a>. Di halaman itu dituliskan hal-hal yang berubah.</p>
<p><a href="http://lh6.googleusercontent.com/-Pf2d8nhVky0/UorYq5A-OGI/AAAAAAAACQE/PGb8mXDC890/panduan-upgrade-bootstrap.png"><img src="http://lh6.googleusercontent.com/-Pf2d8nhVky0/UorYq5A-OGI/AAAAAAAACQE/PGb8mXDC890/panduan-upgrade-bootstrap.png" alt="Foto" /></a></p>
<p>Di situ dijelaskan bahwa kita harus mengganti class CSS</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">.container-fluid</code> menjadi <code class="language-plaintext highlighter-rouge">.container</code></li>
<li><code class="language-plaintext highlighter-rouge">.row-fluid</code> menjadi <code class="language-plaintext highlighter-rouge">.row</code></li>
<li>dan seterusnya</li>
</ul>
<blockquote>
<p>Waduh, source code HTML saya ada ratusan file, bagaimana menemukan file mana saja yang pakai class tersebut ??</p>
</blockquote>
<p>Gampang, bila Anda pakai Linux. Cukup buka command prompt, masuk ke folder source code, lalu ketik perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grep -nir 'container-fluid'
</code></pre></div></div>
<p>Outputnya seperti ini</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>src/main/webapp/main.html:107: <span class="nt"><div</span> <span class="na">class=</span><span class="s">"container-fluid"</span><span class="nt">></span>
target/belajar-restful-web-0.1.2-SNAPSHOT/main.html:107: <span class="nt"><div</span> <span class="na">class=</span><span class="s">"container-fluid"</span><span class="nt">></span>
</code></pre></div></div>
<p>Di situ bisa kita lihat bahwa class CSS itu ada di file <code class="language-plaintext highlighter-rouge">main.html</code> baris 107. File tersebut ada di dua tempat, yaitu folder <code class="language-plaintext highlighter-rouge">src/main/webapp</code> dan <code class="language-plaintext highlighter-rouge">target/belajar-restful-web-0.1.2-SNAPSHOT</code>.</p>
<p><code class="language-plaintext highlighter-rouge">grep</code> adalah perintah standar di Linux. Kita menggunakan tiga opsi yaitu:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">n</code> : menampilkan nomor baris (line number)</li>
<li><code class="language-plaintext highlighter-rouge">i</code> : huruf kecil dan besar tidak berpengaruh (case insensitive)</li>
<li><code class="language-plaintext highlighter-rouge">r</code> : rekursif, cari sampai ke dalam subfolder</li>
</ul>
<p>Di belakangnya kita juga bisa tambahkan lokasi file dan folder untuk mempersempit pencarian.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grep -nir 'icon-' login.html main.html html-pages
</code></pre></div></div>
<p>Perintah di atas hanya akan mencari ke file <code class="language-plaintext highlighter-rouge">login.html</code>, <code class="language-plaintext highlighter-rouge">main.html</code>, dan folder <code class="language-plaintext highlighter-rouge">html-pages</code>. Opsi ini berguna supaya dia tidak ikut mencari di file javascript dan css sehingga membingungkan kita menyortir hasilnya.</p>
<p>Outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>main.html:42: <span class="icon-bar"></span>
pages/system/role.html:23: <i class="icon-plus-sign"></i>Tambah Data
pages/system/role.html:39: <a ng-click="edit(x)"><i class="icon-edit"></i></a>
pages/system/role.html:40: <a ng-click="remove(x)"><i class="icon-remove"></i></a>
pages/system/role.html:73: <i class="icon-plus-sign"></i>Tambah Menu
pages/system/role.html:94: <a ng-click="removeSelectedMenu(m)"><i class="icon-remove"></i></a>
pages/system/role.html:104: <i class="icon-plus-sign"></i>Tambah Permission
pages/system/role.html:125: <a ng-click="removeSelectedPermission(p)"><i class="icon-remove"></i></a>
pages/system/permission.html:22: <i class="icon-plus-sign"></i>Tambah Data
pages/system/permission.html:38: <a ng-click="edit(x)"><i class="icon-edit"></i></a>
pages/system/permission.html:39: <a ng-click="remove(x)"><i class="icon-remove"></i></a>
pages/system/sessions.html:28: <th><a ng-click="refresh()" class="btn"><i class="icon-refresh"></i> refresh</a></th>
pages/system/sessions.html:38: <a ng-click="kick(u)" class="btn"><i class="icon-eject"></i> kick</a>
pages/system/user.html:70: <i class="icon-plus-sign"></i>Tambah Data
pages/system/user.html:94: <a ng-click="edit(x)"><i class="icon-edit"></i></a>
pages/system/user.html:95: <a ng-click="remove(x)"><i class="icon-remove"></i></a>
pages/system/config.html:22: <i class="icon-plus-sign"></i>Tambah Data
pages/system/config.html:40: <a ng-click="edit(c)"><i class="icon-edit"></i></a>
pages/system/config.html:41: <a ng-click="remove(c)"><i class="icon-remove"></i></a>
pages/system/menu.html:61: <i class="icon-plus-sign"></i>Tambah Data
pages/system/menu.html:85: <a ng-click="edit(c)"><i class="icon-edit"></i></a>
pages/system/menu.html:86: <a ng-click="remove(c)"><i class="icon-remove"></i></a>
</code></pre></div></div>
<blockquote>
<p>Wah canggih sekali, tapi saya pakai Windows. Bagaimana dong?</p>
</blockquote>
<p>Gampang, <a href="http://software.endy.muhardin.com/linux/upgrade-ubuntu/">instal saja Ubuntu</a> :D</p>
Octopress Aggregator2013-11-14T10:40:00+07:00https://software.endy.muhardin.com/aplikasi/octopress-aggregator<p>Website Aggregator adalah suatu website yang isinya adalah kumpulan link ke posting di website lain. Beberapa skenario penggunaan website aggregator:</p>
<ul>
<li>website perusahaan, ingin mencantumkan posting blog dari anggota timnya</li>
<li>website komunitas, ingin mencantumkan posting blog dari anggota komunitas tersebut</li>
</ul>
<p>Pada artikel ini kita akan membahas cara pemasangan website aggregator di aplikasi blogging Octopress.</p>
<!--more-->
<h2 id="instalasi-dependensi">Instalasi Dependensi</h2>
<p>Kita akan menggunakan <a href="https://github.com/pote/planet.rb">aplikasi yang namanya planet.rb</a>. Sebelum mengikuti langkah-langkah di websitenya, terlebih dulu kita siapkan Ubuntu supaya <code class="language-plaintext highlighter-rouge">planet.rb</code> bisa diinstal.</p>
<p>Instalasi dependensi dilakukan dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install libcurl3 libcurl3-gnutls libcurl4-openssl-dev
</code></pre></div></div>
<p>Setelah itu, kita install <code class="language-plaintext highlighter-rouge">planet.rb</code> dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install planet
</code></pre></div></div>
<p>Kalau menemui error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: Could not find a valid gem 'planet' (>= 0), here is why:
Unable to download data from https://rubygems.org/ - no such name (https://rubygems.org/latest_specs.4.8.gz)
</code></pre></div></div>
<p>atau ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: While executing gem ... (Gem::RemoteFetcher::UnknownHostError)
no such name (https://rubygems.org/gems/planet-0.5.1.gem)
</code></pre></div></div>
<p>artinya koneksi internet Anda bermasalah. Coba lagi dengan koneksi internet yang lain. Mungkin bisa nebeng dulu di tetangga ;)</p>
<h2 id="konfigurasi">Konfigurasi</h2>
<p>Kita generate dulu konfigurasi <code class="language-plaintext highlighter-rouge">planet.rb</code>. Jalankan perintah ini di dalam folder Octopress kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>planet init
</code></pre></div></div>
<p>Perintah di atas akan membuatkan file konfigurasi <code class="language-plaintext highlighter-rouge">planet.yml</code>. Edit sesuai kebutuhan. Berikut adalah contoh konfigurasi yang saya gunakan</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">planet:
posts_directory: </span><span class="n">source</span><span class="o">/</span><span class="n">_posts</span><span class="o">/</span>
<span class="ss">templates_directory: </span><span class="n">source</span><span class="o">/</span><span class="n">_layouts</span><span class="o">/</span>
<span class="ss">whitelisted_tags: </span><span class="p">[]</span>
<span class="ss">blogs:
</span><span class="o">-</span> <span class="ss">author: </span><span class="s2">"Endy is Programmer"</span>
<span class="ss">feed: </span><span class="s2">"http://software.endy.muhardin.com/atom.xml"</span>
<span class="ss">image: </span><span class="s2">"http://www.gravatar.com/avatar/31694bbf42349c6b6adfe893bb1e19d8.png"</span>
<span class="ss">twitter: </span><span class="s2">"endymuhardin"</span>
<span class="o">-</span> <span class="ss">author: </span><span class="s2">"Endy is Photographer"</span>
<span class="ss">feed: </span><span class="s2">"http://rana.endy.muhardin.com/atom.xml"</span>
<span class="ss">image: </span><span class="s2">"http://www.gravatar.com/avatar/a3c8a6a973fc0c0fa3d89cb69c103ad0.png"</span>
</code></pre></div></div>
<h2 id="eksekusi">Eksekusi</h2>
<p>Selanjutnya, kita jalankan generatornya. <code class="language-plaintext highlighter-rouge">planet.rb</code> akan menjelajahi semua blog yang kita daftarkan dan akan membuatkan posting dalam folder <code class="language-plaintext highlighter-rouge">source/_posts</code>.</p>
<p>Terakhir, kita bisa publish dan deploy seperti biasa.</p>
<h2 id="kekurangan">Kekurangan</h2>
<p>Sayangnya <code class="language-plaintext highlighter-rouge">planet.rb</code> menaruh semua isi posting dari blog asal ke website tujuan. Nah, ini bisa <a href="https://support.google.com/webmasters/answer/66359?hl=en">bikin google cemberut</a>. Idealnya, saya ingin memasang potongan isi artikel saja, dengan disertai link ke artikel aslinya. Ini dikenal dengan <a href="http://daringfireball.net/linked/">istilah linked list</a>.</p>
<p>Octopress sudah memiliki <a href="http://octopress.org/docs/blogging/linklog/">dokumentasi untuk fitur ini</a>, tapi entah kenapa ternyata belum bisa dipakai.</p>
<p>Mungkin harus coba bikin sendiri seperti dijelaskan <a href="http://www.candlerblog.com/2012/01/30/octopress-linked-list/">di sini</a></p>
<h2 id="solusi">Solusi</h2>
<p>Kebetulan semua website yang ingin saya aggregasi adalah website saya sendiri, dan semuanya Octopress. Oleh karena itu, saya modifikasi saja <code class="language-plaintext highlighter-rouge">Atom Feed</code>nya supaya menghasilkan <em>summary</em> artikel kalau ada. Kalau tidak ada <em>summary</em>, barulah tampilkan content penuhnya.</p>
<p>Caranya mudah, edit file <code class="language-plaintext highlighter-rouge">source/atom.xml</code>, yang tadinya seperti ini</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><content</span> <span class="na">type=</span><span class="s">"html"</span><span class="nt">></span><span class="cp"><![CDATA[{{ post.content | expand_urls: site.url | cdata_escape }}]]></span><span class="nt"></content></span>
</code></pre></div></div>
<p>menjadi seperti ini</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{% capture excerpted %}{{ post.content | has_excerpt }}{% endcapture %}
{% if excerpted == 'true' %}
<span class="nt"><summary</span> <span class="na">type=</span><span class="s">"html"</span><span class="nt">></span><span class="cp"><![CDATA[{{ post.content | split: '<!--more-->' | first | expand_urls: site.url | cdata_escape }}]]></span><span class="nt"></summary></span>
{% else %}
<span class="nt"><content</span> <span class="na">type=</span><span class="s">"html"</span><span class="nt">></span><span class="cp"><![CDATA[{{ post.content | expand_urls: site.url | cdata_escape }}]]></span><span class="nt"></content></span>
{% endif %}
</code></pre></div></div>
<p>Setelah itu, generate ulang dan deploy masing-masing website. Hasilnya, pada waktu di-aggregasi hanya akan tampil <em>summary</em> saja. Sehingga yang tampil di website aggregator tidak seluruh isi artikel. Paman Google pun tidak kesal dibuatnya ;)</p>
<p>Selamat mencoba</p>
Problem Solving2013-10-31T11:00:00+07:00https://software.endy.muhardin.com/life/problem-solving<p>Saya aktif berbagai forum diskusi di internet, mostly mengenai pemrograman. Dari apa yang saya amati di forum tersebut, begitu ada pertanyaan, umumnya selalu disambut dengan alternatif solusi yang bermacam-macam. Padahal seharusnya, kita fokus dulu dalam memahami masalahnya. Begitu kita paham masalahnya, solusinya akan mudah ditemukan.</p>
<p>Ada <a href="http://www.snopes.com/business/genius/where.asp">cerita <em>urban legend</em> di internet yang menggambarkan pendekatan ini</a>. Berikut terjemahan bebas dari saya.</p>
<!--more-->
<blockquote>
<p>Budi adalah seorang pemilik pabrik yang sedang pusing tujuh keliling.
Salah satu mesinnya rusak sehingga produksi terhenti.
Padahal minggu depan tanggal gajian dan barang harus segera dikirim agar ada uang masuk.</p>
</blockquote>
<blockquote>
<p>Semua teknisi sudah dikerahkan, tapi tidak bisa memperbaiki mesin tersebut.
Akhirnya dia mendatangkan temannya bernama Agus.</p>
</blockquote>
<blockquote>
<p>Agus mulai beraksi. Tanya kiri-kanan, melihat ke dalam kap mesin, dan berkeliling pabrik seharian.
Di sore hari, dia mengambil spidol dan membuat tanda X di salah satu komponen mesin.
Teknisi mengganti komponen tersebut, dan mesin berjalan kembali dengan normal.</p>
</blockquote>
<blockquote>
<p>Agus kemudian memberikan invoice senilai 100 juta. Budi terkejut, “Masa menulis X saja 100 juta??”
Agus mengambil kembali invoice tersebut dan merevisinya sebagai berikut</p>
</blockquote>
<table>
<thead>
<tr>
<th>Pekerjaan</th>
<th>Biaya</th>
</tr>
</thead>
<tbody>
<tr>
<td>Menulis X</td>
<td>10.000</td>
</tr>
<tr>
<td>Menentukan lokasi X</td>
<td>99.990.000</td>
</tr>
<tr>
<td>Total</td>
<td>100.000.000</td>
</tr>
</tbody>
</table>
<p>Anda mungkin berkomentar,</p>
<blockquote>
<p>Ah itu kan cuma cerita dongeng saja.</p>
</blockquote>
<p>Kenyataannya, saya beberapa kali menemui kasus seperti ini. Contohnya:</p>
<p><a href="/images/uploads/2013/10/problem-solving/floating-point-problem.png"><img src="/images/uploads/2013/10/problem-solving/floating-point-problem.png" alt="Foto" /></a></p>
<p>Kenapa hasilnya tidak sama? Karena memang itu sifat dari bilangan <code class="language-plaintext highlighter-rouge">floating-point</code>.
Begitu kita paham problemnya (yaitu karakteristik bilangan <code class="language-plaintext highlighter-rouge">floating-point</code>), fenomena di atas tidak lagi membingungkan buat kita,
dan solusinya langsung terang benderang.</p>
<p>Contoh lain, di milis ada yang bertanya begini</p>
<blockquote>
<p>Saya ingin menggunakan icon di aplikasi Eclipse dalam aplikasi saya.
Lisensinya <a href="http://en.wikipedia.org/wiki/Eclipse_Public_License">EPL</a>, apakah boleh saya pakai gratis atau harus bayar?</p>
</blockquote>
<p>Diskusi berlanjut dengan orang-orang menjelaskan rincian lisensi EPL apa yang boleh apa yang tidak boleh
berikut referensi ke berbagai website.</p>
<p>Saya melihat masalahnya lebih sederhana</p>
<blockquote>
<p>Ada orang ingin pakai icon bagus di aplikasi yang dia buat.</p>
</blockquote>
<p>Kemudian saya memberikan rekomendasi yang lebih simple</p>
<blockquote>
<p>Coba google dengan keyword <code class="language-plaintext highlighter-rouge">free icon</code>.
Nanti ketemu banyak, salah satunya <a href="http://www.famfamfam.com/lab/icons/">famfamfam</a>.</p>
</blockquote>
<p>Nah, itu sudah jelas gratis, tidak perlu pusing <em>terms & conditions</em> dari lisensi <a href="http://en.wikipedia.org/wiki/Eclipse_Public_License">EPL</a>.</p>
<h2 id="problem-worth-solving">Problem Worth Solving</h2>
<p>Satu hal lagi yang penting, problem solving itu butuh cost. Baik waktu, tenaga, maupun uang. Jadi sebelum kita mulai, dipastikan dulu bahwa cost tersebut ada justifikasinya.</p>
<p>Kalo kita cari solusi di google dan gak nemu, baiknya introspeksi dulu.</p>
<blockquote>
<p>Apakah problem saya sedemikian uniknya sehingga sedunia gak ada yang pernah mengalami?</p>
</blockquote>
<p>Tanyakan juga ke diri sendiri</p>
<blockquote>
<p>Atau jangan2, problem kita sedemikian worthless sehingga gak ada yang mau repot2 bikinin solusi?</p>
</blockquote>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Dengan menjabarkan problemnya dengan baik, solusi yang sederhana bisa didapatkan. Tim saya di <a href="http://www.artivisi.com">ArtiVisi</a> sudah terbiasa dengan pendekatan seperti ini. Setiap kali mereka minta solusi untuk suatu hal teknis yang rumit, saya akan selalu mengarahkan untuk menganalisa ulang requirement. Apakah memang harus demikian solusinya? Bila iya, coba lagi negosiasikan requirement supaya bisa menggunakan solusi yang lebih sederhana. Lebih baik negosiasi requirement seharian daripada harus coding solusi rumit dua minggu (belum lagi testing, debugging, maintenance, dst).</p>
<p>Jangan lupa ditimbang-timbang dulu, apakah problemnya <code class="language-plaintext highlighter-rouge">layak</code> dicarikan solusinya. Supaya waktu, tenaga, uang kita bisa dialokasikan seoptimal mungkin.</p>
Mendapatkan Informasi System dengan Java2013-10-29T09:52:00+07:00https://software.endy.muhardin.com/java/mendapatkan-informasi-system-dengan-java<p>Pada salah satu project yang sedang saya kerjakan, ada kebutuhan untuk mendapatkan informasi mengenai sistem dimana aplikasi diinstal. Informasi yang dibutuhkan antara lain:</p>
<ul>
<li>Sistem Operasi</li>
<li>Kapasitas Memori</li>
<li>Kapasitas Disk</li>
<li>Jumlah Network Interface</li>
<li>MAC Address dari masing-masing Network Interface</li>
</ul>
<p>Dalam artikel ini, kita akan membahas cara mendapatkan berbagai informasi tersebut. Seperti biasa, kode program bisa diambil di <a href="https://github.com/endymuhardin/belajar-java-sysinfo">repository Github saya</a>.</p>
<!--more-->
<h2 id="sistem-operasi">Sistem Operasi</h2>
<p>Informasi tentang Sistem Operasi dapat diperoleh dengan menggunakan class java.lang.System. Dari class ini kita bisa menggunakan method getProperty yang berisi berbagai informasi tentang sistem. Daftar property yang tersedia dapat dilihat di <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#getProperties(\)">halaman dokumentasinya</a>. Property yang berkaitan dengan sistem operasi adalah:</p>
<ul>
<li>os.name</li>
<li>os.version</li>
<li>os.arch</li>
</ul>
<p>Berikut contoh penggunaannya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">os</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"os.name"</span><span class="o">);</span>
<span class="n">os</span> <span class="o">+=</span> <span class="s">":"</span> <span class="o">+</span> <span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"os.version"</span><span class="o">);</span>
<span class="n">os</span> <span class="o">+=</span> <span class="s">":"</span> <span class="o">+</span> <span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"os.arch"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">os</span><span class="o">);</span>
</code></pre></div></div>
<p>Di laptop saya, outputnya seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Linux:3.11.0-12-generic:amd64
</code></pre></div></div>
<h2 id="kapasitas-memori">Kapasitas Memori</h2>
<p>Aplikasi Java berjalan di atas JVM, tidak langsung di atas sistem operasi. Untuk urusan memori, ada dua jenis memori yang kita kenal, yaitu memori yang disediakan untuk JVM dan memori yang tersedia di komputer (RAM). Hal ini sering menimbulkan kebingungan di programmer Java pemula, yaitu</p>
<blockquote>
<p>Komputer saya RAM-nya 8 GB, kenapa aplikasi Java saya cuma bisa pakai 2 GB?</p>
</blockquote>
<p>Biasanya penyebabnya adalah karena memori yang dialokasikan untuk JVM hanya 2 GB.</p>
<h3 id="memori-fisik-ram">Memori Fisik (RAM)</h3>
<p>Untuk mendapatkan informasi tentang memori fisik yang tersedia, kita gunakan class <code class="language-plaintext highlighter-rouge">com.sun.management.OperatingSystemMXBean</code>. Berikut cara mendapatkan object dari class tersebut.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">OperatingSystemMXBean</span> <span class="n">osBean</span>
<span class="o">=</span> <span class="o">(</span><span class="nc">OperatingSystemMXBean</span><span class="o">)</span> <span class="nc">ManagementFactory</span><span class="o">.</span><span class="na">getOperatingSystemMXBean</span><span class="o">();</span>
</code></pre></div></div>
<p>Setelah itu, kita bisa gunakan untuk mendapatkan informasi memori sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Long</span> <span class="n">memory</span> <span class="o">=</span> <span class="n">osBean</span><span class="o">.</span><span class="na">getTotalPhysicalMemorySize</span><span class="o">();</span>
</code></pre></div></div>
<p>Dokumentasi lengkap dari class tersebut bisa dibaca <a href="http://docs.oracle.com/javase/7/docs/jre/api/management/extension/com/sun/management/OperatingSystemMXBean.html">di sini</a>.</p>
<h3 id="memori-jvm">Memori JVM</h3>
<p>Ada dua variabel yang menginformasikan memori JVM, yaitu memori yang dialokasikan, dan memori yang terpakai. Aplikasi kita boleh menggunakan memori maksimal sebanyak yang dialokasikan. Tapi aplikasi belum tentu menggunakan semuanya, hanya sebagian saja yang terpakai. Informasi memori JVM bisa didapatkan dari class <code class="language-plaintext highlighter-rouge">java.lang.Runtime</code>.</p>
<p>Berikut cara untuk mendapatkan memori yang dialokasikan untuk JVM</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Long</span> <span class="n">totalMemory</span> <span class="o">=</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">getRuntime</span><span class="o">().</span><span class="na">totalMemory</span><span class="o">();</span>
</code></pre></div></div>
<p>Sedangkan untuk mendapatkan memori terpakai, kita kurangi <code class="language-plaintext highlighter-rouge">totalMemory</code> dengan <code class="language-plaintext highlighter-rouge">freeMemory</code> (memori yang tidak terpakai)</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Long</span> <span class="n">freeMemory</span> <span class="o">=</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">getRuntime</span><span class="o">().</span><span class="na">freeMemory</span><span class="o">();</span>
<span class="nc">Long</span> <span class="n">terpakai</span> <span class="o">=</span> <span class="n">totalMemory</span> <span class="o">-</span> <span class="n">freeMemory</span><span class="o">;</span>
</code></pre></div></div>
<p>Lebih lengkapnya bisa dilihat langsung di <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#totalMemory(\)">dokumentasi class <code class="language-plaintext highlighter-rouge">Runtime</code></a></p>
<h2 id="kapasitas-storage">Kapasitas Storage</h2>
<p>Informasi tentang storage bisa didapatkan di class <code class="language-plaintext highlighter-rouge">java.io.File</code>. Pertama, kita buat dulu objek <code class="language-plaintext highlighter-rouge">File</code> yang mengarah ke partisi yang ingin kita ketahui informasinya.</p>
<p>Untuk Windows, gunakan <code class="language-plaintext highlighter-rouge">C:</code>, <code class="language-plaintext highlighter-rouge">D:</code>, dan seterusnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span> <span class="n">f</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="s">"C:"</span><span class="o">);</span>
</code></pre></div></div>
<p>Untuk Linux, gunakan <code class="language-plaintext highlighter-rouge">/</code>, <code class="language-plaintext highlighter-rouge">/home</code>, dan sebagainya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span> <span class="n">f</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="s">"/home"</span><span class="o">);</span>
</code></pre></div></div>
<p>Ada beberapa method yang disediakan yaitu:</p>
<ul>
<li>totalSpace() : kapasitas storage</li>
<li>usableSpace() : kapasitas yang bisa digunakan</li>
<li>freeSpace() : ruang kosong</li>
</ul>
<p>Kita tentu bertanya-tanya, apa bedanya <code class="language-plaintext highlighter-rouge">usable</code> dan <code class="language-plaintext highlighter-rouge">free</code>? Free adalah ruang yang tidak terpakai, tapi belum tentu boleh dipakai karena masalah permission, ijin akses, dan sebagainya. Usable adalah ruang yang boleh dipakai. Kadang-kadang, nilai <code class="language-plaintext highlighter-rouge">usable</code> sama dengan nilai <code class="language-plaintext highlighter-rouge">free</code>. Penjelasannya bisa dibaca <a href="http://docs.oracle.com/javase/7/docs/api/java/io/File.html#getUsableSpace()">di sini</a>.</p>
<p>Cara pakainya tidak sulit.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Long</span> <span class="n">totalSpace</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="na">getTotalSpace</span><span class="o">();</span>
<span class="nc">Long</span> <span class="n">usableSpace</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="na">getUsableSpace</span><span class="o">();</span>
<span class="nc">Long</span> <span class="n">freeSpace</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="na">getFreeSpace</span><span class="o">();</span>
</code></pre></div></div>
<h2 id="network-interface">Network Interface</h2>
<p>Informasi tentang network interface bisa didapatkan di <code class="language-plaintext highlighter-rouge">java.net.NetworkInterface</code>. Pertama, kita dapatkan dulu seluruh interface yang ada di komputer. Satu komputer (apalagi server) biasa memiliki lebih dari satu interface. Laptop saja biasanya punya dua, ethernet dan wifi.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Enumeration</span><span class="o"><</span><span class="nc">NetworkInterface</span><span class="o">></span> <span class="n">semuaInterface</span> <span class="o">=</span> <span class="nc">NetworkInterface</span><span class="o">.</span><span class="na">getNetworkInterfaces</span><span class="o">();</span>
</code></pre></div></div>
<p>Setelah itu, kita bisa melakukan loop untuk mendapatkan jumlah interface, nama interface, dan MAC addressnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Integer</span> <span class="n">jumlah</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">while</span><span class="o">(</span><span class="n">semuaInterface</span><span class="o">.</span><span class="na">hasMoreElements</span><span class="o">()){</span>
<span class="nc">NetworkInterface</span> <span class="n">ni</span> <span class="o">=</span> <span class="n">semuaInterface</span><span class="o">.</span><span class="na">nextElement</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Nama Interface : "</span><span class="o">+</span><span class="n">ni</span><span class="o">.</span><span class="na">getDisplayName</span><span class="o">());</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">mac</span> <span class="o">=</span> <span class="n">ni</span><span class="o">.</span><span class="na">getHardwareAddress</span><span class="o">();</span>
<span class="k">if</span><span class="o">(</span><span class="n">mac</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">){</span>
<span class="nc">StringBuilder</span> <span class="n">sb</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuilder</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">mac</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">sb</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%02X%s"</span><span class="o">,</span> <span class="n">mac</span><span class="o">[</span><span class="n">i</span><span class="o">],</span> <span class="o">(</span><span class="n">i</span> <span class="o"><</span> <span class="n">mac</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="mi">1</span><span class="o">)</span> <span class="o">?</span> <span class="s">"-"</span> <span class="o">:</span> <span class="s">""</span><span class="o">));</span>
<span class="o">}</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MAC Address : "</span><span class="o">+</span><span class="n">sb</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
<span class="o">}</span>
<span class="n">jumlah</span><span class="o">++;</span>
<span class="o">}</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Jumlah interface : "</span><span class="o">+</span><span class="n">jumlah</span><span class="o">);</span>
</code></pre></div></div>
<p>Demikianlah cara mendapatkan informasi tentang sistem tempat aplikasi Java kita dijalankan.</p>
Teknik Menggunakan Google2013-10-04T22:59:00+07:00https://software.endy.muhardin.com/aplikasi/teknik-menggunakan-google<p>Pada <a href="http://software.endy.muhardin.com/life/otodidak/">artikel terdahulu</a>, sudah dibahas bahwa kemampuan terpenting yang harus dimiliki profesional IT (khususnya programmer) adalah kemampuan otodidak.</p>
<p>Kali ini, kita akan bahas kemampuan terpenting kedua yang harus dimiliki, yaitu teknik mendayagunakan saudara jauh kita yaitu Paman Google.</p>
<p>Seringkali kita temui sergahan para senior di berbagai forum ketika ada <em>newbie</em> yang bertanya</p>
<blockquote>
<p>Google dulu dong sebelum tanya !!!</p>
</blockquote>
<p>Tapi namanya juga <em>newbie</em>, pastinya gak tau cara googling yang efektif. Kalau dia mahir pakai Google, pasti sudah bukan <em>newbie</em> lagi namanya kan? ;)</p>
<p>Nah, begini caranya menggunakan Google.</p>
<!--more-->
<h2 id="kasus-sederhana">Kasus Sederhana</h2>
<p>Seringkali terjadi, kita mengikuti diskusi di berbagai forum dan kemudian menemui istilah-istilah aneh yang baru kita dengar. Ini lazim terjadi kalau kita mengikuti komunitas yang baru, misalnya programmer ikut komunitas fotografi.</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/php-wtf-2.png"><img src="/images/uploads/2013/10/teknik-menggunakan-google/php-wtf-2.png" alt="PHP??? WTF!!! " /></a></p>
<p>Karena penasaran buru-buru pengen tau, secara refleks kita akan klik kotak komentar, lalu mengetik disana</p>
<blockquote>
<p>PHP apaan sih ??</p>
</blockquote>
<p><strong>STOP !!! Jangan lakukan !!!</strong></p>
<p>Itu hanya akan menunjukkan identitas kita sebagai manusia jadul yang <em>kamseupay</em>. Ada cara lain yang lebih berpendidikan, yaitu buka tab baru, lalu kita ketik disana.</p>
<p>Untuk kasus sederhana seperti ini, jawabannya bisa langsung ditemukan. Berikut contoh screenshot yang saya buat ketika ada orang yang menanyakan</p>
<blockquote>
<p>Apa itu OO Calc ?</p>
</blockquote>
<p>di sebuah <a href="https://www.facebook.com/groups/ForumJavaIndonesia/10151680460748017/?comment_id=10151680514093017">forum di Facebook</a>.</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/tanya-paman-google.png"><img src="/images/uploads/2013/10/teknik-menggunakan-google/tanya-paman-google.png" alt="Apa itu OO Calc? " /></a></p>
<p>Semua jawaban ada di halaman tersebut, tanpa kita klik ke halaman berikutnya semua sudah tersaji di sana.</p>
<h2 id="kasus-kompleks">Kasus Kompleks</h2>
<p>Nah sebagai programmer, tentu masalah yang kita hadapi jauh lebih rumit daripada sekedar <code class="language-plaintext highlighter-rouge">Apa itu OO Calc ?</code>. Kita ambil contoh lain di forum yang sama.</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-00.png"><img src="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-00.png" alt="Bagaimana menghitung arccot di Java? " /></a></p>
<p>TS menanyakan tentang suatu perhitungan matematika yang namanya <code class="language-plaintext highlighter-rouge">ARCCOT</code>. Melihat posting di atasnya, saya mendapatkan petunjuk bahwa beliau sedang membicarakan tentang <em>trigonometri</em>. Waduh, terakhir saya dengar istilah ini, teknologi <code class="language-plaintext highlighter-rouge">pager</code> belum lagi ditemukan orang. Nah, saya yakin banyak pembaca blog ini yang bahkan tidak tahu apa itu <code class="language-plaintext highlighter-rouge">pager</code>. Untuk itu, baiklah saya tunjukkan fotonya. Nih dia barangnya.</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/pager.JPG"><img src="/images/uploads/2013/10/teknik-menggunakan-google/pager.JPG" alt="Pager " /></a>
<a href="/images/uploads/2013/10/teknik-menggunakan-google/pager_in_hand.jpg"><img src="/images/uploads/2013/10/teknik-menggunakan-google/pager_in_hand.jpg" alt="Pager dipegang " /></a></p>
<p>Gambar diambil dari <a href="http://www.pagers.co.uk/shop/">sini</a> dan <a href="http://www.ebay.com/itm/MOTOROLA-ADVISOR-II-VHF-PAGER-143-152-9875-MHz-EMS-FIRE-PAGER-with-ZOOM-/190645429104">sana</a></p>
<p>Masih gak tau juga benda apakah itu? Nah, cukuplah menjadi ilustrasi seberapa jadulnya urusan trigonometri ini buat saya. Pada masa itu, bahkan Bill Gates belum lagi menciptakan Windows.</p>
<p>Lalu, apa yang harus dilakukan? Ya tentu saja buka tab baru, ketikkan <code class="language-plaintext highlighter-rouge">arccot java</code> di address bar. Ini hasilnya</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-01.png"><img src="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-01.png" alt="Google arccot java " /></a></p>
<p>Baris ketiga nampaknya menarik, buka di tab baru. Bisa klik kanan lalu <code class="language-plaintext highlighter-rouge">Open in new tab</code>, atau klik dengan tombol tengah mouse. Klik tengah ini merupakan fitur andalan pada waktu kita googling. Buka link baris ketiga</p>
<p>Scroll ke bawah, sampai kita menemukan hal yang menarik. Saya menemukan dua, ini yang pertama</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-02.png"><img src="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-02.png" alt="Hasil search halaman pertama jawaban pertama " /></a></p>
<p>dan scroll lagi ke bawah, dapat jawaban di bawahnya</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-03.png"><img src="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-03.png" alt="Hasil search halaman pertama jawaban kedua " /></a></p>
<p>Waduh, dia menjelaskan tentang apa itu <code class="language-plaintext highlighter-rouge">arccot</code>, dan cara menghitungnya. Bahkan ada link ke Wikipedia segala. Nah, sebetulnya ini bukan urusan saya, tapi urusan <code class="language-plaintext highlighter-rouge">Irfan Rockability</code> yang bertanya di Facebook. Jadi saya tidak tertarik untuk belajar lagi trigonometri dari Wikipedia. Mari kita buka link di jawaban sebelumnya.</p>
<blockquote>
<p>This is it !! Inilah nasi selada ala chef Farah Quinn !!! It’s so yummy</p>
</blockquote>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/farah-quinn.jpg"><img src="/images/uploads/2013/10/teknik-menggunakan-google/farah-quinn.jpg" alt="This is it !! " /></a></p>
<p>Foto diambil <a href="http://sidomi.com/68276/ala-chef-nya-farah-quinn-kena-tegur-kpi/">dari sini</a>. Kurang jelas mana yang yummy, nasi selada atau orangnya ;)</p>
<p>Lho kok trigonometri yummy begitu? Nah, maksud saya, jawabannya sudah tersaji dengan lengkap di halaman yang kita buka tadi.</p>
<p><a href="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-04.png"><img src="/images/uploads/2013/10/teknik-menggunakan-google/arccot-java-04.png" alt="Jawaban akhir " /></a></p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Sekarang ini jaman mudah. Mulai dari hal sederhana seperti <code class="language-plaintext highlighter-rouge">Apa itu OO Calc</code> sampai <code class="language-plaintext highlighter-rouge">Bagaimana cara menghitung arc cotangent dengan Java</code> sudah tersedia jawabannya di internet. Kadangkala langsung ketemu di halaman pertama. Kali lain harus melalui membuka 3 tab dan melalui 8 langkah. 3 tab itu bukan angka yang besar.</p>
<p>Jadi jangan malas, rajin-rajin Google.</p>
<blockquote>
<p>Ingatlah bahwa skill memilih keyword pencarian di Google akan berdampak signifikan terhadap perkembangan karir Anda. Skill ini hanya bisa didapatkan melalui <a href="http://software.endy.muhardin.com/life/lan-na-zha/">GongFu</a></p>
</blockquote>
Migrasi Database dengan Liquibase2013-10-01T21:22:00+07:00https://software.endy.muhardin.com/java/migrasi-database-dengan-liquibase<p>Salah satu kepuasan kita sebagai programmer adalah pada waktu aplikasi yang kita buat digunakan banyak user dan menghasilkan banyak manfaat bagi mereka. Sebagai konsekuensinya, akan banyak permintaan tambahan dari user untuk menambah fitur ataupun mengubah fitur yang sudah ada supaya makin nyaman digunakan.</p>
<p>Seringkali terjadi, perubahan yang diminta ini akan menyebabkan berubahnya skema database. Ini merupakan hal yang wajar dan umum terjadi, sehingga kita perlu menyiapkan mental untuk menerima kondisi ini sebagai sesuatu yang lumrah dan tidak mengerikan.</p>
<p>Tools yang sering saya gunakan untuk mengurus perubahan (evolusi) skema database adalah <a href="http://www.liquibase.org">Liquibase</a>. Pada <a href="http://software.endy.muhardin.com/java/membuat-changelog-liquibase/">artikel terdahulu</a>, kita sudah membahas cara menggunakan Liquibase untuk mengkonversi skema database menjadi XML supaya bisa dikelola di version control. Kali ini, kita akan menggunakan Liquibase untuk melakukan migrasi dari skema database antar versi aplikasi.</p>
<!--more-->
<p>Studi kasusnya sebagai berikut. Misalnya kita punya aplikasi versi <code class="language-plaintext highlighter-rouge">1.0</code> yang sudah berjalan di production (live) dan digunakan user. Aplikasi versi <code class="language-plaintext highlighter-rouge">1.0</code> ini kita kembangkan selama beberapa minggu –diberikan tambahan fitur, bug diperbaiki, tampilan diperindah, dan berbagai peningkatan lainnya– menjadi versi <code class="language-plaintext highlighter-rouge">2.0</code>. Antar kedua versi ini terjadi perubahan skema database. Kita ingin membuat script SQL untuk mengkonversi (migrasi) skema database versi <code class="language-plaintext highlighter-rouge">1.0</code> menjadi skema versi <code class="language-plaintext highlighter-rouge">2.0</code>. Untuk keperluan itu, kita gunakan Liquibase.</p>
<p>Liquibase memiliki kemampuan untuk membandingkan (diff) dua database, kemudian membuatkan file untuk migrasi dari skema yang satu (versi <code class="language-plaintext highlighter-rouge">1.0</code>) menjadi skema lainnya (versi <code class="language-plaintext highlighter-rouge">2.0</code>).</p>
<h2 id="menyiapkan-kedua-database">Menyiapkan kedua database</h2>
<p>Langkah pertama adalah menyiapkan kedua versi database. Biasanya saya membuat dump dari database production (skema versi <code class="language-plaintext highlighter-rouge">1.0</code>), kemudian di-restore di tempat lain (misalnya di laptop). Kemudian saya juga menyiapkan database development yang menggunakan skema versi <code class="language-plaintext highlighter-rouge">2.0</code>.</p>
<p>Agar lebih jelas, kita namakan saja database yang diambil dari production dengan nama <code class="language-plaintext highlighter-rouge">aplikasi_v_1_0</code> dan database baru dengan nama <code class="language-plaintext highlighter-rouge">aplikasi_v_2_0</code>. Berikut adalah informasi koneksi JDBC untuk kedua database</p>
<h3 id="aplikasi_v_1_0">aplikasi_v_1_0</h3>
<ul>
<li>jdbc.driver = <code class="language-plaintext highlighter-rouge">com.mysql.jdbc.Driver</code></li>
<li>jdbc.url = <code class="language-plaintext highlighter-rouge">jdbc:mysql://localhost/aplikasi_v_1_0</code></li>
<li>jdbc.username = <code class="language-plaintext highlighter-rouge">root</code></li>
<li>jdbc.password = <code class="language-plaintext highlighter-rouge">coba</code></li>
</ul>
<h3 id="aplikasi_v_2_0">aplikasi_v_2_0</h3>
<ul>
<li>jdbc.driver = <code class="language-plaintext highlighter-rouge">com.mysql.jdbc.Driver</code></li>
<li>jdbc.url = <code class="language-plaintext highlighter-rouge">jdbc:mysql://localhost/aplikasi_v_2_0</code></li>
<li>jdbc.username = <code class="language-plaintext highlighter-rouge">root</code></li>
<li>jdbc.password = <code class="language-plaintext highlighter-rouge">coba</code></li>
</ul>
<h2 id="konfigurasi-liquibase">Konfigurasi Liquibase</h2>
<p>Seperti di artikel terdahulu, kita membutuhkan tiga file yang kita letakkan di folder yang sama, yaitu:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">mysql-connector-java-5.1.25.jar</code> : driver database</li>
<li><code class="language-plaintext highlighter-rouge">liquibase-core-2.0.5.jar</code> : aplikasi liquibase</li>
<li><code class="language-plaintext highlighter-rouge">liquibase.properties</code> : konfigurasi liquibase</li>
</ul>
<p>Sesuaikan kedua file jar dengan versi yang terbaru.</p>
<p>File <code class="language-plaintext highlighter-rouge">liquibase.properties</code> berisi konfigurasi yang dibutuhkan oleh liquibase. Berikut isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>driver: com.mysql.jdbc.Driver
classpath: mysql-connector-java-5.1.25.jar
url: jdbc:mysql://localhost/aplikasi_v_2_0
username: root
password: admin
referenceUrl: jdbc:mysql://localhost/aplikasi_v_1_0
referenceUsername: root
referencePassword: admin
changeLogFile: changelog.xml
</code></pre></div></div>
<p>Berikut penjelasan dari isi file:</p>
<ul>
<li>driver: JDBC driver sesuai merek database yang kita gunakan</li>
<li>classpath: lokasi file JDBC driver</li>
<li>url: JDBC url untuk database tujuan (versi <code class="language-plaintext highlighter-rouge">2.0</code>)</li>
<li>username: username untuk database tujuan (versi <code class="language-plaintext highlighter-rouge">2.0</code>)</li>
<li>password: password untuk database tujuan (versi <code class="language-plaintext highlighter-rouge">2.0</code>)</li>
<li>referenceUrl: JDBC url untuk database asal (versi <code class="language-plaintext highlighter-rouge">1.0</code>)</li>
<li>referenceUsername: username untuk database asal (versi <code class="language-plaintext highlighter-rouge">1.0</code>)</li>
<li>referencePassword: password untuk database asal (versi <code class="language-plaintext highlighter-rouge">1.0</code>)</li>
<li>changeLogFile: nama file tujuan untuk menulis hasil perbandingan kedua database</li>
</ul>
<h2 id="menjalankan-liquibase--membuat-file-changelog">Menjalankan Liquibase : Membuat file changelog</h2>
<p>Pertama, kita buat dulu file changelog sesuai format Liquibase. Commandnya adalah sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar liquibase-core-2.0.5.jar diffChangeLog
</code></pre></div></div>
<p>Berikut output dari perintah di atas</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INFO 10/1/13 9:11 PM:liquibase: Reading tables for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_1_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading views for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_1_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading foreign keys for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_1_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading primary keys for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_1_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading columns for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_1_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading unique constraints for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_1_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading indexes for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_1_0 ...
INFO 10/1/13 9:11 PM:liquibase: Sequences not supported for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_1_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading tables for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_2_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading views for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_2_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading foreign keys for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_2_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading primary keys for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_2_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading columns for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_2_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading unique constraints for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_2_0 ...
INFO 10/1/13 9:11 PM:liquibase: Reading indexes for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_2_0 ...
INFO 10/1/13 9:11 PM:liquibase: Sequences not supported for
root@localhost @ jdbc:mysql://localhost/aplikasi_v_2_0 ...
INFO 10/1/13 9:11 PM:liquibase: changelog.xml does not exist, creating
Liquibase 'diffChangeLog' Successful
</code></pre></div></div>
<p>Perintah ini akan menghasilkan file <code class="language-plaintext highlighter-rouge">changelog.xml</code> di folder tempat kita berada. File ini berisi perbedaan antar skema versi 1.0 dengan skema versi 2.0. Isinya seperti ini</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8" standalone="no"?></span>
<span class="nt"><databaseChangeLog</span> <span class="na">xmlns=</span><span class="s">"http://www.liquibase.org/xml/ns/dbchangelog"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="na">xsi:schemaLocation=</span><span class="s">"http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"</span><span class="nt">></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1380636706862-1"</span><span class="nt">></span>
<span class="nt"><addColumn</span> <span class="na">tableName=</span><span class="s">"m_customer"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"allowed_addresses"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></addColumn></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1380636706862-2"</span><span class="nt">></span>
<span class="nt"><addColumn</span> <span class="na">tableName=</span><span class="s">"m_customer"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"deleted"</span> <span class="na">type=</span><span class="s">"BIT"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></addColumn></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1380636706862-3"</span><span class="nt">></span>
<span class="nt"><addColumn</span> <span class="na">tableName=</span><span class="s">"m_plan"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"deleted"</span> <span class="na">type=</span><span class="s">"BIT"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></addColumn></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1380636706862-4"</span><span class="nt">></span>
<span class="nt"><addColumn</span> <span class="na">tableName=</span><span class="s">"m_provider"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"deleted"</span> <span class="na">type=</span><span class="s">"BIT"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></addColumn></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1380636706862-5"</span><span class="nt">></span>
<span class="nt"><addColumn</span> <span class="na">tableName=</span><span class="s">"m_rate"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"deleted"</span> <span class="na">type=</span><span class="s">"BIT"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></addColumn></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1380636706862-6"</span><span class="nt">></span>
<span class="nt"><addColumn</span> <span class="na">tableName=</span><span class="s">"m_routing"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"deleted"</span> <span class="na">type=</span><span class="s">"BIT"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></addColumn></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1380636706862-7"</span><span class="nt">></span>
<span class="nt"><dropNotNullConstraint</span> <span class="na">columnDataType=</span><span class="s">"VARCHAR(255)"</span> <span class="na">columnName=</span><span class="s">"bind_password"</span> <span class="na">tableName=</span><span class="s">"m_provider"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1380636706862-8"</span><span class="nt">></span>
<span class="nt"><dropNotNullConstraint</span> <span class="na">columnDataType=</span><span class="s">"VARCHAR(255)"</span> <span class="na">columnName=</span><span class="s">"bind_username"</span> <span class="na">tableName=</span><span class="s">"m_provider"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"></databaseChangeLog></span>
</code></pre></div></div>
<p>Kita lihat di atas bahwa antara skema versi <code class="language-plaintext highlighter-rouge">1.0</code> dan versi <code class="language-plaintext highlighter-rouge">2.0</code> terjadi perubahan sebagai berikut:</p>
<ul>
<li>penambahan kolom <code class="language-plaintext highlighter-rouge">allowed_address</code> di tabel <code class="language-plaintext highlighter-rouge">m_customer</code></li>
<li>penambahan kolom <code class="language-plaintext highlighter-rouge">deleted</code> di berbagai tabel lainnya</li>
<li>menghilangkan constraint <code class="language-plaintext highlighter-rouge">NOT NULL</code> di kolom <code class="language-plaintext highlighter-rouge">bind_username</code> dan <code class="language-plaintext highlighter-rouge">bind_password</code></li>
</ul>
<p>Kita bisa periksa file ini dan sesuaikan bila ada yang kurang tepat.</p>
<h2 id="menjalankan-liquibase--membuat-script-sql">Menjalankan Liquibase : Membuat script SQL</h2>
<p>File XML saja tentunya tidak bermanfaat banyak buat kita. Untuk bisa dieksekusi di database production, kita memerlukan script SQL. Untungnya Liquibase punya fitur untuk membuat script SQL. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar liquibase-core-2.0.5.jar updateSQL
</code></pre></div></div>
<p>Outputnya adalah sebagai berikut</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- *********************************************************************</span>
<span class="c1">-- Update Database Script</span>
<span class="c1">-- *********************************************************************</span>
<span class="c1">-- Change Log: changelog.xml</span>
<span class="c1">-- Ran at: 10/1/13 9:18 PM</span>
<span class="c1">-- Against: root@localhost@jdbc:mysql://localhost/aplikasi_v_2_0</span>
<span class="c1">-- Liquibase version: 2.0.5</span>
<span class="c1">-- *********************************************************************</span>
<span class="c1">-- Lock Database</span>
<span class="c1">-- Changeset changelog.xml::1380636706862-1::endy (generated)::(Checksum: 3:87288bc453555824cb2e28ddafed9557)</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`m_customer`</span> <span class="k">ADD</span> <span class="nv">`allowed_addresses`</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">`DATABASECHANGELOG`</span> <span class="p">(</span><span class="nv">`AUTHOR`</span><span class="p">,</span> <span class="nv">`COMMENTS`</span><span class="p">,</span> <span class="nv">`DATEEXECUTED`</span><span class="p">,</span> <span class="nv">`DESCRIPTION`</span><span class="p">,</span> <span class="nv">`EXECTYPE`</span><span class="p">,</span> <span class="nv">`FILENAME`</span><span class="p">,</span> <span class="nv">`ID`</span><span class="p">,</span> <span class="nv">`LIQUIBASE`</span><span class="p">,</span> <span class="nv">`MD5SUM`</span><span class="p">,</span> <span class="nv">`ORDEREXECUTED`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'endy (generated)'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="n">NOW</span><span class="p">(),</span> <span class="s1">'Add Column'</span><span class="p">,</span> <span class="s1">'EXECUTED'</span><span class="p">,</span> <span class="s1">'changelog.xml'</span><span class="p">,</span> <span class="s1">'1380636706862-1'</span><span class="p">,</span> <span class="s1">'2.0.5'</span><span class="p">,</span> <span class="s1">'3:87288bc453555824cb2e28ddafed9557'</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
<span class="c1">-- Changeset changelog.xml::1380636706862-2::endy (generated)::(Checksum: 3:27e1f8b069e4471400f3deff5034bca9)</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`m_customer`</span> <span class="k">ADD</span> <span class="nv">`deleted`</span> <span class="nb">BIT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">`DATABASECHANGELOG`</span> <span class="p">(</span><span class="nv">`AUTHOR`</span><span class="p">,</span> <span class="nv">`COMMENTS`</span><span class="p">,</span> <span class="nv">`DATEEXECUTED`</span><span class="p">,</span> <span class="nv">`DESCRIPTION`</span><span class="p">,</span> <span class="nv">`EXECTYPE`</span><span class="p">,</span> <span class="nv">`FILENAME`</span><span class="p">,</span> <span class="nv">`ID`</span><span class="p">,</span> <span class="nv">`LIQUIBASE`</span><span class="p">,</span> <span class="nv">`MD5SUM`</span><span class="p">,</span> <span class="nv">`ORDEREXECUTED`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'endy (generated)'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="n">NOW</span><span class="p">(),</span> <span class="s1">'Add Column'</span><span class="p">,</span> <span class="s1">'EXECUTED'</span><span class="p">,</span> <span class="s1">'changelog.xml'</span><span class="p">,</span> <span class="s1">'1380636706862-2'</span><span class="p">,</span> <span class="s1">'2.0.5'</span><span class="p">,</span> <span class="s1">'3:27e1f8b069e4471400f3deff5034bca9'</span><span class="p">,</span> <span class="mi">6</span><span class="p">);</span>
<span class="c1">-- Changeset changelog.xml::1380636706862-3::endy (generated)::(Checksum: 3:14c13dec957537886d6035b32fd4c90c)</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`m_plan`</span> <span class="k">ADD</span> <span class="nv">`deleted`</span> <span class="nb">BIT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">`DATABASECHANGELOG`</span> <span class="p">(</span><span class="nv">`AUTHOR`</span><span class="p">,</span> <span class="nv">`COMMENTS`</span><span class="p">,</span> <span class="nv">`DATEEXECUTED`</span><span class="p">,</span> <span class="nv">`DESCRIPTION`</span><span class="p">,</span> <span class="nv">`EXECTYPE`</span><span class="p">,</span> <span class="nv">`FILENAME`</span><span class="p">,</span> <span class="nv">`ID`</span><span class="p">,</span> <span class="nv">`LIQUIBASE`</span><span class="p">,</span> <span class="nv">`MD5SUM`</span><span class="p">,</span> <span class="nv">`ORDEREXECUTED`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'endy (generated)'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="n">NOW</span><span class="p">(),</span> <span class="s1">'Add Column'</span><span class="p">,</span> <span class="s1">'EXECUTED'</span><span class="p">,</span> <span class="s1">'changelog.xml'</span><span class="p">,</span> <span class="s1">'1380636706862-3'</span><span class="p">,</span> <span class="s1">'2.0.5'</span><span class="p">,</span> <span class="s1">'3:14c13dec957537886d6035b32fd4c90c'</span><span class="p">,</span> <span class="mi">7</span><span class="p">);</span>
<span class="c1">-- Changeset changelog.xml::1380636706862-4::endy (generated)::(Checksum: 3:b54952b6be5eb80caf649dc75f974f65)</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`m_provider`</span> <span class="k">ADD</span> <span class="nv">`deleted`</span> <span class="nb">BIT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">`DATABASECHANGELOG`</span> <span class="p">(</span><span class="nv">`AUTHOR`</span><span class="p">,</span> <span class="nv">`COMMENTS`</span><span class="p">,</span> <span class="nv">`DATEEXECUTED`</span><span class="p">,</span> <span class="nv">`DESCRIPTION`</span><span class="p">,</span> <span class="nv">`EXECTYPE`</span><span class="p">,</span> <span class="nv">`FILENAME`</span><span class="p">,</span> <span class="nv">`ID`</span><span class="p">,</span> <span class="nv">`LIQUIBASE`</span><span class="p">,</span> <span class="nv">`MD5SUM`</span><span class="p">,</span> <span class="nv">`ORDEREXECUTED`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'endy (generated)'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="n">NOW</span><span class="p">(),</span> <span class="s1">'Add Column'</span><span class="p">,</span> <span class="s1">'EXECUTED'</span><span class="p">,</span> <span class="s1">'changelog.xml'</span><span class="p">,</span> <span class="s1">'1380636706862-4'</span><span class="p">,</span> <span class="s1">'2.0.5'</span><span class="p">,</span> <span class="s1">'3:b54952b6be5eb80caf649dc75f974f65'</span><span class="p">,</span> <span class="mi">8</span><span class="p">);</span>
<span class="c1">-- Changeset changelog.xml::1380636706862-5::endy (generated)::(Checksum: 3:6b840975b06488faa9ec0ed1d3e8d123)</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`m_rate`</span> <span class="k">ADD</span> <span class="nv">`deleted`</span> <span class="nb">BIT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">`DATABASECHANGELOG`</span> <span class="p">(</span><span class="nv">`AUTHOR`</span><span class="p">,</span> <span class="nv">`COMMENTS`</span><span class="p">,</span> <span class="nv">`DATEEXECUTED`</span><span class="p">,</span> <span class="nv">`DESCRIPTION`</span><span class="p">,</span> <span class="nv">`EXECTYPE`</span><span class="p">,</span> <span class="nv">`FILENAME`</span><span class="p">,</span> <span class="nv">`ID`</span><span class="p">,</span> <span class="nv">`LIQUIBASE`</span><span class="p">,</span> <span class="nv">`MD5SUM`</span><span class="p">,</span> <span class="nv">`ORDEREXECUTED`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'endy (generated)'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="n">NOW</span><span class="p">(),</span> <span class="s1">'Add Column'</span><span class="p">,</span> <span class="s1">'EXECUTED'</span><span class="p">,</span> <span class="s1">'changelog.xml'</span><span class="p">,</span> <span class="s1">'1380636706862-5'</span><span class="p">,</span> <span class="s1">'2.0.5'</span><span class="p">,</span> <span class="s1">'3:6b840975b06488faa9ec0ed1d3e8d123'</span><span class="p">,</span> <span class="mi">9</span><span class="p">);</span>
<span class="c1">-- Changeset changelog.xml::1380636706862-6::endy (generated)::(Checksum: 3:909c459661aacc260b40971e4215a481)</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`m_routing`</span> <span class="k">ADD</span> <span class="nv">`deleted`</span> <span class="nb">BIT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">`DATABASECHANGELOG`</span> <span class="p">(</span><span class="nv">`AUTHOR`</span><span class="p">,</span> <span class="nv">`COMMENTS`</span><span class="p">,</span> <span class="nv">`DATEEXECUTED`</span><span class="p">,</span> <span class="nv">`DESCRIPTION`</span><span class="p">,</span> <span class="nv">`EXECTYPE`</span><span class="p">,</span> <span class="nv">`FILENAME`</span><span class="p">,</span> <span class="nv">`ID`</span><span class="p">,</span> <span class="nv">`LIQUIBASE`</span><span class="p">,</span> <span class="nv">`MD5SUM`</span><span class="p">,</span> <span class="nv">`ORDEREXECUTED`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'endy (generated)'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="n">NOW</span><span class="p">(),</span> <span class="s1">'Add Column'</span><span class="p">,</span> <span class="s1">'EXECUTED'</span><span class="p">,</span> <span class="s1">'changelog.xml'</span><span class="p">,</span> <span class="s1">'1380636706862-6'</span><span class="p">,</span> <span class="s1">'2.0.5'</span><span class="p">,</span> <span class="s1">'3:909c459661aacc260b40971e4215a481'</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
<span class="c1">-- Changeset changelog.xml::1380636706862-7::endy (generated)::(Checksum: 3:ac26c24529898f4c1c2133225d3d5ee8)</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`m_provider`</span> <span class="k">MODIFY</span> <span class="nv">`bind_password`</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">`DATABASECHANGELOG`</span> <span class="p">(</span><span class="nv">`AUTHOR`</span><span class="p">,</span> <span class="nv">`COMMENTS`</span><span class="p">,</span> <span class="nv">`DATEEXECUTED`</span><span class="p">,</span> <span class="nv">`DESCRIPTION`</span><span class="p">,</span> <span class="nv">`EXECTYPE`</span><span class="p">,</span> <span class="nv">`FILENAME`</span><span class="p">,</span> <span class="nv">`ID`</span><span class="p">,</span> <span class="nv">`LIQUIBASE`</span><span class="p">,</span> <span class="nv">`MD5SUM`</span><span class="p">,</span> <span class="nv">`ORDEREXECUTED`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'endy (generated)'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="n">NOW</span><span class="p">(),</span> <span class="s1">'Drop Not-Null Constraint'</span><span class="p">,</span> <span class="s1">'EXECUTED'</span><span class="p">,</span> <span class="s1">'changelog.xml'</span><span class="p">,</span> <span class="s1">'1380636706862-7'</span><span class="p">,</span> <span class="s1">'2.0.5'</span><span class="p">,</span> <span class="s1">'3:ac26c24529898f4c1c2133225d3d5ee8'</span><span class="p">,</span> <span class="mi">11</span><span class="p">);</span>
<span class="c1">-- Changeset changelog.xml::1380636706862-8::endy (generated)::(Checksum: 3:968da08768e83eaaa863f0a3cafc204e)</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`m_provider`</span> <span class="k">MODIFY</span> <span class="nv">`bind_username`</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">`DATABASECHANGELOG`</span> <span class="p">(</span><span class="nv">`AUTHOR`</span><span class="p">,</span> <span class="nv">`COMMENTS`</span><span class="p">,</span> <span class="nv">`DATEEXECUTED`</span><span class="p">,</span> <span class="nv">`DESCRIPTION`</span><span class="p">,</span> <span class="nv">`EXECTYPE`</span><span class="p">,</span> <span class="nv">`FILENAME`</span><span class="p">,</span> <span class="nv">`ID`</span><span class="p">,</span> <span class="nv">`LIQUIBASE`</span><span class="p">,</span> <span class="nv">`MD5SUM`</span><span class="p">,</span> <span class="nv">`ORDEREXECUTED`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'endy (generated)'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="n">NOW</span><span class="p">(),</span> <span class="s1">'Drop Not-Null Constraint'</span><span class="p">,</span> <span class="s1">'EXECUTED'</span><span class="p">,</span> <span class="s1">'changelog.xml'</span><span class="p">,</span> <span class="s1">'1380636706862-8'</span><span class="p">,</span> <span class="s1">'2.0.5'</span><span class="p">,</span> <span class="s1">'3:968da08768e83eaaa863f0a3cafc204e'</span><span class="p">,</span> <span class="mi">12</span><span class="p">);</span>
<span class="c1">-- Release Database Lock</span>
</code></pre></div></div>
<p>Output tersebut bisa kita periksa dulu kebenarannya, kemudian kita edit bila perlu, lalu tinggal dijalankan di skema database versi <code class="language-plaintext highlighter-rouge">1.0</code> … voila … dia akan berubah menjadi database dengan skema versi <code class="language-plaintext highlighter-rouge">2.0</code>.</p>
<p>Demikianlah cara mudah untuk melakukan migrasi. Jangan lupa simpan isi file <code class="language-plaintext highlighter-rouge">changelog.xml</code> ke dalam version control supaya segala perubahan terhadap skema database tercatat riwayatnya.</p>
Lan Na Zha2013-08-25T16:38:00+07:00https://software.endy.muhardin.com/life/lan-na-zha<p>Beberapa hari yang lalu, saya membaca artikel menarik tentang profesi IT.
Inti dari artikel tersebut adalah profesi di dunia IT <em>sucks</em> dengan beberapa alasan:</p>
<ol>
<li>Pemrograman termasuk kategori <em>temporary knowledge capital</em>, yaitu pengetahuan yang cepat kadaluarsa</li>
<li>Bahasa pemrograman, teknologi, dan platform berubah dengan sangat cepat</li>
<li>Gajinya kecil dan prestisenya rendah</li>
<li>Jenjang karir tidak jelas</li>
</ol>
<p>Artikel selengkapnya bisa dibaca <a href="http://www.halfsigma.com/2007/03/why_a_career_in.html">di sini</a>.</p>
<p>Untuk masalah nomer #3 dan #4, menurut saya lebih terkait pada masalah ambisi, attitude, dan <a href="http://software.endy.muhardin.com/life/otodidak/">self-improvement/kemampuan otodidak</a>. Di artikel ini, kita akan bahas solusi untuk masalah nomer #1 dan #2.</p>
<p>Seperti di artikel sebelumnya, kali ini kita juga akan mengambil teladan dari seorang tokoh di luar dunia IT. Siapa dia?</p>
<p><a href="/images/uploads/2013/08/lan-na-zha/li-shu-wen.jpg"><img src="/images/uploads/2013/08/lan-na-zha/li-shu-wen.jpg" alt="Li Shu Wen " /></a></p>
<p><em>gambar diambil dari <a href="http://mangafox.me/manga/kenji/v21/c003/3.html">MangaFox</a></em></p>
<p>Dia adalah Li Shu Wen, legenda <a href="http://en.wikipedia.org/wiki/B%C4%81j%C3%ADqu%C3%A1n">kungfu Delapan Mata Angin</a> yang diceritakan di <a href="http://mangafox.me/manga/kenji/">komik Kenji</a>.</p>
<!--more-->
<p>Sekilas kita lihat, memang dunia IT berkembang dengan sangat cepat. Bahasa pemrograman datang dan pergi dengan cepatnya. Waktu saya mulai belajar pemrograman dulu (tahun 2001), bahasa paling populer adalah PHP, VB, dan Delphi. Java waktu itu hanyalah mainan orang-orang di kampus. 12 tahun kemudian, kita mendengar banyak bahasa baru, misalnya:</p>
<ul>
<li>Go</li>
<li>Harlan</li>
<li>Kotlin</li>
<li>Rust</li>
<li>Dart</li>
<li>Clojure</li>
<li>dan banyak lagi yang lainnya</li>
</ul>
<p>Dan juga jangan dilupakan bahasa lama yang kembali ngetren, misalnya:</p>
<ul>
<li>Ruby</li>
<li>Python</li>
<li>JavaScript</li>
</ul>
<p>Hal yang sama berlaku juga di urusan platform. Beberapa tahun yang lalu, Blackberry adalah rajanya. Sebelum Blackberry, JavaME dan Symbian adalah platform utama di mobile. Jaman sekarang mereka semua sudah digusur oleh Android dan iPhone. Ini semua terjadi dalam kurun waktu 5 tahun saja. Jadi kalau 5 tahun yang lalu kemahiran kita dalam pemrograman JavaME membuat kita menjadi idola masyarakat, jaman sekarang kita tidak lebih dari seorang Pak Tua yang sudah mendekati usia pensiun ;)</p>
<p>Nah, saya termasuk salah satu Pak Tua tersebut. Tergabung dalam generasi dinosaurus yang masih tau apa itu CORBA, RMI, SOAP, XML, dan singkatan-singkatan lain yang tidak dipahami generasi masa kini yang urusannya adalah KML, JSON, dan kawan-kawannya. Bagaimana cara saya survive bisa menjadi pelajaran buat generasi 2013 yang akan menyusul menjadi Pak Tua dalam 5 tahun ke depan :D</p>
<p>Kuncinya ada di legenda kungfu jaman dulu, <a href="http://kenji.mangawiki.org/?p=1">Li Shu Wen</a> yang diceritakan dalam komik Kenji dengan nama Lie Syo Bun yang bisa dibaca online <a href="http://manga.my.id/Kenji/21/">di sini</a>. Berikut sedikit bagian yang relevan dengan artikel ini, yaitu tentang gerakan Lan Na Zha.</p>
<blockquote>
<p>Untuk menjadi master di aliran kungfu Delapan Mata Angin, Li Shu Wen harus menguasai dua teknik, yaitu pukulan pamungkas dan teknik tombak. Dia menguasai pukulan pamungkas secara otodidak dengan mempertaruhkan nyawa. Baca ceritanya <a href="http://manga.my.id/Kenji/21/10">di bab dua Kenji volume 21</a>. Setelah itu, dia mendapat pengajaran dasar-dasar ilmu tombak di perguruannya. Karena sering berkelahi, maka Li Shu Wen hanya boleh menonton, tidak boleh mencoba. Gurunya mendemokan tiga gerakan dasar ilmu tombak, yaitu Lan (memutar ke luar), Na (memutar ke dalam), dan Zha (menusuk).</p>
</blockquote>
<p><a href="/images/uploads/2013/08/lan-na-zha/lan-na-zha.jpg"><img src="/images/uploads/2013/08/lan-na-zha/lan-na-zha.jpg" alt="Lan Na Zha " /></a></p>
<p><em>gambar diambil dari <a href="http://manga.my.id/Kenji/21/23">MangaMyID</a></em></p>
<blockquote>
<p>Ketika gurunya mengajarkan teknik kombinasi yang lebih advanced, dia menolak belajar karena menganggap sudah cukup dengan tiga gerakan dasar itu saja, sehingga diskorsing oleh gurunya. Tapi bermodalkan tiga gerakan dasar ini saja, dia berhasil mengalahkan semua yang menantangnya, sehingga digelari Dewa Tombak. Baca cerita lengkapnya <a href="http://manga.my.id/Kenji/21/27">di sini</a>.</p>
</blockquote>
<p>Sedemikian universalnya teknik dasar ini, sehingga bisa kita temukan juga di komik lain, yaitu <a href="">Legenda Naga</a>.</p>
<p><a href="/images/uploads/2013/08/lan-na-zha/lan-na-zha-gongfu.jpg"><img src="/images/uploads/2013/08/lan-na-zha/lan-na-zha-gongfu.jpg" alt="Lan Na Zha Legenda Naga " /></a></p>
<p><em>gambar diambil dari <a href="http://mangafox.me/manga/ryuurouden/v07/c024/16.html">MangaFox</a></em></p>
<p>Lalu apa hubungannya dengan profesi di dunia IT? Pelajaran utama dari komik di atas adalah kita harus mengambil inti dari setiap pengetahuan yang kita miliki. Teknik implementasi bisa berubah dengan cepat, tapi konsep dasar tidak berubah. Tidak percaya? Coba kita lihat istilah-istilah yang sedang trendi di jaman sekarang:</p>
<ul>
<li>Functional Programming, Closure, Clojure, Scala, Groovy, Lambda Expression</li>
<li>Object Oriented Programming, UML</li>
<li>ORM</li>
<li>REST</li>
</ul>
<p>Mari kita telusuri apakah benar istilah tersebut membuat skill kita kadaluarsa?</p>
<h3 id="functional-programming">Functional Programming</h3>
<p><a href="http://en.wikipedia.org/wiki/Functional_programming">Menurut Wikipedia</a>, Functional programming sudah dikenal sejak tahun 1950. Bahasa yang terkenal adalah <a href="http://en.wikipedia.org/wiki/Lisp_(programming_language)">LISP</a> dan <a href="http://en.wikipedia.org/wiki/Haskell_(programming_language)">Haskell</a>.</p>
<p>Jadi, apa yang ngetren dengan nama Clojure di masa kini, sebetulnya sudah ada sejak tahun 1950. Kalau saja kita menguasai konsep functional programming, bukan hanya teknik pemrograman Lisp/Haskell, maka konsep yang kita pahami tersebut akan memungkinkan kita menguasai Clojure dalam waktu satu bulan saja. Demikian juga dengan bahasa pemrograman functional lain seperti Scala, Groovy, dan fitur Lambda Expression yang akan hadir di Java 8.</p>
<h3 id="object-oriented-programming">Object Oriented Programming</h3>
<p>Istilah OOP mungkin tidak terdengar terlalu modern. Tapi sebetulnya dia umurnya sama dengan Functional Programming, yaitu dikenal sejak tahun 1950. Bahasa pemrograman berparadigma OOP yang terkenal antara lain adalah Smalltalk.</p>
<p>Untuk mendokumentasikan desain aplikasi OOP, digunakan notasi UML. Notasi ini dirumuskan pada tahun 1990.</p>
<h3 id="orm">ORM</h3>
<p>Object Relational Mapping atau ORM, sering kita kenal dengan istilah JPA, Hibernate, Toplink, dan sebagainya, sebetulnya bukan konsep baru. ORM hanyalah library yang mengkonversi hasil query database relasional menjadi object, dan sebaliknya. Adapun istilah-istilah yang sering kita temui dalam penggunaan ORM seperti:</p>
<ul>
<li><a href="http://stackoverflow.com/questions/97197/what-is-the-n1-selects-issue">n+1 select</a></li>
<li><a href="http://en.wikipedia.org/wiki/Cartesian_product">cartesian product</a></li>
<li><a href="http://en.wikipedia.org/wiki/Foreign_key#CASCADE">cascade operation</a></li>
</ul>
<p>adalah istilah umum yang juga ditemui di SQL. Sedangkan</p>
<ul>
<li>second level cache</li>
<li>lazy loading vs eager fetching</li>
</ul>
<p>merupakan teknik optimasi pengambilan data, yang tidak hanya berlaku untuk database relasional, tapi juga semua pengambilan data dari sumber eksternal seperti:</p>
<ul>
<li>file</li>
<li>web service</li>
<li>network socket</li>
<li>dan sebagainya</li>
</ul>
<h3 id="rest">REST</h3>
<p><a href="http://en.wikipedia.org/wiki/Representational_state_transfer">REST</a>, sebetulnya hanyalah satu metode untuk mengakses service yang disediakan aplikasi lain. Secara konseptual, dia tidak berbeda dengan:</p>
<ul>
<li>SOAP</li>
<li>ISO-8583</li>
<li>IMAP</li>
<li>POP3</li>
<li>SMTP</li>
</ul>
<p>Intinya adalah ada aplikasi A yang ingin menyuruh aplikasi B melakukan sesuatu. Sesuatu di sini bisa menjalankan perintah tertentu, mengambilkan data, mematikan komputer, dan apapun yang bisa kita pikirkan. Begitu kita paham intinya, menyuruh aplikasi lain bisa dilakukan dengan berbagai cara lain, misalnya:</p>
<ul>
<li>mengirim SMS</li>
<li>mengirim email</li>
<li>mengirim data dengan format yang kita tentukan sendiri melalui jaringan (TCP/IP)</li>
<li>mengirim message chat melalui Yahoo Messenger, BBM, Google Hangout, Line, Whatsapp, dan apapun media lain</li>
</ul>
<p>Kita bisa generalisasi lagi menjadi konsep yang lebih mendasar, yaitu <a href="http://software.endy.muhardin.com/java/integrasi-aplikasi/">bagaimana menghubungkan (integrasi) antar beberapa aplikasi agar bisa bekerjasama</a>.</p>
<h2 id="framework-dan-library">Framework dan Library</h2>
<p>Kita juga sering pusing dengan cepatnya perkembangan framework dan library. Di dunia Java, kita bisa ambil contoh Spring Framework dan Java EE.</p>
<p>Walaupun demikian, dengan menggunakan prinsip Lan-Na-Zha, kita tidak perlu pusing. Coba kita bahas evolusinya.</p>
<h3 id="java-ee">Java EE</h3>
<p>Jaman dulu namanya J2EE, beberapa jargon yang kita dengar adalah:</p>
<ul>
<li>Container Managed Transaction (CMT) : programmer tidak perlu <code class="language-plaintext highlighter-rouge">begin-commit/rollback</code> sendiri, diurus appserver</li>
<li>Enterprise Java Beans (EJB) : object yang diinisialisasi, dimaintain, dan dibersihkan oleh appserver</li>
<li>Session Beans : EJB yang bertugas menampung proses bisnis</li>
<li>Entity Beans : EJB yang bertugas menjadi mapping dari Java ke tabel database</li>
<li>Message Driven Beans : EJB yang bertugas menunggu message JMS dan beraksi begitu ada message</li>
</ul>
<p>Teknik implementasinya adalah sebagai berikut:</p>
<ol>
<li>Coding Session Beans, Entity Beans, Message Driven Beans menggunakan Java</li>
<li>Deklarasikan objectnya di file XML</li>
<li>Bila object tersebut saling terkait, tulis juga hubungannya di file XML</li>
<li>Konfigurasi CMT di file XML juga</li>
</ol>
<p>Jaman sekarang namanya JEE. Implementasinya beda, seperti ini:</p>
<ol>
<li>Coding Session Beans, Entity Beans, Message Driven Beans menggunakan Java</li>
<li>Tambahkan annotation <code class="language-plaintext highlighter-rouge">@Stateless</code>, <code class="language-plaintext highlighter-rouge">@Stateful</code>, <code class="language-plaintext highlighter-rouge">@Entity</code>, <code class="language-plaintext highlighter-rouge">@MessageDriven</code> supaya EJB dikelola oleh appserver</li>
<li>Bila object tersebut saling terkait, tulis juga hubungannya menggunakan <code class="language-plaintext highlighter-rouge">@Inject</code></li>
<li>CMT sudah diaktifkan by-default. Konfigurasi tambahan bisa dilakukan menggunakan <code class="language-plaintext highlighter-rouge">@TransactionAttribute</code></li>
</ol>
<p>Konsepnya tetap sama. Tidak percaya? Baiklah saya copy paste ;)</p>
<ul>
<li>Container Managed Transaction (CMT) : programmer tidak perlu <code class="language-plaintext highlighter-rouge">begin-commit/rollback</code> sendiri, diurus appserver</li>
<li>Enterprise Java Beans (EJB) : object yang diinisialisasi, dimaintain, dan dibersihkan oleh appserver</li>
<li>Session Beans : EJB yang bertugas menampung proses bisnis</li>
<li>Entity Beans : EJB yang bertugas menjadi mapping dari Java ke tabel database</li>
<li>Message Driven Beans : EJB yang bertugas menunggu message JMS dan beraksi begitu ada message</li>
</ul>
<p>Seperti kita lihat, kalau kita menghafalkan implementasi (konfigurasi XML), maka skill J2EE kita akan kadaluarsa begitu JavaEE 6 dirilis. Tapi kalau kita pahami benar-benar konsepnya (CMT, EJB), maka pemahaman tersebut masih relevan di JavaEE 5, JavaEE 6, dan kemungkinan besar juga di JavaEE 10 beberapa tahun yang akan datang ;)</p>
<h3 id="spring-framework">Spring Framework</h3>
<p>Bagi aliran non JavaEE, biasanya pakai Spring Framework. Di sinipun tidak jauh berbeda. Dulunya konfigurasi menggunakan XML, sekarang bisa pakai Annotation dan JavaConfig.</p>
<p>Implementasi jaman dulu:</p>
<ul>
<li>inisialisasi (me<code class="language-plaintext highlighter-rouge">new</code>kan object) ada di XML</li>
<li>transaction management dilakukan menggunakan AOP, juga di XML</li>
</ul>
<p>Contohnya bisa dilihat di <a href="http://sourceforge.net/p/playbilling/code/7/tree/trunk/web/WEB-INF/conf/ctx-billing.xml">kode program saya tahun 2006 - 7 tahun yang lalu</a>. Perhatikan bahwa setiap class <code class="language-plaintext highlighter-rouge">DAO</code> dideklarasikan di XML. Demikian juga konfigurasi transaction di bagian <code class="language-plaintext highlighter-rouge">transactionAttributes</code>.</p>
<p>Implementasi jaman sekarang:</p>
<ul>
<li>inisialisasi dilakukan melalui annotation</li>
<li>transaction management dikonfigurasi di annotation</li>
</ul>
<p>Contohnya bisa dilihat di <a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">kode program saya tahun 2013</a>.</p>
<p>Walaupun kode programnya berubah sama sekali, konsepnya tidak berubah dalam kurun waktu 7 tahun, yaitu:</p>
<ul>
<li><a href="http://software.endy.muhardin.com/java/memahami-dependency-injection/">Dependency Injection</a></li>
<li>Aspect Oriented Programming</li>
</ul>
<h2 id="gong-fu">Gong Fu</h2>
<p>Kalau kita baca terus kisah Li Shu Wen sampai <a href="http://mangafox.me/manga/kenji/v21/c005/10.html">bab 5</a>, kita akan mendapat pelajaran penting lainnya, yaitu <code class="language-plaintext highlighter-rouge">gongfu</code>. Gongfu artinya adalah <code class="language-plaintext highlighter-rouge">hasil yang didapat dari latihan</code>. Kita melakukan satu hal berulang-ulang, sehingga hal tersebut menjadi refleks dan bisa kita lakukan tanpa harus berpikir lagi.</p>
<p><a href="/images/uploads/2013/08/lan-na-zha/gongfu.jpg"><img src="/images/uploads/2013/08/lan-na-zha/gongfu.jpg" alt="Gong Fu " /></a></p>
<p><em>gambar diambil dari <a href="http://mangafox.me/manga/kenji/v21/c005/21.html">MangaFox</a></em></p>
<p>Salah satu latihan yang sering saya lakukan adalah membuat aplikasi dengan satu tabel database, satu form input, satu tabel list, lengkap dengan validasinya, menggunakan framework yang biasa digunakan (Spring dan Hibernate), <strong>dari nol</strong>. Dengan latihan tersebut, saya hafal dan tahu persis fungsi dan tujuan setiap baris kode program dan konfigurasi yang ada dalam aplikasi. Bila ada error, saya bisa mengatasinya dengan cepat dengan bermodalkan <code class="language-plaintext highlighter-rouge">gongfu</code> dalam mengintegrasikan berbagai framework.</p>
<blockquote>
<p>Di jaman content seperti saat ini, <em>gongfu</em> yang paling perlu dipupuk adalah <a href="http://software.endy.muhardin.com/aplikasi/teknik-menggunakan-google/">teknik googling</a>. Memilih keyword yang tepat supaya hasil search sesuai keinginan merupakan suatu skill khusus. Skill ini hanya bisa ditingkatkan melalui latihan.</p>
</blockquote>
<p>Khusus programmer, penting juga dilatih cara untuk mendebug error. Membaca pesan error, memisahkan mana yang relevan dan tidak, kemudian mencari tahu baris kode yang menyebabkan error, merupakan skill yang hanya bisa didapat melalui latihan.</p>
<p>Dalam melatih gongfu, ada satu prinsip yang penting untuk kita gunakan, yaitu <a href="http://joshvogelart.blogspot.com/2013/02/slow-is-smooth-and-smooth-is-fast.html">Slow is smooth, smooth is fast</a>. Artinya, kita lakukan langkah demi langkah seakurat mungkin walaupun perlahan. Fokusnya adalah <strong>melakukan secara benar</strong>. Seiring dengan durasi latihan kita, rangkaian gerakan tersebut akan tertanam menjadi refleks di otak kita, sehingga kita bisa melakukannya dengan lebih cepat. Ulangi terus dalam jangka waktu yang lama, maka kita akan bisa melakukan hal tersebut dengan sangat cepat, namun tetap akurat.</p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Belajar dari kisah Li Shu Wen di atas, kita tidak perlu khawatir dengan cepatnya perkembangan teknologi di dunia IT. Bila kita paham konsep dan prinsip dasarnya, kita tidak akan tertinggal. Teknik implementasi, bahasa pemrograman, platform, format data, protokol komunikasi, memang akan terus berubah sepanjang jaman. Tapi prinsip yang mendasarinya akan tetap berpulang pada konsep klasik dan akal sehat.</p>
<p>Jadi, bila kita belajar sesuatu, tidak hanya di dunia IT, jangan cepat puas begitu kita tahu <strong>bagaimana cara melakukannya</strong>. Gali terus sampai kita memahami <strong>tujuan kenapa kita melakukan hal tersebut</strong> dan <strong>berbagai cara lain untuk mencapai tujuan yang sama</strong>. Jangan puas begitu kita bisa <strong>insert data menggunakan ORM</strong>. Tapi gali terus sampai kita paham <strong>mengapa ORM dibuat</strong> dan <strong>bagaimana cara kerja/isi perut ORM</strong>.</p>
<p>Setelah paham, latih terus pemahaman konsep tersebut sampai kita mahir. Jangan berhenti ketika merasa sudah mahir, karena semua skill dan teknik perlu maintenance supaya tidak hilang.</p>
<p>Terakhir, untuk mendapatkan hasil maksimal, kombinasikan artikel ini dengan <a href="http://software.endy.muhardin.com/life/otodidak/">artikel sebelumnya</a>. Pahami prinsip dasar dari skill otodidak, kemudian latihan sampai mahir. Jangan menyerah kalau merasa sulit dalam belajar. Yang namanya belajar pasti sulit, kalau sudah tidak sulit lagi tandanya kita sudah bisa ;)</p>
<p>Juga jangan bosan bila kita tidak cepat memahami/menguasai sesuatu. Menguasai suatu konsep dan teknik dasar seringkali butuh waktu lama. Dalam komik Legenda Naga, Lan Na Zha dilatih selama enam bulan</p>
<p><a href="/images/uploads/2013/08/lan-na-zha/lan-na-zha-gongfu.jpg"><img src="/images/uploads/2013/08/lan-na-zha/lan-na-zha-gongfu.jpg" alt="Lan Na Zha Legenda Naga " /></a></p>
<p><em>gambar diambil dari <a href="http://mangafox.me/manga/ryuurouden/v07/c024/16.html">MangaFox</a></em></p>
<p>Dalam komik Kenji, Li Shu Wen melatih kuda-kuda dasar selama 3 bulan. Tidak seperti aliran lain, selama periode tersebut siswa belum belajar memukul sama sekali.</p>
<p><a href="/images/uploads/2013/08/lan-na-zha/horse-stance-training.jpg"><img src="/images/uploads/2013/08/lan-na-zha/horse-stance-training.jpg" alt="Latihan Kuda-kuda " /></a></p>
<p><em>gambar diambil dari <a href="http://manga.my.id/Kenji/21/66">MangaMyID</a></em></p>
<p>Bagaimana dengan programming?</p>
<blockquote>
<p>Menurut Peter Norvig, <a href="http://norvig.com/21-days.html">belajar pemrograman butuh waktu 10 tahun</a>.</p>
</blockquote>
<p>Jadi, kalau kita belajar sesuatu, setelah dua minggu belum paham dan masih bingung, jangan khawatir. Masih ada waktu 10 tahun kurang
2 minggu lagi untuk belajar ;)</p>
<p>Nah, dengan artikel ini, mudah-mudahan orang bisa memahami mengapa di berbagai forum, milis, sesi kuliah dan pelatihan, saya selalu bersikeras supaya orang <a href="http://software.endy.muhardin.com/life/rtfm/">mencoba dulu sebelum bertanya/minta solusi</a>. Karena dengan mencoba, kita akan meningkatkan gongfu dalam skill problem solving.</p>
<p>Selamat berlatih ;)</p>
Otodidak2013-08-18T20:04:00+07:00https://software.endy.muhardin.com/life/otodidak<p>Sebagai programmer, ada banyak tokoh yang bisa kita teladani, seperti Linus Torvalds, Bill Gates, Richard Stallman, Eric Raymond, Brian Kernighan, Dennis Ritchie, Erich Gamma, dan banyak lainnya. Tetapi di dunia modern seperti sekarang ini, dimana teknologi baru bermunculan lebih cepat dari kemampuan kita untuk bisa menggunakannya (apalagi bisa mempelajarinya), ada satu tokoh yang wajib kita teladani, bukan berasal dari dunia IT. Siapa dia?</p>
<p><a href="/images/uploads/2013/08/otodidak/hatake-kakashi.jpg"><img src="/images/uploads/2013/08/otodidak/hatake-kakashi.jpg" alt="Hatake Kakashi " /></a></p>
<p>Gambar diambil dari <a href="http://www.fanpop.com/clubs/kakashi/images/18899114/title/kakashi-hatake-screencap">Fanpop</a></p>
<p>Dia adalah Hatake Kakashi dari komik Naruto.</p>
<p>Kenapa begitu? Karena skill dan special ability yang dia miliki adalah juga skill dan special ability yang wajib dimiliki semua programmer tanpa kecuali, agar bisa selamat dari bencana pengangguran. Buat yang tidak mengikuti komik Naruto, berikut profil singkat Hatake Kakashi yang berkaitan dengan urusan programming. Sisanya bisa dibaca sendiri <a href="http://naruto.wikia.com/wiki/Kakashi_Hatake">di sini</a>.</p>
<!--more-->
<blockquote>
<p>Hatake Kakashi adalah guru (sensei) dari Uzumaki Naruto dan Uchiha Sasuke, dua tokoh sentral dalam komik Naruto. Dia memiliki julukan <em>Copy Ninja Kakashi</em>, karena kemampuannya untuk mengamati, menganalisa, dan menirukan jurus yang digunakan lawannya hanya dengan sekali lihat saja. Karena kemampuan khusus ini, lawannya menjadi serba salah. Mengeluarkan jurus andalan, resikonya dicuri dan digunakan melawan dia sendiri. Tidak mengeluarkan jurus, ya sama saja menyediakan diri menjadi sansak berjalan.</p>
</blockquote>
<p>Penggunaan skill ini bisa dibaca di <a href="http://komikbaru.com/Naruto/015/7">Naruto episode 15</a> dimana Kakashi bertarung melawan Momochi Zabuza. Kakashi melihat, menganalisa, mempelajari, menirukan, dan kemudian menghajar Zabuza dengan jurusnya sendiri.</p>
<p>Seorang programmer harus mampu menguasai konsep dan teknik baru dengan cepat, seperti halnya Copy Ninja Kakashi.
Ini disebabkan karena pengetahuan teknis dalam pemrograman bisa kadaluarsa dalam waktu yang cepat. Contohnya, di tahun 2003, programmer Java menggunakan framework yang disebut EJB 2 dan Struts 1 untuk membuat aplikasi. Hanya dalam kurun waktu 2 tahun (tahun 2005), kedua teknologi tersebut sudah usang dan diganti dengan jargon baru yang disebut SOAP. Saat ini di tahun 2013, siapapun yang berani menyebut-nyebut EJB 2, Struts 1, ataupun SOAP, terancam diledek sebagai Pak Tua ;) Di tahun 2013 ini, era-nya REST dan JavaScript. Entah apa lagi trend di masa depan.</p>
<p>Saya sendiri belajar banyak hal secara otodidak, diantaranya adalah pemrograman, fotografi, membaca Al Qur’an, bahkan kerajinan membuat gelang ;)</p>
<p><a href="/images/uploads/2013/08/otodidak/camera-strap.jpg"><img src="/images/uploads/2013/08/otodidak/camera-strap.jpg" alt="Camera Strap buatan sendiri " /></a></p>
<p>Saking sering mempelajari segala sesuatu secara otodidak, saya seringkali malas diajari orang lain. Belum tentu cara mengajar dan cara berpikirnya sesuai dengan kebutuhan saya. Orang lain biasanya tidak tahu <em>modal awal</em> kita, sehingga bisa jadi terlalu banyak memberikan materi pemula, atau sebaliknya mengasumsikan saya sudah tau banyak hal dan langsung saja ke topik advanced.</p>
<blockquote>
<p>I am always ready to learn although I do not always like being taught - Winston Churchill</p>
</blockquote>
<p>Dalam hal belajar otodidak, hal yang paling penting kita kuasai adalah:</p>
<ol>
<li>Membuat jalur belajar (roadmap), mana yang duluan, mana yang belakangan</li>
<li>Menentukan metode latihan untuk menguasai konsep atau teknik tertentu</li>
<li>Melakukan kegiatan belajar (membaca / menonton) yang diperlukan sesuai langkah #2 sampai paham</li>
<li>Kalau langkah #3 tidak efektif, revisi metode latihan (kembali ke #2)</li>
<li>Begitu sudah dikuasai cepat-cepat diabadikan dalam bentuk tulisan atau pelatihan supaya tidak cepat lupa</li>
</ol>
<p>Satu hal penting lainnya, kita juga harus tahu sampai di mana batasnya kita bisa belajar otodidak. Belajar membaca Al Qur’an, tidak boleh 100% otodidak. Ada materi tertentu -misalnya <em>makhrajul huruf</em>- yang harus diverifikasi oleh orang yang kompeten. Ini disebabkan karena Al Qur’an diwariskan secara lisan, bukan tulisan. Sehingga teknik membacanya harus dicek secara lisan juga apakah sudah benar atau belum. Selain itu, ilmu membaca Al Qur’an merupakan suatu ilmu yang harus jelas sanadnya. Teknik yang kita gunakan harus bisa ditelusuri asal-usulnya sampai ke Rasulullah SAW. Walaupun demikian, belajar otodidak dapat digunakan supaya kita tidak terlalu merepotkan guru kita.</p>
<p>Langkah #1 dan #2 juga penting apabila kita menjadi mengajari orang lain baik sebagai instruktur, guru, atau dosen. Pengajar yang baik mampu mendefinisikan urutan belajar dan metode latihan yang cocok untuk setiap muridnya. Contohnya, lagi-lagi dari komik Naruto, kita lihat bagaimana Kakashi membuatkan metode latihan untuk kontrol chakra pada Naruto, Sasuke, dan Sakura <a href="http://komikbaru.com/Naruto/018/2">di episode 18</a>. Lihat juga bagaimana Sensei Jiraiya memberikan latihan khusus untuk Naruto <a href="http://komikbaru.com/Naruto/150/16">di episode 150</a>.</p>
<p>Mari kita bahas satu persatu.</p>
<h2 id="membuat-roadmap">Membuat Roadmap</h2>
<p>Langkah pertama yang harus kita lakukan adalah memetakan topik yang akan kita pelajari. Mana yang termasuk pengetahuan dasar (fundamental), mana yang tingkat menengah, mana yang tingkat lanjut. Kita juga harus menentukan urutan penguasaan materi. Sebagai contoh, untuk bisa memahami penggunaan framework Hibernate, kita harus paham dulu konsep SQL dan database relasional. Untuk bisa memahami <a href="http://www.infoq.com/minibooks/JTDS">berbagai strategi transaction dalam Java EE</a>, kita harus paham dulu <a href="http://software.endy.muhardin.com/java/database-transaction/">apa itu database transaction</a>.</p>
<p>Jalur untuk belajar Java sudah saya tuliskan di beberapa artikel:</p>
<ul>
<li><a href="http://software.endy.muhardin.com/java/road-to-java-se/">Java Fundamental</a>.</li>
<li><a href="http://software.endy.muhardin.com/java/road-to-java-ee/">Java EE</a></li>
</ul>
<p>Bila kita ikut kursus, sekolah, atau kuliah, dosen yang kompeten sudah membuatkan jalur belajar ini sehingga peserta tinggal mengikutinya. Tapi bila kita ingin belajar secara otodidak, kita harus memiliki kemampuan untuk membuat jalur ini sendiri.</p>
<blockquote>
<p>Bagaimana cara membuat roadmap belajar?</p>
</blockquote>
<p>Pertama, kita kumpulkan dulu semua materi yang berkaitan dengan topik yang ingin kita pelajari. Sebagai contoh, pada waktu saya belajar fotografi, semua artikel dan video yang berhasil saya kumpulkan saya masukkan ke <a href="https://springpad.com/#!/endy.muhardin/notebooks/belajarfotografi/blocks">Springpad</a>.</p>
<p>Materi ini tidak perlu dibaca/ditonton semua. Pada tahap ini kita hanya mengumpulkan dan mengelompokkan. Biasanya saya hanya membaca/menonton sedikit bagian di awal, tengah, dan akhir saja. Dari hasil skimming ini, saya kelompokkan menjadi materi dasar, menengah, dan lanjut. Untuk materi fotografi, hasil pengelompokannya bisa dilihat <a href="https://springpad.com/#!/endy.muhardin/notebooks/belajarfotografi/blocks/note/learningpath">di sini</a>.</p>
<p>Beberapa pertimbangan dalam membuat roadmap:</p>
<ul>
<li>urutan penguasaan materi. Misalnya sebelum Hibernate, harus paham JDBC dulu. Sebelum Spring MVC harus paham Servlet dan JSP dulu.</li>
<li>tingkat kompleksitas materi. Materi tingkat lanjut biasanya baru bisa dipahami dengan menggabungkan konsep-konsep tingkat dasar. Contohnya untuk bisa memahami <code class="language-plaintext highlighter-rouge">@Transactional</code>, kita harus paham dulu <a href="http://software.endy.muhardin.com/java/database-transaction/">konsep umum database transaction</a>, <a href="http://www.infoq.com/minibooks/JTDS">apa itu distributed transaction dan berbagai strategi penggunaannya</a>, baru kemudian terakhir <a href="http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/transaction.html">bagaimana cara pakainya dalam kode program</a>.</li>
<li>urutan dan pengelompokan ini tidak bersifat final. Sepanjang perjalanan kita belajar, kita akan beberapa kali mengoreksi sudut pandang kita, mengubah prioritas mana yang penting mana yang tidak, dan juga mengubah pemahaman kita terhadap suatu konsep. Pada waktu itu terjadi, jangan sungkan untuk merevisi roadmap. Toh ini kita buat untuk diri sendiri, ya bebas saja kalau mau diubah kapanpun. Ini adalah hal yang normal.</li>
</ul>
<h2 id="menentukan-metode-latihan">Menentukan Metode Latihan</h2>
<p>Selain konsep di atas, kita juga perlu menentukan latihan yang dibutuhkan. Contohnya seperti ini:</p>
<ol>
<li>
<p>Memahami akses database Java menggunakan JDBC</p>
<ul>
<li>membuat koneksi database</li>
<li>memasukkan data ke dalam database (insert)</li>
<li>mengubah data dalam database (update)</li>
<li>mengambil data dari database (select)</li>
<li>menghapus data dalam database (delete)</li>
</ul>
</li>
<li>
<p>Memahami fitur aplikasi web Java</p>
<ul>
<li>menangani request terhadap URL tertentu</li>
<li>menerima variabel melalui HTTP GET</li>
<li>menerima variabel melalui HTTP POST</li>
<li>implementasi login menggunakan HTTP Session dan Filter</li>
<li>membuat inisialisasi aplikasi web menggunakan Listener</li>
<li>upload/download file</li>
</ul>
</li>
</ol>
<p>Kalau kita tidak otodidak, melainkan kursus, sekolah, atau kuliah, pengajar sudah membuatkan daftar latihan ini buat kita.
Kita tinggal ikuti. Masalahnya adalah, metode latihan yang dibuatkan pengajar biasanya disesuaikan untuk kebutuhan orang banyak dari berbagai latar belakang. Kadang cocok dengan kita, kadang tidak cocok. Bila kita otodidak, kita bisa menentukan metode latihan yang sesuai dengan kebutuhan kita.</p>
<p>Bila kita ingin menjadi pengajar, kita juga harus bisa melihat karakteristik murid dan membuatkan metode latihan yang cocok dengan murid tersebut. Dalam komik Naruto, ini dicontohkan Sensei Jiraiya <a href="http://komikbaru.com/Naruto/092/13">di episode 92</a>, pada waktu dia membuatkan latihan khusus sesuai bakat Naruto.</p>
<p>Yang perlu diperhatikan dalam menentukan metode latihan adalah tujuan dari suatu latihan. Setiap kegiatan yang kita lakukan harus memiliki tujuan akhir, yaitu pemahaman konsep tertentu atau penguasaan teknik tertentu.</p>
<p>Latihan yang kita rancang haruslah mencukupi, tapi tidak berlebihan. Jangan sampai dalam latihan untuk menguasai materi Z, kita terpaksa melakukan semua kegiatan dari A sampai Y. Contohnya, bila kita ingin menguasai Hibernate, tidak perlu membuat tampilan desktop/web secara lengkap. Cukup method <code class="language-plaintext highlighter-rouge">public static void main</code> saja dengan data yang dihardcode, dibuat kode programnya sampai bisa masuk ke tabel database.</p>
<p>Hal lain yang juga penting, kita tidak perlu menyusun program latihan sampai materi tingkat lanjut. Kenapa begitu? Karena pada waktu kita baru mulai di tingkat dasar, tentu kita belum paham materi tingkat lanjut, sehingga belum tau metode latihan yang sesuai dengan materi tersebut. Kalaupun dipaksakan, hasilnya tidak akan bagus dan kemungkinan besar pasti akan direvisi lagi di tengah jalan. Jadi daripada dua kali kerja, lebih baik tidak usah dikerjakan dulu sebelum waktunya.</p>
<p>Sama seperti urutan materi, jangan ragu untuk merevisi program latihan di tengah jalan.</p>
<h2 id="belajar-dan-berlatih">Belajar dan Berlatih</h2>
<p>Setelah urutan belajar dan daftar latihan kita susun, kita harus luangkan waktu untuk melakukannya dengan benar sampai ke tujuan. Artinya, setelah membaca artikel tertentu dan melakukan program latihan tertentu, pastikan bahwa kita menguasai konsep dan teknik yang kita tuju. Bila masih belum paham konsepnya, atau belum mahir tekniknya, ulangi atau revisi program latihannya.</p>
<p>Beberapa latihan yang saya lakukan dapat dilihat di repository Github saya, misalnya:</p>
<ul>
<li><a href="https://github.com/endymuhardin/belajar-restful">belajar-restful</a> : latihan untuk memahami arsitektur <a href="http://en.wikipedia.org/wiki/Representational_state_transfer">REST</a></li>
<li><a href="https://github.com/endymuhardin/belajar-sip">belajar-sip</a> : latihan untuk memahami <a href="http://en.wikipedia.org/wiki/Session_Initiation_Protocol">Session Initiation Protocol</a> menggunakan Java</li>
<li><a href="https://github.com/endymuhardin/belajar-akses-database-java">belajar-akses-database-java</a> : latihan untuk memahami berbagai metode akses database</li>
<li><a href="https://github.com/endymuhardin/belajar-spring-android">belajar-spring-android</a> : latihan untuk memahami struktur project aplikasi Android dengan Maven</li>
<li><a href="https://github.com/endymuhardin/belajar-velocity-textoutput">belajar-velocity-textoutput</a> : latihan untuk menguasai penggunaan library <a href="http://velocity.apache.org">Velocity</a> untuk membuat cetakan printer dot matrix</li>
<li><a href="https://github.com/endymuhardin/belajar-ssl">belajar-ssl</a> : latihan untuk memahami konfigurasi SSL</li>
<li><a href="https://github.com/endymuhardin/belajar-auditlog">belajar-auditlog</a> : latihan untuk menguasai library <a href="http://www.jboss.org/envers">Hibernate Envers</a>, library untuk membuat history perubahan database</li>
<li><a href="https://github.com/endymuhardin/belajar-spring-integration">belajar-spring-integration</a> : latihan untuk menguasai library <a href="http://www.springsource.org/spring-integration">Spring Integration</a></li>
<li><a href="https://github.com/endymuhardin/belajar-spring-data-jpa">belajar-spring-data-jpa</a> : latihan untuk menguasai library <a href="http://www.springsource.org/spring-data/jpa">Spring Data JPA</a></li>
<li><a href="https://github.com/endymuhardin/belajar-spring-security">belajar-spring-security</a> : latihan untuk menguasai library <a href="http://www.springsource.org/spring-security">Spring Security</a></li>
<li><a href="https://github.com/endymuhardin/belajar-jmf">belajar-jmf</a> : latihan untuk mencoba fitur <a href="http://www.oracle.com/technetwork/java/javase/tech/index-jsp-140239.html">Java Media Framework</a></li>
<li><a href="https://github.com/endymuhardin/belajar-software-testing">belajar-software-testing</a> : latihan untuk mencoba software testing dengan Maven</li>
<li><a href="https://github.com/endymuhardin/belajar-ws">belajar-ws</a> : latihan untuk mencoba berbagai teknik dan framework <a href="http://en.wikipedia.org/wiki/SOAP">SOAP</a></li>
<li><a href="https://github.com/endymuhardin/belajar-extjs">belajar-extjs</a> : latihan untuk memahami framework javascript <a href="http://www.sencha.com/products/extjs">ExtJS</a></li>
<li><a href="https://github.com/endymuhardin/belajar-css">belajar-css</a> : latihan untuk memahami CSS, berguna untuk yang sama sekali belum tau apa itu CSS</li>
<li><a href="https://github.com/endymuhardin/belajar-spring">belajar-spring</a> : latihan untuk memahami cara penggunaan <a href="http://www.springsource.org/spring-framework">Spring Framework</a></li>
<li><a href="https://github.com/endymuhardin/belajar-servlet">belajar-servlet</a> : latihan untuk memahami fundamental aplikasi web dalam Java</li>
<li><a href="https://github.com/endymuhardin/belajar-jsf">belajar-jsf</a> : latihan untuk memahami penggunaan framework <a href="http://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html">JSF</a></li>
<li><a href="https://github.com/endymuhardin/belajar-jpos">belajar-jpos</a> : latihan untuk memahami penggunaan library <a href="http://jpos.org/">JPos</a> untuk menangani message <a href="http://en.wikipedia.org/wiki/ISO_8583">ISO-8583</a></li>
</ul>
<p>Dari latihan yang sekian banyak, kita bisa melihat bahwa tidak ada yang instan di dunia ini. Untuk menguasai berbagai hal secara otodidak, kita harus mencoba dengan tangan sendiri sampai paham. Pantau terus koleksi repository saya untuk mengamati bagaimana proses belajar saya. Semua repository yang diawali dengan nama <code class="language-plaintext highlighter-rouge">belajar-</code> adalah repository latihan yang saya buat untuk memahami sesuatu.</p>
<h2 id="menulis-dan-mengajar">Menulis dan Mengajar</h2>
<p>If you can’t explain it simply, you don’t understand it well enough. - Albert Einstein</p>
<p>Salah satu kelemahan saya dalam urusan belajar adalah easy come easy go. Setelah puluhan tahun belajar macam-macam hal secara otodidak, maka saya bisa belajar hal baru dalam waktu relatif singkat. Walaupun demikian, kapasitas otak manusia ternyata ada batasnya. Setiap kali saya mempelajari sesuatu yang baru, ada saja pengetahuan lama yang hilang. Contohnya, pada waktu saya belajar Linux dan networking, pengetahuan akuntansi saya memudar. Demikian juga setelah lama berfokus di pemrograman Java, konsep dan perintah aplikasi <code class="language-plaintext highlighter-rouge">iptables</code> (aplikasi firewall di Linux) yang dulu saya hafal, saya lupa semua.</p>
<p>Untuk mengatasi masalah tersebut, ada beberapa cara yang saya lakukan:</p>
<ol>
<li>Sering menggunakan konsep dan teknik yang dimaksud. Sebagai contoh, supaya saya tidak lupa <a href="http://software.endy.muhardin.com/linux/backup-home-folder/">cara menggunakan rsync</a>, saya selalu menggunakannya untuk copy file dari laptop ke komputer lain atau ke harddisk external.</li>
<li>Mencatatnya di blog. Perintah <code class="language-plaintext highlighter-rouge">iptables</code> adalah sesuatu yang jarang saya pakai, karena sebagai orang normal, saya tidak mengkonfigurasi firewall setiap hari. Bahkan belum tentu saya lakukan sebulan sekali. Agar tidak lupa, pemakaian yang sering saya gunakan seperti <a href="http://software.endy.muhardin.com/linux/network-address-translation/">internet connection sharing</a> dan <a href="http://software.endy.muhardin.com/linux/setup-proxmox-dengan-1-ip-public/">port forwarding</a> saya tuliskan di blog. Demikian juga halnya untuk <a href="http://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl/">cara konfigurasi SSL</a>.</li>
<li>Mengajarkannya pada orang lain. Ini adalah salah satu alasan mengapa saya mengajar di Universitas Pancasila dan berbagai pelatihan. Dengan mengajar, saya memperdalam pengetahuan saya tentang materi yang diajarkan, dan juga mencegah supaya tidak lupa.</li>
</ol>
<p>Demikianlah penjelasan tentang otodidak. Skill ini adalah salah satu solusi untuk mengatasi beratnya perjuangan di dunia IT seperti dibahas <a href="http://www.halfsigma.com/2007/03/why_a_career_in.html">di artikel ini</a>. Ada solusi lain yaitu menguasai konsep, bukan teknis implementasi yang bisa dibaca di <a href="http://software.endy.muhardin.com/life/lan-na-zha/">artikel berikutnya</a>.</p>
Menggunakan SSD di Ubuntu2013-07-16T13:40:00+07:00https://software.endy.muhardin.com/linux/menggunakan-ssd-di-ubuntu<p>Beberapa minggu yang lalu, saya mengganti harddisk menjadi SSD (Solid State Disk).
Pengaruhnya sangat signifikan, dari tekan tombol power sampai desktop siap digunakan hanya makan waktu < 10 detik.
Sebelumnya juga tidak terlalu lama, tapi masih berkisar di atas 1 menit. Demikian juga setelah login, menyalakan Google Chrome, hanya sekejap mata browser sudah tampil. Sebelumnya butuh waktu 5-10 detik.</p>
<p>Pemasangan harddisk tidak jauh berbeda dengan proses instalasi harddisk pada umumnya. Tinggal lepas harddisk lama, dan pasang yang baru. Walaupun demikian, ada beberapa tuning yang harus kita lakukan berkaitan dengan karakteristik SSD, yaitu:</p>
<ul>
<li>optimasi file/folder temporary</li>
<li>mengganti scheduler</li>
<li>optimasi proses hapus</li>
</ul>
<!--more-->
<h2 id="opsi-atime">Opsi atime</h2>
<p>Pada waktu awal dikeluarkan, SSD sudah memiliki karakteristik baca-tulis yang sangat cepat, karena tidak ada bagian bergerak (moving parts) di dalamnya. Walaupun demikian, ada kekurangan SSD (dan juga flashdisk USB yang biasa kita gunakan) yaitu umur pakainya yang relatif pendek. Semakin sering ditulis dan dihapus, maka umurnya semakin pendek.</p>
<p>Karena keterbatasan tersebut, maka ada beberapa <a href="http://askubuntu.com/questions/1400/how-do-i-optimize-the-os-for-ssds">tips tuning Linux</a> supaya dia tidak terlalu sering tulis-hapus, yaitu:</p>
<ul>
<li>mematikan fungsi <code class="language-plaintext highlighter-rouge">atime</code>. Fungsi <code class="language-plaintext highlighter-rouge">atime</code> gunanya untuk mengupdate keterangan di file dan folder, menunjukkan kapan dia terakhir diakses.</li>
<li>memindahkan file temporary dan logfile ke memori sehingga tidak menulis ke harddisk.</li>
</ul>
<p>Seiring perkembangan teknologi, keterbatasan umur SSD ini sudah berhasil diatasi. <a href="http://www.storagesearch.com/ssdmyths-endurance.html">Sebuah percobaan di tahun 2007</a> membuktikan bahwa bila kita melakukan tulis-hapus-tulis-hapus dengan kecepatan 80Mbps 24 jam sehari, maka SSD bisa bertahan sampai 51 tahun. Itu percobaan tahun 2007, tentu di tahun 2013 ini SSD sudah semakin baik. Lagipula dalam kurun waktu tersebut kita pasti akan mengganti harddisk. Bukan karena rusak, melainkan karena sudah tidak muat lagi menampung segala macam foto, musik, video yang kita miliki. Seperti halnya di tahun 2000, kita sudah merasa cukup dengan floppy disk 1,4 MB :)</p>
<p>Jadi sebetulnya kita tidak perlu khawatir lagi, khususnya dengan fungsi <code class="language-plaintext highlighter-rouge">atime</code> pada Linux.</p>
<p>Bagi yang masih khawatir, bisa mematikan fungsi <code class="language-plaintext highlighter-rouge">atime</code> ini pada file <code class="language-plaintext highlighter-rouge">/etc/fstab</code> dengan menambahkan opsi <code class="language-plaintext highlighter-rouge">noatime</code>. Berikut isi file <code class="language-plaintext highlighter-rouge">/etc/fstab</code> yang asli</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># / was on /dev/sda1 during installation
UUID=a7c6516b-b6c7-481a-b85f-5297f6ffcbbb / ext4 errors=remount-ro 0 1
# /home was on /dev/sda6 during installation
UUID=0dfcd40a-28bd-4d7f-8e41-4ab85ef776fa /home ext4 defaults 0 2
</code></pre></div></div>
<p>Dan ini yang telah ditambahi opsi <code class="language-plaintext highlighter-rouge">noatime</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># / was on /dev/sda1 during installation
UUID=a7c6516b-b6c7-481a-b85f-5297f6ffcbbb / ext4 noatime,nodiratime,errors=remount-ro 0 1
# /home was on /dev/sda6 during installation
UUID=0dfcd40a-28bd-4d7f-8e41-4ab85ef776fa /home ext4 noatime,nodiratime,defaults 0 2
</code></pre></div></div>
<p>Tapi sekali lagi saya tekankan bahwa ini tidak perlu. Selain umur pakai SSD yang sudah dijelaskan di atas, juga optimasi di sisi Linux sendiri. Secara default, <a href="http://askubuntu.com/questions/2099/is-it-worth-to-tune-ext4-with-noatime">Linux masa kini menggunakan opsi <code class="language-plaintext highlighter-rouge">relatime</code> yang lebih cerdas daripada <code class="language-plaintext highlighter-rouge">atime</code></a>.</p>
<h2 id="temporary-filefolder">Temporary File/Folder</h2>
<p>Optimasi ini bermanfaat tidak cuma untuk harddisk SSD. Intinya, file temporary yang biasanya ada di folder <code class="language-plaintext highlighter-rouge">/tmp</code> dan berbagai log aplikasi yang ada di <code class="language-plaintext highlighter-rouge">/var/log</code> akan kita pindahkan ke RAM. Akses baca-tulis dari RAM jauh lebih cepat daripada disk. Konsekuensinya, file yang dipindah ke memori ini akan hilang pada waktu komputer direstart. Untuk komputer desktop tidak masalah karena kita jarang menggunakan logfile. Tapi ini menjadi masalah buat server, karena logfile berguna untuk kebutuhan analisa dan troubleshooting bila terjadi masalah.</p>
<p>Untuk memindahkan kegiatan baca-tulis folder <code class="language-plaintext highlighter-rouge">/tmp</code> dan <code class="language-plaintext highlighter-rouge">/var/log</code> dari harddisk ke memori, tambahkan baris berikut di <code class="language-plaintext highlighter-rouge">/etc/fstab</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0
tmpfs /var/spool tmpfs defaults,noatime,mode=1777 0 0
tmpfs /var/log tmpfs defaults,noatime,mode=0755 0 0
</code></pre></div></div>
<p>Entri yang ada labelnya <code class="language-plaintext highlighter-rouge">tmpfs</code> artinya akan disimpan di RAM.</p>
<p>Semua file dan folder yang bersifat sementara bisa kita pindahkan ke <code class="language-plaintext highlighter-rouge">tmpfs</code>. Beberapa orang juga menaruh <code class="language-plaintext highlighter-rouge">/var/cache/apt/archives</code> (berisi file <code class="language-plaintext highlighter-rouge">*.deb</code> yang diunduh selama proses instalasi) dalam <code class="language-plaintext highlighter-rouge">tmpfs</code> agar tidak bikin penuh harddisk. Saya tidak melakukannya karena membutuhkan file ini untuk keperluan [upgrade dan replikasi instalasi].</p>
<p>Walaupun demikian, folder <code class="language-plaintext highlighter-rouge">/var/tmp</code> jangan disimpan di <code class="language-plaintext highlighter-rouge">tmpfs</code>, karena dia <a href="http://linuxers.org/article/differences-between-tmp-and-vartmp">berbeda dengan file temporary lainnya</a></p>
<h2 id="io-scheduler">I/O Scheduler</h2>
<p>Linux secara default menganggap kita menggunakan harddisk tradisional. Dia menggunakan sistem antrian (scheduler) <a href="http://en.wikipedia.org/wiki/CFQ">CFQ</a>, mendahulukan request yang lokasinya terdekat dengan posisi jarum harddisk (sehingga lebih cepat). Karena SSD tidak pakai jarum, akses ke semua tempat dalam disk kecepatannya sama, sehingga tidak perlu diatur seperti CFQ. Untuk itu, kita bisa gunakan scheduler <a href="http://en.wikipedia.org/wiki/Deadline_scheduler">deadline</a> yang lebih cepat.</p>
<p>Posisi scheduler yang sedang aktif bisa dilihat dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat /sys/block/sda/queue/scheduler
</code></pre></div></div>
<p>Outputnya di Ubuntu saya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>noop [deadline] cfq
</code></pre></div></div>
<p>Artinya yang sedang aktif adalah <code class="language-plaintext highlighter-rouge">deadline</code>, dan pilihan lain yang tersedia adalah <code class="language-plaintext highlighter-rouge">noop</code> dan <code class="language-plaintext highlighter-rouge">cfq</code>. Entah kenapa, Ubuntu sudah memilih <code class="language-plaintext highlighter-rouge">deadline</code> secara default, sehingga saya tidak perlu melakukan konfigurasi tambahan. Namun bila kondisinya berbeda di komputer Anda, tambahkan dua baris ini di akhir file <code class="language-plaintext highlighter-rouge">/etc/rc.local</code> di atas baris <code class="language-plaintext highlighter-rouge">exit 0</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo deadline > /sys/block/sda/queue/scheduler
echo 1 > /sys/block/sda/queue/iosched/fifo_batch
</code></pre></div></div>
<p>Ganti <code class="language-plaintext highlighter-rouge">sda</code> dengan nama device harddisk Anda.</p>
<h2 id="optimasi-proses-hapus">Optimasi Proses Hapus</h2>
<p>Harddisk SSD memiliki <a href="http://en.wikipedia.org/wiki/TRIM">fitur TRIM</a> yang memungkinkan SSD mencatat blok mana yang sudah tidak terpakai (karena filenya dihapus), sehingga bisa digunakan lagi oleh file lain. Fitur TRIM ini bisa dilakukan secara otomatis maupun secara manual.</p>
<p>Untuk mengaktifkan TRIM otomatis, tambahkan opsi <code class="language-plaintext highlighter-rouge">discard</code> di <code class="language-plaintext highlighter-rouge">/etc/fstab</code> seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># / was on /dev/sda1 during installation
UUID=a7c6516b-b6c7-481a-b85f-5297f6ffcbbb / ext4 discard,errors=remount-ro 0 1
# /home was on /dev/sda6 during installation
UUID=0dfcd40a-28bd-4d7f-8e41-4ab85ef776fa /home ext4 discard,defaults 0 2
</code></pre></div></div>
<p>Walaupun demikian, TRIM otomatis <a href="https://patrick-nagel.net/blog/archives/337">kadangkala malah membuat lambat</a>. Untuk itu, kadangkala kita perlu menjalankan trim secara manual dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo fstrim -v /
sudo fstrim -v /home
</code></pre></div></div>
<p>Perintah tersebut dijalankan untuk setiap partisi dalam harddisk SSD kita. Contohnya seperti di atas bila Anda seperti saya, memisahkan antara partisi <code class="language-plaintext highlighter-rouge">/</code> dan <code class="language-plaintext highlighter-rouge">/home</code>.</p>
<p>Kita juga bisa menjalankannya secara terjadwal menggunakan <code class="language-plaintext highlighter-rouge">cron</code>.</p>
Memasang Sertifikat SSL2013-07-12T14:14:00+07:00https://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl<p>Setelah kita menjalankan semua prosedur untuk mendapatkan digital certificate, tiba saatnya kita memasangnya di webserver supaya bisa melayani request https. Pada artikel ini, kita akan membahas konfigurasi https pada webserver yang sering digunakan. Karena banyaknya merek webserver yang beredar di pasaran, kita akan ambil dua sebagai perwakilan, yaitu Tomcat mewakili webserver Java, dan Nginx mewakili non-Java. Webserver Java merek lain seperti JBoss, Jetty, Glassfish, dan rekan-rekannya memiliki cara konfigurasi yang mirip dengan Tomcat. Demikian juga Apache HTTPD, Lighttpd, Mongrel, dan sejawatnya memiliki cara konfigurasi yang mirip dengan Nginx.</p>
<p>Artikel ini adalah bagian terakhir dari 4 artikel, yaitu:</p>
<ol>
<li><a href="http://software.endy.muhardin.com/aplikasi/apa-itu-ssl/">Apa itu SSL</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/membuat-self-signed-certificate/">Membuat self-signed certificate</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/membeli-sertifikat-ssl/">Membeli sertifikat SSL</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl/">Memasang sertifikat SSL</a></li>
</ol>
<!--more-->
<p>Untuk mengaktifkan fitur https pada webserver, pada prinsipnya kita membutuhkan tiga jenis file:</p>
<ol>
<li>Private Key</li>
<li>Certificate yang sudah diberikan signature oleh Certificate Authority (CA)</li>
<li>Certificate CA yang memberikan signature. File jenis ini bisa terdiri dari banyak file, tergantung apakah CA tersebut memiliki intermediate certificate atau tidak</li>
</ol>
<p>Sebagai contoh, kita akan gunakan file-file berikut yang didapatkan dari <del><a href="https://www.startssl.com/?app=1">penyedia SSL gratis StartSSL</a></del>. <strong>Update : StartSSL <a href="https://arstechnica.com/information-technology/2017/07/google-drops-the-boom-on-wosign-startcom-certs-for-good/">sudah diblokir oleh semua browser</a>. Sekarang kita bisa pakai LetsEncrypt yang gratis dan otomatis.</strong> File self-signed yang kita buat di artikel bagian kedua juga bisa digunakan, malah lebih mudah karena sertifikat CA hanya satu, sedangkan yang dari StartSSL ada 2 file, satu Root CA dan satu Intermediate CA. Berikut adalah file-file tersebut:</p>
<ol>
<li>Private Key : <code class="language-plaintext highlighter-rouge">demo.muhardin.com.key</code></li>
<li>Sertifikat yang sudah ditandatangani CA : <code class="language-plaintext highlighter-rouge">demo.muhardin.com.crt</code></li>
<li>Sertifikat Root CA : <code class="language-plaintext highlighter-rouge">ca.pem</code></li>
<li>Sertifikat Intermediate CA : <code class="language-plaintext highlighter-rouge">sub.class1.server.ca.pem</code></li>
</ol>
<p>Ekstensi file <code class="language-plaintext highlighter-rouge">crt</code> dan <code class="language-plaintext highlighter-rouge">pem</code> sama saja. Sama-sama file text yang bisa dibuka dengan Gedit atau Notepad.</p>
<h2 id="ssl-dengan-nginx">SSL dengan Nginx</h2>
<h3 id="pengecekan-awal">Pengecekan Awal</h3>
<p>Sebelum mulai, pastikan dulu virtual host kita sudah berjalan. Berikut konfigurasi virtual host saya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server{
server_name demo.muhardin.com;
root /var/www/demo.muhardin.com/public_html;
index index.html;
}
</code></pre></div></div>
<p>Bila kita jalankan di laptop sendiri (localhost), tambahkan baris berikut di <code class="language-plaintext highlighter-rouge">/etc/hosts</code> supaya pada waktu kita mengakses alamat <code class="language-plaintext highlighter-rouge">http://demo.muhardin.com</code> akan diarahkan ke Nginx di laptop sendiri.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>127.0.0.1 demo.muhardin.com
</code></pre></div></div>
<p>Coba browse ke <code class="language-plaintext highlighter-rouge">http://demo.muhardin.com</code>, pastikan bisa diakses dengan baik.</p>
<h3 id="private-key">Private Key</h3>
<p>Private key kita normalnya disimpan dalam bentuk terenkripsi dengan passphrase. Kita bisa menggunakan file ini apa adanya, tapi setiap kali Nginx dijalankan, kita harus mengentri passphrasenya. Ini tentu lebih aman, karena private key kita selalu berada dalam bentuk yang terlindungi.</p>
<p>Tapi untuk keperluan development, pada saat private key juga bikinan sendiri, kita bisa membuka file ini menjadi bentuk plain text dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl rsa -in demo.muhardin.com.key -out demo.muhardin.com.key.txt
</code></pre></div></div>
<p>Tentu kita akan ditanyai passphrase. Setelah itu file private key yang tidak terenkripsi (plain text) akan dibuat di <code class="language-plaintext highlighter-rouge">demo.muhardin.key.txt</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter pass phrase for demo.muhardin.com.key:
writing RSA key
</code></pre></div></div>
<h3 id="ca-certificate">CA Certificate</h3>
<p>Bila CA kita memiliki rantai sertifikat, kita harus gabungkan semua menjadi satu file. Ini bisa dilakukan dengan membuka semuanya di text editor, kemudian select all, copy, paste isinya ke file baru. Dimulai dari Root CA sampai ke Intermediate paling bawah.</p>
<p>CA yang memiliki rantai sertifikat contohnya Comodo. Kita bisa lihat informasi rantainya <a href="https://support.comodo.com/index.php?_m=knowledgebase&_a=viewarticle&kbarticleid=1182">di sini</a>. Sebagai contoh, bila kita mendapatkan Free Certificate dari Comodo, maka rantainya adalah:</p>
<ol>
<li>Root : AddTrustExternalCARoot.crt</li>
<li>Intermediate 1 : UTNAddTrustSGCCA.crt</li>
<li>Intermediate 2 : ComodoUTNSGCCA.crt</li>
<li>Intermediate 3 : EssentialSSLCA_2.crt</li>
</ol>
<p>Setelah itu terakhir barulah sertifikat kita sendiri yang ditandatangani oleh Intermediate 3.</p>
<p>Kita bisa juga menggabungkan semuanya menggunakan perintah linux. Contohnya, seandainya <code class="language-plaintext highlighter-rouge">demo.muhardin.com.crt</code> ditandatangani oleh Comodo, maka perintahnya adalah sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat demo.muhardin.com.crt EssentialSSLCA_2.crt ComodoUTNSGCCA.crt UTNAddTrustSGCCA.crt AddTrustExternalCARoot.crt > sertifikat-gabungan.crt
</code></pre></div></div>
<blockquote>
<p>Awas jangan salah urutannya. Mulai dari sertifikat kita sendiri, kemudian intermediate 3 yang menandatangani sertifikat kita sendiri, intermediate 2, begitu seterusnya sampai sertifikat Root di paling akhir.</p>
</blockquote>
<h3 id="konfigurasi-nginx">Konfigurasi Nginx</h3>
<p>Berikut adalah konfigurasi Nginx yang harus ditambahkan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>listen 443 default_server ssl;
ssl_certificate /etc/nginx/sertifikat-gabungan.crt;
ssl_certificate_key /etc/nginx/demo.muhardin.com.key.txt;
</code></pre></div></div>
<p>Isi konfigurasi virtual host <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code> selengkapnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server{
server_name demo.muhardin.com;
root /var/www/demo.muhardin.com/public_html;
index index.html;
listen 443 default_server ssl;
ssl_certificate /etc/nginx/sertifikat-gabungan.crt;
ssl_certificate_key /etc/nginx/demo.muhardin.com.key.txt;
}
</code></pre></div></div>
<h2 id="ssl-dengan-tomcat">SSL dengan Tomcat</h2>
<p>Tomcat dan juga webserver Java lainnya membutuhkan file keystore yang berisi semua sertifikat dan private key yang dibutuhkan. Untuk itu, kita akan memasukkan private key, sertifikat yang sudah ditandatangani CA, dan sertifikat milik CA (beserta semua intermediate) ke dalam keystore. Prosedurnya sebagai berikut:</p>
<ol>
<li>Buat database berisi private key dan sertifikat domain yang sudah ditandatangani CA dalam format PKCS#12. Ini dilakukan menggunakan <code class="language-plaintext highlighter-rouge">openssl</code></li>
<li>Konversi format PKCS#12 menjadi JKS menggunakan keytool</li>
<li>Import sertifikat CA dan sertifikat intermediate satu persatu</li>
<li>Pasang di Tomcat</li>
</ol>
<h3 id="membuat-database-pkcs12">Membuat database PKCS12</h3>
<p>Gabungkan dulu semua sertifikat CA menjadi satu file. Ini penting dilakukan, kalau tidak rantai sertifikasinya tidak akan tersambung dan browser kita tetap akan menampilkan peringatan seperti SSL self-signed. Sama seperti penggabungan pada waktu konfigurasi Nginx, mulai dari intermediate CA yang menandatangani sertifikat kita, terus berurutan sampai Root CA di posisi terakhir.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat EssentialSSLCA_2.crt ComodoUTNSGCCA.crt UTNAddTrustSGCCA.crt AddTrustExternalCARoot.crt > ca-chain.crt
</code></pre></div></div>
<p>Verifikasi apakah sertifikat kita sudah lengkap rantainya sampai ke Root CA</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl verify -CAfile ca-chain.crt demo.muhardin.com.crt
</code></pre></div></div>
<p>Kalau tidak tersambung sampai Root CA, pesan errornya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>demo.muhardin.com.crt: OU = Domain Control Validated, OU = EssentialSSL, CN = demo.muhardin.com
error 20 at 0 depth lookup:unable to get local issuer certificate
</code></pre></div></div>
<p>Kalau sukses, outputnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>demo.muhardin.com.crt: OK
</code></pre></div></div>
<p>Jalankan perintah berikut untuk membuat database PKCS12 berisi private key, sertifikat domain, dan rantai sertifikat CA</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl pkcs12 -export -chain \
-inkey myserver.key \
-in demo.muhardin.com.crt \
-name "demo.muhardin.com" \
-CAfile ca-chain.crt \
-caname "Intermediate 3" \
-caname "Intermediate 2" \
-caname "Intermediate 1" \
-caname "Root CA" \
-out demo.muhardin.com.p12
</code></pre></div></div>
<p>Opsi <code class="language-plaintext highlighter-rouge">caname</code> gunanya untuk memberi nama untuk sertifikat CA agar lebih mudah dipahami sertifikat mana mengacu ke CA mana. Urutannya harus sesuai dengan urutan file yang kita gabungkan pada langkah sebelumnya.</p>
<p>Kita akan ditanyai passphrase tiga kali. Pertama untuk membuka private key, kedua dan ketiga untuk password database PKCS12.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter pass phrase for demo.muhardin.com.key:
Enter Export Password:
Verifying - Enter Export Password:
</code></pre></div></div>
<p>Kita akan mendapatkan file <code class="language-plaintext highlighter-rouge">demo.muhardin.com.p12</code>.</p>
<h3 id="konversi-pkcs12-menjadi-jks">Konversi PKCS12 menjadi JKS</h3>
<p>JKS adalah format database key dalam Java, atau dikenal dengan istilah keystore. Kita bisa membuatnya dengan cara import dari database berformat PKCS12. Berikut perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool -importkeystore \
-srcstoretype PKCS12 \
-srckeystore demo.muhardin.com.p12 \
-destkeystore demo.muhardin.com.jks
</code></pre></div></div>
<p>Kita akan ditanyai password sebanyak tiga kali. Pertama dan kedua adalah password keystore JKS yang baru, sedangkan password ketiga adalah password database PKCS12 yang kita buat di langkah sebelumnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter destination keystore password:
Re-enter new password:
Enter source keystore password:
Entry for alias 1 successfully imported.
Import command completed: 1 entries successfully imported, 0 entries failed or cancelled
</code></pre></div></div>
<p>Verifikasi hasilnya menggunakan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool -keystore demo.muhardin.com.jks -list
</code></pre></div></div>
<p>Berikut outputnya, menyatakan bahwa kita sudah punya satu data dalam <code class="language-plaintext highlighter-rouge">demo.muhardin.com.jks</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter keystore password:
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 1 entry
demo.muhardin.com, Jul 9, 2013, PrivateKeyEntry,
Certificate fingerprint (SHA1): 10:A6:A1:7B:A4:46:10:38:0C:5C:72:77:FA:EB:86:46:91:DA:E7:C0
</code></pre></div></div>
<h3 id="konfigurasi-tomcat">Konfigurasi Tomcat</h3>
<p>Copy file <code class="language-plaintext highlighter-rouge">demo.muhardin.com.jks</code> ke dalam folder konfigurasi Tomcat. Di laptop saya lokasinya ada di <code class="language-plaintext highlighter-rouge">/opt/tomcat/conf</code>. Lalu buka file <code class="language-plaintext highlighter-rouge">server.xml</code> di folder yang sama menggunakan text editor. Edit baris berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Connector</span> <span class="na">port=</span><span class="s">"8443"</span> <span class="na">protocol=</span><span class="s">"HTTP/1.1"</span> <span class="na">SSLEnabled=</span><span class="s">"true"</span>
<span class="na">maxThreads=</span><span class="s">"150"</span> <span class="na">scheme=</span><span class="s">"https"</span> <span class="na">secure=</span><span class="s">"true"</span>
<span class="na">clientAuth=</span><span class="s">"false"</span> <span class="na">sslProtocol=</span><span class="s">"TLS"</span>
<span class="na">keystoreFile=</span><span class="s">"conf/demo.muhardin.com.jks"</span>
<span class="na">keystorePass=</span><span class="s">"test1234"</span>
<span class="na">keyAlias=</span><span class="s">"demo.muhardin.com"</span>
<span class="nt">/></span>
</code></pre></div></div>
<p>Sesuaikan variabel berikut:</p>
<ul>
<li>keystoreFile : lokasi keystore JKS</li>
<li>keystorePass : password untuk membuka keystore</li>
<li>keyAlias : nama alias untuk sertifikat domain (karena isi keystore ada 3 entri, harus dijelaskan mana yang akan digunakan)</li>
</ul>
<h2 id="test">Test</h2>
<p>Setelah semua selesai dikonfigurasi, Nginx bisa dites dengan mengakses <code class="language-plaintext highlighter-rouge">https://demo.muhardin.com</code>, sedangkan Tomcat bisa diakses di <code class="language-plaintext highlighter-rouge">https://demo.muhardin.com:8443</code>.</p>
<p>Bila kita menggunakan sertifikat dari CA resmi, walaupun gratis kita akan mendapatkan warna hijau di address bar</p>
<p><a href="/images/uploads/2013/07/ssl/04-tomcat-https-green.png"><img src="/images/uploads/2013/07/ssl/04-tomcat-https-green.png" alt="Tomcat HTTPS Green " /></a></p>
<p>Namun bila kita menggunakan CA buatan sendiri (self-signed), maka pada waktu pertama diakses, kita akan diberikan peringatan.</p>
<p><a href="/images/uploads/2013/07/ssl/01-ssl-warning.png"><img src="/images/uploads/2013/07/ssl/01-ssl-warning.png" alt="Peringatan SSL Self Signed " /></a></p>
<p>Agar bisa tetap diakses, kita bisa klik Add Exception. Kita akan diperlihatkan informasi tentang sertifikat SSL</p>
<p><a href="/images/uploads/2013/07/ssl/02-add-exception.png"><img src="/images/uploads/2013/07/ssl/02-add-exception.png" alt="Add Exception " /></a></p>
<p>Kita bisa melihat detail informasi dari sertifikat tersebut, siapa pemilik server dan siapa yang menandatangani sertifikatnya</p>
<p><a href="/images/uploads/2013/07/ssl/03-certificate-info.png"><img src="/images/uploads/2013/07/ssl/03-certificate-info.png" alt="Detail Informasi Sertifikat " /></a></p>
<p>Bila kita klik Confirm Security Exception, barulah halaman web kita ditampilkan. Tapi masih ada warna merah di address bar.</p>
<p><a href="/images/uploads/2013/07/ssl/05-tomcat-https-red.png"><img src="/images/uploads/2013/07/ssl/05-tomcat-https-red.png" alt="Tomcat HTTPS Red " /></a></p>
<h2 id="error-yang-umum-terjadi">Error yang umum terjadi</h2>
<h3 id="password-private-key-berbeda-dengan-password-keystore">Password private key berbeda dengan password keystore</h3>
<p>Kadangkala pada waktu menjalankan Tomcat, kita menjumpai pesan error berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SEVERE: Catalina.start
LifecycleException: Protocol handler initialization failed: java.io.IOException: Cannot recover key
</code></pre></div></div>
<p>Ini disebabkan karena password untuk membuka keystore berbeda dengan password untuk membuka private key. Biasanya terjadi bila kita mengganti password keystore setelah melakukan import. Keytool hanya akan mengganti password keystore, tapi tidak mengganti password private key.</p>
<p>Kita harus samakan dulu dengan mengganti salah satunya.</p>
<p>Bila ingin mengganti password private key, berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool -keystore demo.muhardin.com.jks -alias demo.muhardin.com -keypasswd
Enter keystore password: Masukkan password keystore
Enter key password for <loket.asialink.co.id>: Masukkan password private key yang lama
New key password for <loket.asialink.co.id>: Masukkan password private key yang baru, samakan dengan password keystore
Re-enter new key password for <loket.asialink.co.id>: Konfirmasi password private key yang baru
</code></pre></div></div>
<p>Atau bila ingin mengganti password keystore, berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool -keystore demo.muhardin.com.jks -storepasswd
</code></pre></div></div>
<h3 id="certificate-chain-tidak-tersambung">Certificate Chain tidak tersambung</h3>
<p>Ada kalanya sertifikat yang sudah kita beli mahal-mahal tetap menimbulkan warning di browser. Ini bisa disebabkan karena dua hal, yaitu:</p>
<ol>
<li>
<p>CA tidak dikenali oleh browser. Pastikan kita membeli sertifikat dari CA yang dikenali browser. Daftarnya bisa dilihat <a href="http://en.wikipedia.org/wiki/Comparison_of_SSL_certificates_for_web_servers">di Wikipedia</a></p>
</li>
<li>
<p>Kita tidak menginstal rantai sertifikat dengan benar.</p>
</li>
</ol>
<p>Untuk masalah kedua, kita bisa periksa menggunakan perintah <code class="language-plaintext highlighter-rouge">openssl</code> di commandline sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl s_client -connect demo.muhardin.com:443
</code></pre></div></div>
<p>Kadangkala kita mendapat pesan error berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONNECTED(00000003)
140315329672896:error:14077438:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert internal error:s23_clnt.c:741:
</code></pre></div></div>
<p>Hal tersebut disebabkan karena <a href="https://bugs.launchpad.net/ubuntu/+source/openjdk-6/+bug/1006776">bug pada OpenJDK</a>, gunakan opsi <code class="language-plaintext highlighter-rouge">-ssl3</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl s_client -connect demo.muhardin.com:443 -ssl3
</code></pre></div></div>
<p>Bila <a href="http://blog.fnsecurity.org/2013/02/proper-ssl-chaining.html">sertifikat kita tidak tersambung sampai Root CA</a>, maka kita akan mendapat error seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONNECTED(00000003)
depth=0 description = KWbw1iy6BUBg3JQ8, C = ID, CN = demo.muhardin.com, emailAddress = endy.muhardin@gmail.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 description = KWbw1iy6BUBg3JQ8, C = ID, CN = demo.muhardin.com, emailAddress = endy.muhardin@gmail.com
verify error:num=27:certificate not trusted
verify return:1
depth=0 description = KWbw1iy6BUBg3JQ8, C = ID, CN = demo.muhardin.com, emailAddress = endy.muhardin@gmail.com
verify error:num=21:unable to verify the first certificate
verify return:1
</code></pre></div></div>
<p>Periksa lagi pembuatan sertifikat kita. Pastikan rantai sertifikat CA sudah diimport dengan benar. Biasanya ini terjadi pada waktu pembuatan keystore Java. Coba ikuti lagi langkah-langkah di atas dengan teliti.</p>
<p>Sertifikat yang benar harusnya mengeluarkan output seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONNECTED(00000003)
depth=2 C = IL, O = StartCom Ltd., OU = Secure Digital Certificate Signing, CN = StartCom Certification Authority
verify error:num=19:self signed certificate in certificate chain
verify return:0
---
Certificate chain
0 s:/description=KWbw1iy6BUBg3JQ8/C=ID/CN=demo.muhardin.com/emailAddress=endy.muhardin@gmail.com
i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Class 1 Primary Intermediate Server CA
1 s:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Class 1 Primary Intermediate Server CA
i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
2 s:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
</code></pre></div></div>
<p>Kita bisa lihat pada output di atas bahwa:</p>
<ul>
<li>issuer di sertifikat 0 sama dengan subject di sertifikat 1</li>
<li>issuer di sertifikat 1 sama dengan subject di sertifikat 2</li>
<li>issuer di sertifikat 2 sama dengan subject dirinya sendiri (self-signed)</li>
</ul>
<p>Root CA memang self-signed, karena dia paling atas. Sertifikat self-signed milik Root CA inilah yang ditanam di dalam aplikasi seperti dijelaskan di <a href="http://software.endy.muhardin.com/aplikasi/apa-itu-ssl/">artikel bagian pertama</a>.</p>
<p>Demikianlah langkah-langkah untuk mengaktifkan SSL di Tomcat dan Nginx. Selamat mencoba. Kalau ada error atau hal yang kurang jelas, silahkan ditulis di komentar.</p>
Membeli Sertifikat SSL2013-07-11T19:55:00+07:00https://software.endy.muhardin.com/aplikasi/membeli-sertifikat-ssl<p>Pada waktu saya pertama kali ingin membeli sertifikat SSL, saya dihadapkan pada berbagai jenis sertifikat yang bisa dibeli. Butuh waktu dan usaha yang relatif besar untuk mengetahui maksud dari macam-macam jenis sertifikat tersebut. Pada artikel ini, kita akan membahas berbagai jenis sertifikat tersebut dengan bahasa yang mudah dipahami.</p>
<p>Artikel ini adalah bagian ketiga dari 4 artikel, yaitu:</p>
<ol>
<li><a href="http://software.endy.muhardin.com/aplikasi/apa-itu-ssl/">Apa itu SSL</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/membuat-self-signed-certificate/">Membuat self-signed certificate</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/membeli-sertifikat-ssl/">Membeli sertifikat SSL</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl/">Memasang sertifikat SSL</a></li>
</ol>
<!--more-->
<h2 id="vendor-ssl">Vendor SSL</h2>
<p>Ada banyak sekali vendor SSL yang tersedia. Beberapa yang populer antara lain:</p>
<ul>
<li><a href="http://www.comodo.com/">Comodo</a></li>
<li><a href="http://www.verisign.com/">Verisign</a></li>
<li><a href="http://www.verizonenterprise.com/products/security/identity/ssl/">Cybertrust/Verizon</a></li>
<li><a href="http://www.startssl.com/">StartSSL</a></li>
<li>dan masih banyak yang lain lagi</li>
</ul>
<p>Masing-masing vendor menyediakan berbagai paket dan kategori, masing-masing diberi nama yang terdengar canggih dan hi-tech. Ini semua membuat kita yang baru pertama kali membeli sertifikat menjadi bingung. Berdasarkan hasil tanya-tanya dan googling, akhirnya saya mendapatkan metode pengelompokan yang lebih mudah dipahami end-user.</p>
<p>Kita bisa membedakan sertifikat SSL berdasarkan dua hal:</p>
<ul>
<li>cakupan penggunaan</li>
<li>prosedur validasi</li>
</ul>
<h2 id="penggunaan-sertifikat">Penggunaan Sertifikat</h2>
<p>Sertifikat SSL biasanya digunakan untuk webserver dan mailserver. Kedua layanan ini bekerja berdasarkan nama domain. Misalnya saya punya domain <code class="language-plaintext highlighter-rouge">artivisi.com</code>. Dari domain ini, saya bisa memiliki banyak server misalnya:</p>
<ul>
<li>www.artivisi.com : website perusahaan</li>
<li>demo.artivisi.com : situs untuk demo produk dan layanan</li>
<li>mail.artivisi.com : mail server, untuk mengirim dan menerima email</li>
</ul>
<p>Kita bisa membedakan jenis sertifikat SSL berdasarkan penggunaannya pada domain di atas menjadi:</p>
<ul>
<li>single domain</li>
<li>wildcard domain</li>
<li>multi domain</li>
</ul>
<h3 id="single-domain">Single Domain</h3>
<p>Sertifikat single domain hanya bisa digunakan di satu domain saja. Misalnya kita membeli sertifikat untuk <code class="language-plaintext highlighter-rouge">www.artivisi.com</code> sehingga website ArtiVisi bisa diakses melalui <code class="language-plaintext highlighter-rouge">https://www.artivisi.com</code>. Sertifikat yang kita beli ini hanya bisa digunakan untuk domain <code class="language-plaintext highlighter-rouge">www.artivisi.com</code>. Dia tidak bisa kita pasang di mail server yang memiliki nama <code class="language-plaintext highlighter-rouge">mail.artivisi.com</code>.</p>
<p>Bila kita membutuhkan SSL untuk <code class="language-plaintext highlighter-rouge">mail.artivisi.com</code>, kita harus membeli sertifikat sekali lagi. Demikian seterusnya, masing-masing domain/subdomain harus memiliki sertifikat sendiri.</p>
<blockquote>
<p>Wah, apa tidak merepotkan? Belum lagi harganya pasti akan jadi mahal kalau setiap subdomain harus beli lagi.</p>
</blockquote>
<p>Nah, pada saat kita merasa kerepotan dan kemahalan, tiba saatnya kita membeli sertifikat <code class="language-plaintext highlighter-rouge">wildcard domain</code></p>
<h3 id="wildcard-domain">Wildcard Domain</h3>
<p>Sertifikat ini disebut wildcard karena satu sertifikat bisa digunakan untuk seluruh subdomain. CA akan membuatkan sertifikat dengan CN <code class="language-plaintext highlighter-rouge">*.artivisi.com</code>, sehingga bisa digunakan untuk <code class="language-plaintext highlighter-rouge">www.artivisi.com</code>, <code class="language-plaintext highlighter-rouge">mail.artivisi.com</code>, dan seluruh subdomain <code class="language-plaintext highlighter-rouge">artivisi.com</code>.</p>
<p>Harganya tentu lebih mahal daripada single domain. Sebagai gambaran kita lihat <a href="http://www.symantec.com/page.jsp?id=compare-ssl-certificates">harga dari Verisign</a>. Single domain harganya $400 dan wildcard harganya $1200. Kita baru balik modal kalau punya 3 subdomain.</p>
<h3 id="multi-domain--san-extended">Multi Domain / SAN Extended</h3>
<p>Sertifikat dengan extended validation (akan dibahas di bawah) tidak bisa menggunakan mekanisme wildcard. Untuk itu, diakali dengan menaruh banyak nama domain di field <code class="language-plaintext highlighter-rouge">subject alternative name</code> (SAN), sehingga satu sertifikat bisa digunakan di banyak nama domain.</p>
<p>Dengan klasifikasi penggunaan ini, kita bisa memilih mana yang akan kita gunakan. Umumnya kita cuma memilih antara single dan wildcard. Pada kasus tertentu dimana kita menggunakan Extended Validation, barulah kita mempertimbangkan antara single domain dan multi domain.</p>
<h2 id="prosedur-validasi">Prosedur Validasi</h2>
<p>Berdasarkan prosedur validasi, sertifikat SSL dibagi menjadi tiga jenis:</p>
<ul>
<li>Domain Validation (DV)</li>
<li>Organization Validation (OV)</li>
<li>Extended Validation (EV)</li>
</ul>
<h3 id="domain-validation">Domain Validation</h3>
<p>Metode validasi yang paling mudah dan cepat adalah domain validation. Saking mudahnya, biasanya proses ini dilakukan dengan aplikasi dan tidak melibatkan campur tangan manusia, sehingga lebih murah.</p>
<p>Domain validation memastikan bahwa pemohon benar-benar punya akses terhadap domain. Berikut cara kerjanya:</p>
<ol>
<li>
<p>Pemohon mengirim CSR pada CA berisi domain yang akan disertifikasi. Misalnya <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code>.</p>
</li>
<li>
<p>CA akan mencari tahu siapa pemilik domain tersebut menggunakan fasilitas whois. Disana nantinya ada informasi email pemilik domain. Contoh output whois seperti ini:</p>
<p><a href="/images/uploads/2013/07/ssl/output-whois.png"><img src="/images/uploads/2013/07/ssl/output-whois.png" alt="Output Whois " /></a></p>
</li>
<li>
<p>Adakalanya orang yang membeli domain berbeda dengan orang teknis yang mengurus domain, sehingga alamat email di whois tidak bisa digunakan. Bila terjadi seperti ini, pemohon bisa minta CA menggunakan email lain, biasanya <code class="language-plaintext highlighter-rouge">postmaster@muhardin.com</code> atau <code class="language-plaintext highlighter-rouge">webmaster@muhardin.com</code>. Ini adalah alamat email standar untuk administrasi mailserver dan webserver. Jadi pastikan kedua alamat email ini dilindungi dengan baik dan hanya diberikan aksesnya pada orang yang berhak.</p>
</li>
<li>
<p>CA mengirim email ke alamat yang ditentukan di langkah sebelumnya. Di email tersebut ada kode verifikasi untuk memastikan bahwa pemohon benar-benar punya akses ke alamat email tersebut.</p>
</li>
<li>
<p>Pemohon membuktikan bahwa dia telah menerima email verifikasi. Cara yang umum dilakukan antara lain memasukkan kode verifikasi ke website CA, atau mengklik link verifikasi yang ada dalam email.</p>
</li>
<li>
<p>Setelah verifikasi dilakukan, CA akan mengirim sertifikat domain yang telah ditandatangani. Atau pemohon juga bisa mengunduhnya di website CA.</p>
</li>
</ol>
<p>Pada level validasi ini, CA memastikan identitas pemohon berupa:</p>
<ul>
<li>akses/kontrol terhadap domain</li>
</ul>
<h3 id="organization-validation">Organization Validation</h3>
<p>Selangkah lebih jauh, ada yang namanya Organization Validation. Pada validasi level ini, CA melakukan validasi tambahan terhadap organisasi yang meminta sertifikat SSL. Biasanya pemohon harus mengirim akta perusahaan atau bukti legalitas lainnya.</p>
<p>Pada level validasi ini, CA memastikan identitas pemohon berupa:</p>
<ul>
<li>akses/kontrol terhadap domain</li>
<li>legalitas organisasi pemilik domain</li>
</ul>
<h3 id="extended-validation">Extended Validation</h3>
<p>Pada metode validasi sebelumnya, CA sama sekali tidak melakukan validasi terhadap orang/contact person.</p>
<p><a href="http://en.wikipedia.org/wiki/Extended_Validation_Certificate">Metode Extended Validation</a> adalah metode validasi yang paling lengkap.</p>
<p>Di sini CA juga akan menghubungi langsung contact person dari organisasi pemohon sertifikat. Detail teknisnya saya kurang paham karena belum pernah membeli sertifikat EV. Konon katanya, EV ini melibatkan pemeriksaan fisik berupa kunjungan atau pertemuan dengan perwakilan organisasi.</p>
<p>Mengingat tingkat ketelitian dalam validasinya, maka sertifikat level EV tidak boleh diterbitkan dalam bentuk <em>wildcard</em>. Ini disebabkan karena sertifikat wildcard memungkinkan kita membuat domain tanpa harus divalidasi CA. Bila kita ingin satu sertifikat untuk banyak domain, maka kita harus menggunakan Subject Alternative Name (SAN), dimana masing-masing nama domain akan divalidasi satu-persatu.</p>
<p>Sertifikat yang memiliki validasi EV akan terlihat berbeda di browser, ditandai dengan adanya <em>green bar</em> seperti di Internet Banking Mandiri</p>
<p><a href="/images/uploads/2013/07/ssl/green-bar-mandiri.png"><img src="/images/uploads/2013/07/ssl/green-bar-mandiri.png" alt="Green Bar Mandiri " /></a></p>
<p>atau di KlikBCA</p>
<p><a href="/images/uploads/2013/07/ssl/01-green-bar.png"><img src="/images/uploads/2013/07/ssl/01-green-bar.png" alt="Green Bar BCA " /></a></p>
<p>Kita bisa melihat informasi detail tentang sertifikat SSL dengan cara klik green bar tersebut</p>
<p><a href="/images/uploads/2013/07/ssl/02-green-bar-info.png"><img src="/images/uploads/2013/07/ssl/02-green-bar-info.png" alt="Informasi Green Bar " /></a></p>
<p>Pada level validasi EV, CA memastikan identitas pemohon berupa:</p>
<ul>
<li>akses/kontrol terhadap domain</li>
<li>legalitas organisasi pemilik domain</li>
<li>keberadaan fisik organisasi dan contact person yang ditugaskan mewakilinya</li>
</ul>
<h3 id="memilih-metode-validasi">Memilih metode validasi</h3>
<blockquote>
<p>Dari ketiga metode ini, pilih mana?</p>
</blockquote>
<p>Pertimbangan pertama tentu masalah harga. Makin teliti validasinya, biaya akan semakin tinggi.</p>
<p>Pertimbangan kedua adalah masalah kerepotannya. Makin tinggi level validasi, makin banyak dokumen yang harus kita siapkan.</p>
<p>Satu hal yang perlu diperhatikan, <a href="http://unmitigatedrisk.com/?p=203">pengunjung awam tidak akan bisa membedakan antara Domain Validation dan Organization Validation</a> . Jadi kalau ingin memilih, cukup dua yang dipertimbangkan, Extended Validation (EV) atau non-EV.</p>
<p>Perlu diperhatikan juga bahwa validasi tidak menjamin keamanan 100%. Pernah ada kasus website palsu di Amerika Serikat yang memiliki sertifikat asli. Ceritanya bisa dibaca <a href="http://voices.washingtonpost.com/securityfix/2006/02/the_new_face_of_phishing_1.html">di sini</a>.</p>
<h3 id="memilih-ca">Memilih CA</h3>
<p>CA yang menjajakan sertifikat di internet sangat banyak. Mulai dari yang gratis sampai yang mahal. Sepintas terlihat ada CA yang kredibilitasnya tinggi seperti Cybertrust atau VeriSign yang banyak dipakai bank di Indonesia, dan juga ada yang jarang terdengar seperti <del>StartSSL</del> atau Cacert. Kredibilitas CA ini mempengaruhi harga. Untuk produk yang setingkat, misalnya produk termurah single domain dengan domain validation, harganya bisa berbeda beberapa kali lipat. Coba bandingkan harga <a href="http://www.mangkukmerah.com/ssl">Comodo</a> dengan <a href="http://www.symantec.com/en/aa/theme.jsp?themeid=compare-ssl-certificates">Verisign</a>. Harga Comodo 240 ribu rupiah, sedangkan VeriSign 4 juta rupiah.</p>
<p>Walaupun demikian, menurut pandangan saya pribadi, masalah kredibilitas ini tidak terlalu signifikan pengaruhnya terhadap pengunjung. Sebagai ilustrasi, kita yang sering menggunakan internet banking apa pernah tahu siapa CA yang digunakan oleh bank kita? Atau apakah kita tahu siapa itu Cybertrust atau VeriSign?</p>
<p>Untuk menambah added value, beberapa CA mengeluarkan stempel (seal) yang bisa dipasang di website. Contohnya bisa dilihat di KlikBCA berikut</p>
<p><a href="/images/uploads/2013/07/ssl/01-green-bar.png"><img src="/images/uploads/2013/07/ssl/01-green-bar.png" alt="Green Bar BCA " /></a></p>
<p>atau Internet Banking Mandiri berikut</p>
<p><a href="/images/uploads/2013/07/ssl/stempel-ca-mandiri.png"><img src="/images/uploads/2013/07/ssl/stempel-ca-mandiri.png" alt="Green Bar Mandiri " /></a></p>
<p>Tapi menurut saya, stempel inipun juga tidak menambah keyakinan pengunjung, karena siapapun bisa memasang logo tersebut dengan mudah.</p>
<p>Ada faktor lain yang lebih penting dipertimbangkan menurut saya, yaitu kompatibilitasnya di berbagai browser. CA mahal biasanya kompatibel di semua browser, sedangkan CA murah kadang-kadang tidak diakui browser, sehingga menimbulkan warning seperti halnya self-signed certificate. Kalau memang tetap mengeluarkan warning, ya buat apa kita beli. Lebih baik pakai yang gratisan saja.</p>
<p>Demikianlah penjelasan tentang bermacam-macam sertifikat SSL yang tersedia di pasaran. Silahkan pilih yang sesuai dengan kebutuhan. Setelah sertifikat didapatkan, baik self-signed atau dari CA resmi, kita akan membahas <a href="http://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl/">cara instalasinya di artikel selanjutnya</a>.</p>
Membuat Self Signed Certificate2013-07-09T13:41:00+07:00https://software.endy.muhardin.com/aplikasi/membuat-self-signed-certificate<p>Idealnya, sertifikat SSL disetujui (signed) oleh Certificate Authority (CA).
Proses persetujuan ini biasanya dikenakan biaya, walaupun ada juga yang <a href="http://www.startssl.com/?app=1">gratisan</a>.
Sertifikat yang disetujui CA memiliki batas waktu pemakaian. Setelah waktu tersebut habis, maka sertifikat tidak bisa digunakan dan kita harus mengurus perpanjangannya.</p>
<p>Kondisi ini tentu kurang menyenangkan pada waktu kita ada di fase development. Untuk itu kita ingin membuat sertifikat sendiri, dan kemudian menyetujuinya sendiri juga. Ini disebut dengan istilah <code class="language-plaintext highlighter-rouge">self-signed certificate</code>. Bila diakses di browser, <code class="language-plaintext highlighter-rouge">self-signed certificate</code> ini akan menimbulkan peringatan seperti screenshot dibawah.</p>
<p><a href="/images/uploads/2013/07/ssl/01-ssl-warning.png"><img src="/images/uploads/2013/07/ssl/01-ssl-warning.png" alt="Halaman Warning " /></a></p>
<p>Agar bisa mengakses halaman tersebut, kita perlu menambahkan exception. Kita akan dikonfirmasi seperti pada layar berikut</p>
<p><a href="/images/uploads/2013/07/ssl/02-add-exception.png"><img src="/images/uploads/2013/07/ssl/02-add-exception.png" alt="Add Exception " /></a></p>
<p>Tapi tidak masalah, karena pada fase development yang pakai aplikasi/website ini hanya kita sendiri, bukan end-user. Nantinya sebelum dipublish untuk umum, belilah sertifikat yang benar.</p>
<p>Artikel ini adalah bagian kedua dari 4 artikel, yaitu:</p>
<ol>
<li><a href="http://software.endy.muhardin.com/aplikasi/apa-itu-ssl/">Apa itu SSL</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/membuat-self-signed-certificate/">Membuat self-signed certificate</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/membeli-sertifikat-ssl/">Membeli sertifikat SSL</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl/">Memasang sertifikat SSL</a></li>
</ol>
<!--more-->
<h2 id="garis-besar-prosedur">Garis Besar Prosedur</h2>
<p>Ada beberapa langkah yang perlu kita lakukan untuk membuat self-signed certificate. Berikut adalah hasil akhir yang kita harapkan setelah semua prosedur dilalui:</p>
<ul>
<li>File private key yang dienkripsi dengan password</li>
<li>File certificate yang sudah disetujui CA</li>
<li>File certificate milik CA. Untuk self-signed filenya cuma satu. Tapi ketika kita membeli sertifikat berbayar, file dari CA ini bisa banyak tergantung dari rantai approval dari CA tersebut.</li>
<li>Khusus untuk aplikasi Java, ketiga file tersebut harus dimasukkan ke dalam database yang disebut dengan istilah <code class="language-plaintext highlighter-rouge">keystore</code></li>
</ul>
<p>Berikut adalah langkah-langkah untuk membuat self-signed certificate:</p>
<ol>
<li>Buat private key</li>
<li>Buat permohonan persetujuan sertifikat, dalam bahasa Inggris disebut Certificate Signing Request (CSR)</li>
<li>Buat dulu CA yang akan menandatangani sertifikat. Langkah ini tidak diperlukan bila kita membeli sertifikat dari CA.</li>
<li>Setujui CSR oleh CA yang dibuat sendiri. Langkah ini juga harusnya dilakukan oleh CA yang benar.</li>
<li>Khusus aplikasi Java, masukkan semua file yang dibutuhkan ke dalam <code class="language-plaintext highlighter-rouge">keystore</code></li>
</ol>
<h2 id="aplikasi-yang-dibutuhkan">Aplikasi yang dibutuhkan</h2>
<p>Ada dua aplikasi yang umum digunakan untuk melakukan prosedur di atas, yaitu <code class="language-plaintext highlighter-rouge">openssl</code> dan <code class="language-plaintext highlighter-rouge">keytool</code>. Aplikasi <code class="language-plaintext highlighter-rouge">openssl</code> biasanya sudah terinstal di distro Linux populer sehingga kita tinggal pakai saja. Aplikasi <code class="language-plaintext highlighter-rouge">keytool</code> merupakan bawaan dari Java SDK. Kita harus menginstal dulu Java SDK untuk bisa menggunakan aplikasi ini.</p>
<p>Kedua aplikasi bisa melakukan semua prosedur di atas. Pertanyaan penting yang kita hadapi adalah</p>
<blockquote>
<p>Mana yang lebih baik pakai openssl atau keytool ?</p>
</blockquote>
<p>Setelah mencoba kedua aplikasi, saran saya adalah: gunakan OpenSSL untuk semua proses, kecuali memasukkan file ke dalam keystore. OpenSSL tidak bisa mengelola keystore, sehingga kita harus pakai keytool.</p>
<blockquote>
<p>Kenapa begitu?</p>
</blockquote>
<p>Untuk aplikasi web, sertifikat SSL biasanya digunakan oleh webserver. Bila aplikasi kita Java, maka webservernya adalah Tomcat atau Jetty. Apabila non Java, biasanya Apache HTTPD, Nginx, Lighttpd, atau lainnya.</p>
<p>Webserver untuk aplikasi Java membutuhkan file sertifikat dalam bentuk keystore, sedangkan webserver untuk aplikasi non Java membutuhkan sertifikat dalam bentuk file text.</p>
<p>Dari bentuk file text kita bisa membuat bentuk keystore dengan mudah. Cukup berbekal <code class="language-plaintext highlighter-rouge">openssl</code> dan <code class="language-plaintext highlighter-rouge">keytool</code> pekerjaan sudah bisa diselesaikan. Tapi sebaliknya, mengeluarkan private key dari keystore menjadi file text <a href="http://conshell.net/wiki/index.php/Keytool_to_OpenSSL_Conversion_tips">relatif sulit dan membutuhkan aplikasi tambahan</a>.</p>
<p>Oleh karena itu, pada artikel ini, semua langkah akan dilakukan dengan OpenSSL. Keytool akan digunakan di bagian terakhir pada waktu kita ingin memasang sertifikat di Tomcat.</p>
<h2 id="struktur-folder">Struktur Folder</h2>
<p>Sebagai studi kasus, kita akan membuat aplikasi web yang dihosting dengan nama domain <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code>. Seluruh file yang terlibat dalam rangkaian artikel ini bisa diambil di <a href="https://github.com/endymuhardin/belajar-ssl">repository Github</a>. Perhatikan juga <a href="https://github.com/endymuhardin/belajar-ssl/commits/master">history commit</a> untuk mengetahui urutan langkah-langkah yang dilakukan.</p>
<p>Siapkan dua folder, <code class="language-plaintext highlighter-rouge">ca</code> dan <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code>. Folder <code class="language-plaintext highlighter-rouge">ca</code> akan digunakan untuk operasional CA yaitu menyetujui CSR yang masuk. Sedangkan folder <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code> berisi semua file yang dibutuhkan webserver kita supaya bisa menjalankan HTTPS.</p>
<p>Nantinya bila kita membeli sertifikat berbayar, kita tidak perlu membuat folder <code class="language-plaintext highlighter-rouge">ca</code> ini.</p>
<h2 id="membuat-private-key">Membuat Private Key</h2>
<p>Kita ingin membuat private key dengan konfigurasi sebagai berikut:</p>
<ul>
<li>ukuran key : 2048 bit</li>
<li>algoritma : RSA</li>
<li>enkripsi : AES 256 bit</li>
</ul>
<p>Berikut adalah perintahnya, lakukan di dalam folder <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl genrsa -aes256 2048 > demo.muhardin.com.key
</code></pre></div></div>
<p>Berikut adalah outputnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Generating RSA private key, 2048 bit long modulus
......+++
.................................+++
e is 65537 (0x10001)
Enter pass phrase:
Verifying - Enter pass phrase:
</code></pre></div></div>
<p>Kita diminta passphrase untuk mengenkripsi private key tersebut. Saya masukkan <code class="language-plaintext highlighter-rouge">test1234</code> sebagai password. Setelah selesai, kita akan mendapatkan satu file bernama <code class="language-plaintext highlighter-rouge">demo.muhardin.com.key</code>. Isinya adalah private key yang dienkripsi dengan algoritma AES berkekuatan 256 bit.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,425E8DDEEC7FD63F9E67608F7B25BAC5
USIjQCyFsSfj/gbhXC0gT43qM9/4D9qGaEOAX2Ks7lxhs256LJImgeBywbnkzC67
l8ZJQlivxSlIaC658RUcVNNbWwN4EgsczCTSsGjxUxUQ46ay1XUaAbO82oLeZWx7
It0hQ/plno/kkpodtVwOvSks66ZdOIyTZ/IEA4BTMrmdsYi0PHfkbvlHfql9pdSO
8d4cbgQU9nSuGOzXIX1UEMXluTIYBP0aj151bvPeocjeK5WhioldTm+LemfutiMd
2N672BgMkhDeuYBauglChS0Q/fLmcRFM98ys+LcvEMlKtkT7L+vL4Wb7ulwhrOdG
g2ecMvAjcl9RBrSszOWZ6L2xMqvjFo6xS0bras1BVetYbvdC9PEQW0RXycbwzwGL
L9PJuQtKssI3MGTAydeKwrpT0Di27TiLqqb/QNBgJ/954JGLJJmpqVG3bt9YhCe5
DwGASsFysuRxNPCW1C3aQ3qhydA3xgYJZrpdaRAIk/KRwxx+CFrk4oV0dom0kVTx
w6jwjE0BS3q8e+nI42kXAilK84Qd4x6ZMAxM20DNQcucWGMRtJbrvvhJuLgMUNTI
MjOHAqojk9vAu5m4XXCUtXALhMZrepFXjaVTblfyPZ1hhFutiDhZaGzzj5SL1uoY
yLavTih3dQMpNBcOXY0TU/E4nKa0OhdGsNHs3gK9l77nO7IvZABviG2Qq50NuATj
AjvQVNeEiJKdq5FaiHgX+/g7PLNbXUtNWhLZ6jXLc6flQLOuBwiMCR0Ug/46IlTC
DOhsO36MDrMIxRjPgpcH00k9iXOvbuEILWGPjQ/WPNw7UoGEBepQSuIw2zzyD6ai
DHVx2pD60U2anRzvvXzVR7Tf42Tm1fxR4epTdrJIY3LJQtTr2PMCNbcn8+kGfTg1
M+1MCr+WN2pJhpYYr7ACFXFBJc1QLc/wF6/e47bkH3BLxWLZG1KQsLKzLim4teX+
zFvBkOz7SSgDlH0xyogiihjlgZxjRNkk/P5OvtbdKWpOrJh649mw+zwvuN3TmrA0
C/m70olU9QgjLCK3wlgkIr6bj1dS4sDPHghmEtpmp6OC8Ao1R9zDsIhIM1K9+ro7
rE/TzvADeZPg7tzwzRJeTiYq2gN/xxeqlW7YdxGZVPizcfPtqhLnBqzxKeb/uKjT
xuDMlQwZdlTV1QAGB1HKlb3qZdBtyEfUDEkwDL2z66r6roR750DtMnCwTsv9RIAJ
9J2BBIvWjBQiS61p+m5vQ+9ImfqIs6KnZmvbvX5qZvrXRlqyQKdcio87AbqlPNl8
IwrNP5ZePS5fBOJXcqtrecOKB3un+HmqiHqKWl8+O8TaMlgJNwdyw+4etzDWSH11
7AYJJ2SXqIVf0UqZPeYHS7oBsISvP2ekRLAx0XSvxnukBSGolRzyhs8oaRhB3MBr
uJRlKnQAfcFCk1zMNa75CC2vPP2WFMPixaR4HbBQ0pnMQp3KQHCuwmU7miS56EQm
hZVa0yzlKJ/Tiv7Vy+1v0FSiVqWhO5K1p/TG0jcj2tsQ76P2gwXvUZLFnm7bdom2
o8OfPVrr1Btje9+InVqiw2N3pgBBhRmE8ug/IYmz3tU3dxjXvjoJLpklah7ag4s2
-----END RSA PRIVATE KEY-----
</code></pre></div></div>
<h2 id="membuat-csr">Membuat CSR</h2>
<p>Dengan bermodalkan private key tersebut, kita akan membuat CSR. Berikut perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl req -new -key demo.muhardin.com.key -out demo.muhardin.com.csr
</code></pre></div></div>
<p>Kita akan ditanyai beberapa pertanyaan yang akan kita bahas dibawah. Berikut adalah outputnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter pass phrase for demo.muhardin.com.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:ID
State or Province Name (full name) [Some-State]:DKI Jakarta
Locality Name (eg, city) []:Jakarta Timur
Organization Name (eg, company) [Internet Widgits Pty Ltd]:PT. Endy Muhardin
Organizational Unit Name (eg, section) []:Divisi Teknologi Informasi
Common Name (e.g. server FQDN or YOUR name) []:demo.muhardin.com
Email Address []:endy@muhardin.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
</code></pre></div></div>
<ol>
<li>Kita diminta passphrase untuk membuka private key. Masukkan <code class="language-plaintext highlighter-rouge">test1234</code> seperti yang kita buat pada langkah pertama.</li>
<li>Kita akan ditanyai informasi tentang identitas kita. Masukkan sesuai kebutuhan.</li>
<li>Kita harus mengisi <code class="language-plaintext highlighter-rouge">Common Name</code>. Ini penting, terutama kalau kita membeli sertifikat berbayar!! Pastikan kita isi sesuai nama domain yang ingin kita pasangi https. Bila namanya tidak cocok, sertifikatnya akan menimbulkan warning. Saya isi sesuai nama domain yaitu <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code>.</li>
<li>Email address juga harus diisi dengan benar. Ini biasanya digunakan CA untuk melakukan verifikasi.</li>
<li>Challenge password dan optional company name tidak saya isi</li>
</ol>
<p>Hasilnya adalah file CSR bernama <code class="language-plaintext highlighter-rouge">demo.muhardin.com.csr</code> yang isinya sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-----BEGIN CERTIFICATE REQUEST-----
MIIDADCCAegCAQAwgboxCzAJBgNVBAYTAklEMRQwEgYDVQQIDAtES0kgSmFrYXJ0
YTEWMBQGA1UEBwwNSmFrYXJ0YSBUaW11cjEaMBgGA1UECgwRUFQuIEVuZHkgTXVo
YXJkaW4xIzAhBgNVBAsMGkRpdmlzaSBUZWtub2xvZ2kgSW5mb3JtYXNpMRowGAYD
VQQDDBFkZW1vLm11aGFyZGluLmNvbTEgMB4GCSqGSIb3DQEJARYRZW5keUBtdWhh
cmRpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/CYCB6xU9
j6k0JwB4TwF2hI+H41fH1fG1J/ZZPO3tXaJuibrXOnJNMAXD+GkSv/fOA45uIcX4
6vvULXNz/fS8z3+RkVdrQ/BH2MBMeHjlU3duPYhv6c3yUVmZr7IsViBhTswT1VG9
ZAq57lmjc/DTI/8PjGFvA64pzeWce3yTAmu30zwnDQ22m5gGOfPMfsf2TTW3B1eS
Nv5WmlAIr6DtB4KxeCMXoKnIzBTDpqkeEf+MuIsqCw4/eXnWgBHZH66VIAy/9ygk
hvX2dQNaMAZoExOfAEWVYtzgW84t0j+CwSgxQuk/nvjknE5TGXOrhjZg8mn0eXZK
mmReIsYwjc09AgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEAlbGTK4M2/wCHjg9p
aveomNfU/9qvcK7YTBNNET/ryZGcDCWmG8td01BeSeJUHhqjdxFWHBFsdg+aK5L1
xNd/eKG6NT7obviVHZGPVU2xMAIEOjx1xBP3FNbpIkuvapXcUxGBFlKy5aEmX2oo
/XYyu582J69t9HeDxVBKpdHoLEfwnvTGytka4GRwDZBwiDHTWYlKeBONUUCqO1/L
cd6GqFek37YPoP8ZfMIwTMDGrnCpcK0Z/nhkOZKAgfLtbHgHxvDG1ZUiul1hCLiY
U+us6wgir1kbWXN9Jg6IFlN3ZeLQ4ahDSkIvmLO34ujtFwlq870qJArcW57Po5Pl
3/bddQ==
-----END CERTIFICATE REQUEST-----
</code></pre></div></div>
<p>File CSR ini akan dikirim pemilik domain ke CA, biasanya melalui email.</p>
<p>Selesailah bagian pemilik domain. Selanjutnya kita akan berpura-pura menjadi CA.</p>
<h2 id="ca-menyetujui-csr-dari-pemilik-domain">CA menyetujui CSR dari pemilik domain</h2>
<h3 id="kelengkapan-ca">Kelengkapan CA</h3>
<p>Sebelum bisa menyetujui, kita sediakan dulu kelengkapan CA, yaitu:</p>
<ul>
<li>private key CA</li>
<li>sertifikat CA</li>
</ul>
<h3 id="private-key-ca">Private Key CA</h3>
<p>Private key CA dibuat dengan perintah yang sama seperti pembuatan private key domain di atas.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl genrsa -aes256 2048 > ca.key
</code></pre></div></div>
<p>Seperti sebelumnya, kita juga akan dimintai passphrase.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Generating RSA private key, 2048 bit long modulus
..+++
.............................................................+++
e is 65537 (0x10001)
Enter pass phrase:
Verifying - Enter pass phrase:
</code></pre></div></div>
<p>Saya gunakan <code class="language-plaintext highlighter-rouge">testca1234</code> sebagai passphrasenya. Hasilnya adalah file <code class="language-plaintext highlighter-rouge">ca.key</code> berisi text sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,B7E9F6A263310662657F6B7052CE5CA0
7JX037X9M4Eqsr+BdM/27pj6ADtJIGAYQpHAFoZawhrmArW0PScnjbh/B1R06z3K
ej4HewKQB+SqCznHhmFpJmAuBpb0+snMZuoI3lm1zcRFUs8OSsLLd1B1w4USJlYN
K9mzLqQMlZrl56W9c4QthOegjkqE7RHleierWW3X2VqtWD29GISE8yi9mSb/hSaN
iATuMUaO8etM21F1MZownxKeetfBHJF2Y0XNt9AamrL8+gvb/dE2pPn8Zi2tlR3O
eoUX/FKaa0aV+f6e3X8IRivFp01mnT5UFm1OyDhDrQ1OL0tNzIlLMLqlk8o9njJ0
bBTlDAp5yZFbxtb/GZ94S8x9Nqql5ymVGFTC3BfE0TAfWMIH8YRYEVqMK8YLbGyg
M0vkDpV6impYgaTzJdICVdvOzb7rU9TnvkWsMrp3SRpSqoCtst6yDD59kXBgbPBq
Mj9QeCSXh8jf3LkKq2evSpJ6GlbCzR5EXeWCFcevsHY2xN//jA8v5UPtBOtojYu2
/uyAipnwctj+FEXki1BJwNrPuygzxibImJF3K4u83BkA2bBwIionufr71fNyUNai
2ToKPmxEgOFWGqypSsAw/GUw0LVseg4OlD+Ci2PCYPSwzcvkwulCklXOv8oIuXPb
hsoTVin67c5diok6ayqgyZuMLVIJKr2CAK+Gm77EO652SaIGJtdx5+DBs4BP/XF/
flgwG6DFpft2q93qaYqLPv2LqXAzsP6ACXBRjbPLUMkjv8Sg4iijy1siCxI9i9+N
harFVLRSagFy0dXDw/Wur42xsypcTCMY9kujszx0P4+1BO+vkdNCpSWWofgcIFhI
nZwLpHm9YZ5Sb7GxigBXh7dXc4Q+y2X8xvTg3Xdf+Cew4LpFktAByunUDjw4hjv4
iy56Rvx7lcxlmpwxXw3ZQTZinkSM7f7wqZjvH4E+wt5ShlPrFUGl3d2hmJ2thOTl
hWJ9CYZDDV02UtfYRVqpyY2BCKG3d4YfkUprfGs38/eJMA5zCUi8Uy6eJNu8t4gI
d55PEBltgyMOw8I4M3anbdYwphjhdlQPkbpbsA+AkV+m5dRIjSLxNSG1wXXyVuhI
vMLfrZ3uIKEkn9YXkb59lDx2E8NCmBU7hcV+w7hLu12q8CRCPZZNjnsbNa3Zg4X3
sBN78ak5OOp4thLRSeykpHdPn8sWyyk9RPGaAHv2Y3n4zv5P9JqZe4hA14sAjxj1
Ur+0gLfWoWQwu68F6fsOvngQnMjQneJaSHjhFdGr+HXdzOFqP9u6nxb9LpeFMTX9
i6Ut2IVvTL55LN5TT/U0mcvQo0C/k+31ABNTrsWhv3mkoj92RzWBGoj+1bM1NbeA
foSmmX674LnSUQvqlmsSOtDG5PCVBz+J5C+cmcGp98QFX5ViC2hVi3R+ItPbIirS
7NjJzXZ/f3yz8TOr5MU+I4aYtYNDMvzU8dArTHE8fU++dBTCj2F0KvpIGZlRpHf8
D6AA0a/9VNpwUyILeWD8rjdDiIvx3xPIODMZaN5SJFFAtbDKy9AhU1JCsNAjYdLz
crBRgAwXpcGHOTeDZr+nevcSo8sm7gsqyXcJ6c8r8urnOAKWhvptxGzy6LG8XqPQ
-----END RSA PRIVATE KEY-----
</code></pre></div></div>
<h3 id="sertifikat-ca">Sertifikat CA</h3>
<p>Selanjutnya kita buat sertifikat berisi informasi tentang CA palsu kita ini. Sertifikat ini yang akan kita gunakan untuk menyetujui CSR dari pemilik domain. Berikut perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl req -new -x509 -days 3650 -key ca.key > ca.crt
</code></pre></div></div>
<p>Perintah di atas akan membuat sertifikat dengan <a href="http://en.wikipedia.org/wiki/X.509#Structure_of_a_certificate">format X-509</a>, berlaku selama 10 tahun (3650 hari), menggunakan private key dalam file <code class="language-plaintext highlighter-rouge">ca.key</code>. Sertifikat ini akan ditulis ke file <code class="language-plaintext highlighter-rouge">ca.crt</code>. Kita juga akan ditanyai beberapa informasi tentang CA palsu yang kita buat ini. Berikut output dari perintah di atas</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter pass phrase for ca.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:ID
State or Province Name (full name) [Some-State]:DKI Jakarta
Locality Name (eg, city) []:Jakarta Timur
Organization Name (eg, company) [Internet Widgits Pty Ltd]:CA Palsu
Organizational Unit Name (eg, section) []:Divisi Sertifikasi
Common Name (e.g. server FQDN or YOUR name) []:ROOT CA Palsu
Email Address []:
</code></pre></div></div>
<p>Berikut adalah isi file <code class="language-plaintext highlighter-rouge">ca.crt</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-----BEGIN CERTIFICATE-----
MIIEKzCCAxOgAwIBAgIJAKHgEOaDGy3mMA0GCSqGSIb3DQEBBQUAMIGrMQswCQYD
VQQGEwJJRDEUMBIGA1UECAwLREtJIEpha2FydGExFjAUBgNVBAcMDUpha2FydGEg
VGltdXIxETAPBgNVBAoMCENBIFBhbHN1MRswGQYDVQQLDBJEaXZpc2kgU2VydGlm
aWthc2kxFjAUBgNVBAMMDVJPT1QgQ0EgUGFsc3UxJjAkBgkqhkiG9w0BCQEWF2Vu
ZHkubXVoYXJkaW5AZ21haWwuY29tMB4XDTEzMDcwOTA0MDg1MloXDTIzMDcwNzA0
MDg1MlowgasxCzAJBgNVBAYTAklEMRQwEgYDVQQIDAtES0kgSmFrYXJ0YTEWMBQG
A1UEBwwNSmFrYXJ0YSBUaW11cjERMA8GA1UECgwIQ0EgUGFsc3UxGzAZBgNVBAsM
EkRpdmlzaSBTZXJ0aWZpa2FzaTEWMBQGA1UEAwwNUk9PVCBDQSBQYWxzdTEmMCQG
CSqGSIb3DQEJARYXZW5keS5tdWhhcmRpbkBnbWFpbC5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDda7cS/4qag6APajdqT7krxQOZZkYaCwzafZZN
P9ANwIfUvwB2DOMVLFDFf8DM/Y/hcm8lQAamTNnQ+b58X8oOqOwKX2Y6uPUau+n3
fCY4oTFhdYsbJHOW/hmTZXk+Exg3DHP+aFG7frUn39f8IuIGqujHIuWZfdokQFg8
AaheYdvigtIv3FBqxSCbNIYhmhkhZnrQvJbfbD92n1cHB91eYT9Mj2Z7tFt0ykVV
iQrOQ1n+CmMFUGheSD8Rnsi2ApsnBAsAj6ZruH1S+ZJJW+irZnbQE5LAsgUqjJYf
IFYMoVphTTEMVfoizj2JvspzinufX11V6lylPl0P7EhQvkNJAgMBAAGjUDBOMB0G
A1UdDgQWBBQ0c0rLkgOxJHxuBYyd3xeUVMHzxzAfBgNVHSMEGDAWgBQ0c0rLkgOx
JHxuBYyd3xeUVMHzxzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBQ
W4t7WMj0FrVcxi/+W/jlAJ2BKGYev68bJiEeLVzbDHN3CQhfQwMWD0L0v6icP3c8
SLeGR+PEcq7FwGHb6gpIySVlnl8bxzRFK2lqfo+672djwtP0F/mrDKTDSzoIkPOq
G53otMXIAoplWDU7SRZE489AWfwOCCSmUJke9sEh1YYQwRVU7ceWSZLbRgfmzLeY
xjIfF/KBZrs8CMbYCEqHHHX5XAtIEP+3KSAbzfoxv/a07+FE+5/r5E6aXEBSbVme
y79zpM9VIV8FLlZr/+TITXqLKOBMmCmTVM1h1LJ8IjmWx9L6v631DX3/tc67HMoa
fauYxPo71IRYfIAN4RQ5
-----END CERTIFICATE-----
</code></pre></div></div>
<p>Informasi di dalam sertifikat tersebut bisa kita lihat dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl x509 -noout -text -in ca.crt
</code></pre></div></div>
<p>Ini adalah hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Certificate:
Data:
Version: 3 (0x2)
Serial Number: 11664341617117703654 (0xa1e010e6831b2de6)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=ID, ST=DKI Jakarta, L=Jakarta Timur, O=CA Palsu,
OU=Divisi Sertifikasi,
CN=ROOT CA Palsu
Validity
Not Before: Jul 9 04:08:52 2013 GMT
Not After : Jul 7 04:08:52 2023 GMT
Subject: C=ID, ST=DKI Jakarta, L=Jakarta Timur, O=CA Palsu,
OU=Divisi Sertifikasi,
CN=ROOT CA Palsu
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:dd:6b:b7:12:ff:8a:9a:83:a0:0f:6a:37:6a:4f:
b9:2b:c5:03:99:66:46:1a:0b:0c:da:7d:96:4d:3f:
d0:0d:c0:87:d4:bf:00:76:0c:e3:15:2c:50:c5:7f:
c0:cc:fd:8f:e1:72:6f:25:40:06:a6:4c:d9:d0:f9:
be:7c:5f:ca:0e:a8:ec:0a:5f:66:3a:b8:f5:1a:bb:
e9:f7:7c:26:38:a1:31:61:75:8b:1b:24:73:96:fe:
19:93:65:79:3e:13:18:37:0c:73:fe:68:51:bb:7e:
b5:27:df:d7:fc:22:e2:06:aa:e8:c7:22:e5:99:7d:
da:24:40:58:3c:01:a8:5e:61:db:e2:82:d2:2f:dc:
50:6a:c5:20:9b:34:86:21:9a:19:21:66:7a:d0:bc:
96:df:6c:3f:76:9f:57:07:07:dd:5e:61:3f:4c:8f:
66:7b:b4:5b:74:ca:45:55:89:0a:ce:43:59:fe:0a:
63:05:50:68:5e:48:3f:11:9e:c8:b6:02:9b:27:04:
0b:00:8f:a6:6b:b8:7d:52:f9:92:49:5b:e8:ab:66:
76:d0:13:92:c0:b2:05:2a:8c:96:1f:20:56:0c:a1:
5a:61:4d:31:0c:55:fa:22:ce:3d:89:be:ca:73:8a:
7b:9f:5f:5d:55:ea:5c:a5:3e:5d:0f:ec:48:50:be:
43:49
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
34:73:4A:CB:92:03:B1:24:7C:6E:05:8C:9D:DF:17:94:54:C1:F3:C7
X509v3 Authority Key Identifier:
keyid:34:73:4A:CB:92:03:B1:24:7C:6E:05:8C:9D:DF:17:94:54:C1:F3:C7
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
50:5b:8b:7b:58:c8:f4:16:b5:5c:c6:2f:fe:5b:f8:e5:00:9d:
81:28:66:1e:bf:af:1b:26:21:1e:2d:5c:db:0c:73:77:09:08:
5f:43:03:16:0f:42:f4:bf:a8:9c:3f:77:3c:48:b7:86:47:e3:
c4:72:ae:c5:c0:61:db:ea:0a:48:c9:25:65:9e:5f:1b:c7:34:
45:2b:69:6a:7e:8f:ba:ef:67:63:c2:d3:f4:17:f9:ab:0c:a4:
c3:4b:3a:08:90:f3:aa:1b:9d:e8:b4:c5:c8:02:8a:65:58:35:
3b:49:16:44:e3:cf:40:59:fc:0e:08:24:a6:50:99:1e:f6:c1:
21:d5:86:10:c1:15:54:ed:c7:96:49:92:db:46:07:e6:cc:b7:
98:c6:32:1f:17:f2:81:66:bb:3c:08:c6:d8:08:4a:87:1c:75:
f9:5c:0b:48:10:ff:b7:29:20:1b:cd:fa:31:bf:f6:b4:ef:e1:
44:fb:9f:eb:e4:4e:9a:5c:40:52:6d:59:9e:cb:bf:73:a4:cf:
55:21:5f:05:2e:56:6b:ff:e4:c8:4d:7a:8b:28:e0:4c:98:29:
93:54:cd:61:d4:b2:7c:22:39:96:c7:d2:fa:bf:ad:f5:0d:7d:
ff:b5:ce:bb:1c:ca:1a:7d:ab:98:c4:fa:3b:d4:84:58:7c:80:
0d:e1:14:39
</code></pre></div></div>
<h3 id="konfigurasi-ca">Konfigurasi CA</h3>
<p>CA yang asli biasanya memiliki banyak private key dan certificate turunan.
Dari private key dan certificate yang utama (Root), dia akan membuat beberapa private key dan certificate lain.
Ini karena CA yang besar biasanya memiliki banyak reseller untuk menjualkan sertifikat SSL.
Untuk membatasi resiko dan juga memudahkan pendataan, request dari pelanggan masing-masing reseller disetujui dengan certificate turunan yang diperuntukkan khusus untuk reseller tersebut. Dengan demikian, bila terjadi masalah dengan reseller tersebut,
cukup certificate turunan yang khusus untuk reseller tersebut yang dibatalkan. Certificate turunan ini disebut dengan istilah <code class="language-plaintext highlighter-rouge">intermediate CA certificate</code>.</p>
<p>Pada waktu pembuatan signature terhadap CSR, ada banyak informasi yang dibutuhkan. Apalagi kalau suatu CA memiliki banyak intermediate certificate, masing-masing intermediate certificate tersebut berbeda informasi <code class="language-plaintext highlighter-rouge">issuer</code> nya. Contohnya, suatu CA memiliki lini produk seperti ini:</p>
<ul>
<li>Gratis : hanya berlaku 30 hari</li>
<li>Standar : berlaku 1 tahun, untuk satu domain</li>
<li>Enterprise : berlaku 1 tahun, multi domain</li>
<li>dan sebagainya</li>
</ul>
<p>Agar berbagai kategori di atas mudah dikelola, kita akan membuat file konfigurasi. Kita beri nama <code class="language-plaintext highlighter-rouge">openssl-config.txt</code> dan berisi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ca]
default_ca = ca-palsu
[ca-palsu]
dir = ca-palsu
# sertifikat dan private key CA
certificate = $dir/ca.crt
private_key = $dir/ca.key
# Folder penyimpanan
cert = $dir/sertifikat-customer
new_certs_dir = $dir/sertifikat-customer
crl = $dir/sertifikat-batal
# Database sertifikat yang sudah dikeluarkan
database = $dir/database.txt
serial = $dir/serial.txt
# Nilai default untuk sertifikat baru
default_days = 365 # masa berlaku sertifikat customer
default_crl_days = 30 # masa berlaku daftar pembatalan sertifikat
default_md = sha1
x509_extensions = usr_cert
# Konfigurasi tambahan
policy = policy-saya
x509_extensions = certificate_extensions
[ policy-saya ]
# Gunakan informasi dalam CSR
commonName = supplied
stateOrProvinceName = supplied
countryName = supplied
emailAddress = supplied
organizationName = supplied
organizationalUnitName = optional
[ certificate_extensions ]
# Sertifikat customer tidak boleh jadi CA
basicConstraints = CA:false
[ req ]
# private key yang digunakan untuk menyetujui CSR
default_keyfile = ca-palsu/ca.key
</code></pre></div></div>
<p>Konfigurasi di atas berisi satu CA (tanpa intermediate certificate). Bila nantinya kita mau membuat beberapa CA, baik sebagai Root CA maupun Intermediate CA, kita tinggal copy-paste konfigurasi untuk <code class="language-plaintext highlighter-rouge">ca-palsu</code>.</p>
<p>Root CA kita <code class="language-plaintext highlighter-rouge">ca-palsu</code> kita buatkan folder tersendiri untuk menyimpan file-filenya. Kita masukkan private key dan certificate dirinya sendiri. Kemudian, kita juga membutuhkan file database dan serial. File database gunanya untuk menyimpan data semua sertifikat yang pernah diterbitkan oleh CA <code class="language-plaintext highlighter-rouge">ca-palsu</code>. Karena database ini merupakan file text biasa, bukan database server seperti MySQL atau Oracle, maka kita perlu bantu <code class="language-plaintext highlighter-rouge">openssl</code> untuk membuatkan serial number dengan cara menyediakan file text lain bernama <code class="language-plaintext highlighter-rouge">serial.txt</code>. Di sini <code class="language-plaintext highlighter-rouge">openssl</code> bisa mencatat nomer terakhir yang sudah terpakai. Untuk data awal, kita isi saja nilai <code class="language-plaintext highlighter-rouge">1000</code> dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo 1000 > ca-palsu/serial.txt
</code></pre></div></div>
<p>Kita buatkan juga file <code class="language-plaintext highlighter-rouge">database.txt</code>, karena <code class="language-plaintext highlighter-rouge">openssl</code> tidak bisa membuat sendiri. Cukup gunakan perintah <code class="language-plaintext highlighter-rouge">touch</code> di Linux.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>touch ca-palsu/database.txt
touch ca-palsu/database.txt.attr
</code></pre></div></div>
<p>Bila sulit dibayangkan, langsung saja lihat <a href="https://github.com/endymuhardin/belajar-ssl/tree/10c1f7302226b07ac31505dc24e8fdf6091dfee9/ca">struktur foldernya di Github</a>. Di Github kita juga bisa lihat versi awal dari <a href="https://github.com/endymuhardin/belajar-ssl/blob/10c1f7302226b07ac31505dc24e8fdf6091dfee9/ca/ca-palsu/serial.txt">serial.txt</a>, <a href="https://github.com/endymuhardin/belajar-ssl/blob/10c1f7302226b07ac31505dc24e8fdf6091dfee9/ca/ca-palsu/database.txt">database.txt</a>, dan <a href="https://github.com/endymuhardin/belajar-ssl/blob/10c1f7302226b07ac31505dc24e8fdf6091dfee9/ca/ca-palsu/database.txt.attr">database.txt.attr</a>.</p>
<p>Jangan heran bila <code class="language-plaintext highlighter-rouge">database.txt</code> dan <code class="language-plaintext highlighter-rouge">database.txt.attr</code> tidak ada isinya. Memang harusnya seperti itu.</p>
<p>Sekarang kita siap membuatkan signature untuk CSR dari <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code></p>
<h3 id="membuat-signature-csr-dengan-sertifikat-ca">Membuat signature CSR dengan Sertifikat CA</h3>
<p>CA menerima CSR dari pemilik domain. Untuk simulasinya, kita copy saja file CSR ke folder <code class="language-plaintext highlighter-rouge">ca-palsu/sertifikat-request</code>.
Setelah diterima, seharusnya CA melakukan verifikasi terhadap pemilik domain <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code>. Prosedur verifikasi yang asli akan kita bahas di artikel berikutnya. Kita asumsikan saja verifikasi berjalan sukses, sehingga kita bisa langsung membuatkan signaturenya.</p>
<p>Berikut adalah perintah untuk membuatkan signature dengan menggunakan CA <code class="language-plaintext highlighter-rouge">ca-palsu</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl ca -config openssl-config.txt -in ca-palsu/sertifikat-request/demo.muhardin.com.csr
</code></pre></div></div>
<p>OpenSSL akan menanyakan password untuk private key <code class="language-plaintext highlighter-rouge">ca-palsu</code>, kemudian membuatkan sertifikat baru sesuai konfigurasi <code class="language-plaintext highlighter-rouge">new_certs_dir</code> dalam <code class="language-plaintext highlighter-rouge">openssl-config.txt</code>. Berikut output perintah di atas.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Using configuration from openssl-config.txt
Enter pass phrase for ca-palsu/ca.key:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName :PRINTABLE:'ID'
stateOrProvinceName :ASN.1 12:'DKI Jakarta'
localityName :ASN.1 12:'Jakarta Timur'
organizationName :ASN.1 12:'PT. Endy Muhardin'
organizationalUnitName:ASN.1 12:'Divisi Teknologi Informasi'
commonName :ASN.1 12:'demo.muhardin.com'
emailAddress :IA5STRING:'endy@muhardin.com'
Certificate is to be certified until Jul 9 05:48:04 2014 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4096 (0x1000)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=ID, ST=DKI Jakarta, L=Jakarta Timur,
O=CA Palsu, OU=Divisi Sertifikasi, CN=ROOT CA Palsu
Validity
Not Before: Jul 9 05:48:04 2013 GMT
Not After : Jul 9 05:48:04 2014 GMT
Subject: CN=demo.muhardin.com, ST=DKI Jakarta,
C=ID/emailAddress=endy@muhardin.com,
O=PT. Endy Muhardin, OU=Divisi Teknologi Informasi
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:bf:09:80:81:eb:15:3d:8f:a9:34:27:00:78:4f:
01:76:84:8f:87:e3:57:c7:d5:f1:b5:27:f6:59:3c:
ed:ed:5d:a2:6e:89:ba:d7:3a:72:4d:30:05:c3:f8:
69:12:bf:f7:ce:03:8e:6e:21:c5:f8:ea:fb:d4:2d:
73:73:fd:f4:bc:cf:7f:91:91:57:6b:43:f0:47:d8:
c0:4c:78:78:e5:53:77:6e:3d:88:6f:e9:cd:f2:51:
59:99:af:b2:2c:56:20:61:4e:cc:13:d5:51:bd:64:
0a:b9:ee:59:a3:73:f0:d3:23:ff:0f:8c:61:6f:03:
ae:29:cd:e5:9c:7b:7c:93:02:6b:b7:d3:3c:27:0d:
0d:b6:9b:98:06:39:f3:cc:7e:c7:f6:4d:35:b7:07:
57:92:36:fe:56:9a:50:08:af:a0:ed:07:82:b1:78:
23:17:a0:a9:c8:cc:14:c3:a6:a9:1e:11:ff:8c:b8:
8b:2a:0b:0e:3f:79:79:d6:80:11:d9:1f:ae:95:20:
0c:bf:f7:28:24:86:f5:f6:75:03:5a:30:06:68:13:
13:9f:00:45:95:62:dc:e0:5b:ce:2d:d2:3f:82:c1:
28:31:42:e9:3f:9e:f8:e4:9c:4e:53:19:73:ab:86:
36:60:f2:69:f4:79:76:4a:9a:64:5e:22:c6:30:8d:
cd:3d
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Signature Algorithm: sha1WithRSAEncryption
2e:1e:b9:4b:e4:ad:74:34:1c:7c:46:9c:d5:d8:83:db:da:b7:
b9:a0:ad:c5:54:f7:f2:51:c1:8f:77:70:ae:b4:a1:f6:9d:40:
ec:d5:55:76:0d:d0:d3:1b:36:ca:8b:bb:64:52:a8:ca:95:18:
5d:d1:28:47:e9:46:89:72:16:8e:57:b9:97:ac:93:c3:5d:6e:
17:ac:b3:91:87:61:26:a7:c6:6e:35:d9:03:92:cb:06:08:e5:
93:83:89:fb:44:6b:b9:78:b5:b7:e5:d4:84:47:3e:0e:26:40:
ec:4e:7a:6f:c1:76:75:b6:6c:f2:ef:c5:97:46:8e:d5:1b:1a:
67:95:1e:3e:d7:56:91:43:6d:a2:7a:e7:a4:bd:29:f0:1a:67:
62:d0:83:35:45:96:19:1f:c8:ee:cf:27:3e:f7:9b:11:cc:b3:
a8:dd:e6:b4:8a:85:c2:69:36:4f:0c:c3:50:f4:3a:30:e0:3a:
f6:6a:2b:72:4e:a3:44:0f:af:73:06:8e:fd:20:d3:4b:99:f5:
8b:72:f8:f6:21:3c:f4:03:ce:4d:bc:b2:e1:11:29:0d:3d:80:
23:41:72:d1:fb:28:f0:32:cf:49:56:5d:cc:e3:a7:dc:88:5c:
61:8f:36:9b:bc:28:df:81:89:4a:e6:7f:06:7e:45:9f:19:f8:
83:ca:1b:df
-----BEGIN CERTIFICATE-----
MIIDsDCCApigAwIBAgICEAAwDQYJKoZIhvcNAQEFBQAwgYMxCzAJBgNVBAYTAklE
MRQwEgYDVQQIDAtES0kgSmFrYXJ0YTEWMBQGA1UEBwwNSmFrYXJ0YSBUaW11cjER
MA8GA1UECgwIQ0EgUGFsc3UxGzAZBgNVBAsMEkRpdmlzaSBTZXJ0aWZpa2FzaTEW
MBQGA1UEAwwNUk9PVCBDQSBQYWxzdTAeFw0xMzA3MDkwNTQ4MDRaFw0xNDA3MDkw
NTQ4MDRaMIGiMRowGAYDVQQDDBFkZW1vLm11aGFyZGluLmNvbTEUMBIGA1UECAwL
REtJIEpha2FydGExCzAJBgNVBAYTAklEMSAwHgYJKoZIhvcNAQkBFhFlbmR5QG11
aGFyZGluLmNvbTEaMBgGA1UECgwRUFQuIEVuZHkgTXVoYXJkaW4xIzAhBgNVBAsM
GkRpdmlzaSBUZWtub2xvZ2kgSW5mb3JtYXNpMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAvwmAgesVPY+pNCcAeE8BdoSPh+NXx9XxtSf2WTzt7V2ibom6
1zpyTTAFw/hpEr/3zgOObiHF+Or71C1zc/30vM9/kZFXa0PwR9jATHh45VN3bj2I
b+nN8lFZma+yLFYgYU7ME9VRvWQKue5Zo3Pw0yP/D4xhbwOuKc3lnHt8kwJrt9M8
Jw0NtpuYBjnzzH7H9k01twdXkjb+VppQCK+g7QeCsXgjF6CpyMwUw6apHhH/jLiL
KgsOP3l51oAR2R+ulSAMv/coJIb19nUDWjAGaBMTnwBFlWLc4FvOLdI/gsEoMULp
P5745JxOUxlzq4Y2YPJp9Hl2SppkXiLGMI3NPQIDAQABow0wCzAJBgNVHRMEAjAA
MA0GCSqGSIb3DQEBBQUAA4IBAQAuHrlL5K10NBx8RpzV2IPb2re5oK3FVPfyUcGP
d3CutKH2nUDs1VV2DdDTGzbKi7tkUqjKlRhd0ShH6UaJchaOV7mXrJPDXW4XrLOR
h2Emp8ZuNdkDkssGCOWTg4n7RGu5eLW35dSERz4OJkDsTnpvwXZ1tmzy78WXRo7V
GxpnlR4+11aRQ22ieuekvSnwGmdi0IM1RZYZH8juzyc+95sRzLOo3ea0ioXCaTZP
DMNQ9Dow4Dr2aityTqNED69zBo79INNLmfWLcvj2ITz0A85NvLLhESkNPYAjQXLR
+yjwMs9JVl3M46fciFxhjzabvCjfgYlK5n8GfkWfGfiDyhvf
-----END CERTIFICATE-----
Data Base Updated
</code></pre></div></div>
<p>Setelah sukses, <code class="language-plaintext highlighter-rouge">openssl</code> akan membuatkan sertifikat yang sudah ditandatangani menggunakan private key <code class="language-plaintext highlighter-rouge">ca-palsu</code>. Berikut daftar file/folder yang terdampak selama proses ini:</p>
<ul>
<li>serial.txt : tadinya berisi 1000, sekarang berisi 1001</li>
<li>database.txt : tadinya kosong, sekarang ada satu baris berisi informasi sertifikat yang baru saja dibuat</li>
<li>folder sertifikat-customer : tadinya kosong, sekarang berisi satu file sertifikat untuk <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code> dengan nama file <code class="language-plaintext highlighter-rouge">1000.pem</code></li>
</ul>
<p>Daftar perubahan ini bisa <a href="https://github.com/endymuhardin/belajar-ssl/commit/f950bdfb866be9da7a80e613b7d95bf282454b05">dilihat di Github</a>.</p>
<p>Selanjutnya, CA akan mengirimkan file <code class="language-plaintext highlighter-rouge">1000.pem</code> tersebut ke pemilik domain <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code>.</p>
<h2 id="kelengkapan-file-pemilik-domain">Kelengkapan File Pemilik Domain</h2>
<p>Setelah menerima file <code class="language-plaintext highlighter-rouge">100.pem</code> dari CA, pemilik domain <code class="language-plaintext highlighter-rouge">demo.muhardin.com</code> akan memiliki file berikut:</p>
<ul>
<li>File private key : <code class="language-plaintext highlighter-rouge">demo.muhardin.com.key</code></li>
<li>File CSR : <code class="language-plaintext highlighter-rouge">demo.muhardin.com.csr</code>. File ini <a href="http://security.stackexchange.com/questions/32857/could-i-delete-csr-files-when-the-key-is-signed-by-ca">boleh dihapus setelah kita menerima sertifikat yang telah disahkan oleh CA</a>.</li>
<li>File sertifikat dari CA : <code class="language-plaintext highlighter-rouge">1000.pem</code>. Agar lebih jelas, kita rename saja menjadi <code class="language-plaintext highlighter-rouge">demo.muhardin.com.crt</code>.</li>
</ul>
<p>Demikianlah langkah-langkah membuat self-signed certificate. Pada artikel berikutnya, kita akan membahas bagaimana <a href="http://software.endy.muhardin.com/aplikasi/membeli-sertifikat-ssl/">cara membeli sertifikat</a>. Kemudian memasang sertifikat (baik yang self-signed maupun yang berbayar) ke webserver kita.</p>
Apa itu SSL2013-07-08T17:52:00+07:00https://software.endy.muhardin.com/aplikasi/apa-itu-ssl<p>SSL adalah protokol keamanan yang kita gunakan sehari-hari. Sebagai programmer, kita harus paham konsep SSL dan bisa memanfaatkannya dalam aplikasi yang kita buat.</p>
<p>Artikel ini adalah bagian pertama dari 4 artikel, yaitu:</p>
<ol>
<li><a href="http://software.endy.muhardin.com/aplikasi/apa-itu-ssl/">Apa itu SSL</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/membuat-self-signed-certificate/">Membuat self-signed certificate</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/membeli-sertifikat-ssl/">Membeli sertifikat SSL</a></li>
<li><a href="http://software.endy.muhardin.com/aplikasi/memasang-sertifikat-ssl/">Memasang sertifikat SSL</a></li>
</ol>
<!--more-->
<p><a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">SSL adalah singkatan dari Secure Socket Layer, juga dikenal dengan istilah Transport Secure Layer (TLS)</a>.</p>
<p>Ada beberapa keuntungan bila kita menggunakan SSL, yaitu:</p>
<ul>
<li>kerahasiaan (confidentiality)</li>
<li>identifikasi (authentication)</li>
</ul>
<p>Kerahasiaan diperlukan bila kita mengirim informasi yang tidak boleh diketahui orang lain, misalnya username dan password pada saat mengisi halaman login. Kita tidak ingin ada orang lain yang menyadap trafik internet kita dan mengintip password kita (<a href="http://en.wikipedia.org/wiki/Eavesdropping">eavesdropping</a>).</p>
<p>Identifikasi diperlukan untuk memastikan identitas suatu layanan/aplikasi. Misalnya kita mengakses situs internet banking, kita butuh keyakinan bahwa yang kita akses adalah aplikasi yang asli. Tidak dimanipulasi orang di tengah jalan (<a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack">man in the middle attack</a>) ataupun diganti dengan situs palsu (<a href="https://en.wikipedia.org/wiki/Phishing">phising</a>).</p>
<p>Untuk bisa memahami apa itu SSL, ada beberapa konsep yang perlu kita pahami yaitu:</p>
<ul>
<li>identitas</li>
<li>trust model</li>
<li>public key infrastructure</li>
<li>dasar-dasar enkripsi</li>
</ul>
<p>Dengan memahami beberapa istilah di atas, nanti kita akan memahami dari mana datangnya fitur SSL seperti kerahasiaan dan identifikasi.</p>
<h2 id="identitas">Identitas</h2>
<p>Coba kunjungi website internet banking seperti <a href="https://ibank.klikbca.com/">KlikBCA</a> dan <a href="https://ib.bankmandiri.co.id/retail/Login.do?action=form&lang=in_ID">Bank Mandiri</a>. Bagaimana kita memastikan bahwa kedua situs barusan asli dikelola oleh bank masing-masing? Bila Anda klik link di atas, bagaimana Anda yakin bahwa saya tidak mengarahkan Anda ke situs saya sendiri yang mirip dengan situs aslinya?</p>
<p>Di kehidupan sehari-hari, kita biasa memastikan identitas seseorang dengan beberapa cara, misalnya:</p>
<ul>
<li>menyuruh orang tersebut menulis tandatangan, kemudian dibandingkan dengan KTP, SIM, atau bukti identitas lain.</li>
<li>mengambil sidik jarinya (pakai tinta ataupun fingerprint scanner), dan dibandingkan dengan data yang sudah kita miliki sebelumnya.</li>
<li>membandingkan sidik mata (iris) dengan database.</li>
</ul>
<p>Manapun cara yang kita gunakan, ada satu kesamaan. Kita akan memeriksa informasi/benda yang hanya bisa dimiliki pemegang aslinya. Tentu semua hal bisa dipalsukan, tapi untuk menyederhanakan masalah, kita bisa asumsikan bahwa tandatangan, sidik jari, dan sidik mata hanya bisa dimiliki oleh orang yang asli.</p>
<p>Website yang kita akses ditaruh di server. Tentunya server tidak memiliki anggota tubuh yang bisa dipastikan keunikannya. Untuk itu, kita membuat padanan sidik jari, yaitu suatu file yang disebut <code class="language-plaintext highlighter-rouge">private key</code>. File ini bisa kita buat di server itu sendiri, ataupun bisa kita buat di laptop dan kemudian dipasang (copy-paste/upload) ke server tersebut. Private key ini harus kita lindungi dengan baik. Karena bila orang lain sampai bisa mendapatkannya, sama saja dengan kita menyerahkan jari kita ke orang lain ataupun mengajari orang membuat tanda tangan kita.</p>
<p>Private key bentuknya adalah file text biasa yang isinya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,1CB1AC44166F92FA3817B5E95EDBF1E5
rhH0ueRW1uxTa2w2wY1r9bkm9eyFjDzXLAHMGoMpDyCCYiYQ4/BmFYx+68ElsYPO
+tV2iRz4OXthU2SXK+StBZYtE27BSUMbrR6NXNkZFCs9sEYGsJzoiyT/caHYr4p3
8gI+SJQc215oJdltpS8jML8Ygg3FCfOQoxnR9Ior0W3EKQtUbzsnkHLGH9s06Y8B
RyHb9hKB5YCiWBFo3lH4wfvSxb5A68s0hH0enGu6F/216TLis733E6PEgnhjwUvp
FTuKzie+UXWGRjNwPNUIYh74hmS3vfaj4c0tKDzrNcuO7tZmMGEEOJICyBjEMmDO
0PAUe+Cy0KqkckYfJCS8cN1lAgWuR1NrqaCezt87qVEbr3Om1H6z45ObwGNdnfUi
uncFPqkUrjRYriB6xt6+Lg2iyk2dhbwrU6vMxDG3VPvMiAvXXYBlUBPi4xlpKv28
awuLpaSM4yegiZO2wTjRoHECJJVHSADQ7KVRlCc5fRe+3QyIYRQmYG/u44J+ykDK
Fx7nvTSHy/qDsDYrmacHIJJAdzoxiWIDJ2MedvYdamJ6EGH2Zk+xBOpV+ss4slhq
nStKIUA0qR9Wnh3KicubosKsNo8yp3uzrNRO/f+cQdmItjMLjosOOKhIDNgD3xCv
csGziNQMq+iJ0mt6ntu+B+ETsaVsD4HspkxZpL9JHnqGKvn46DtJuvBeJ9TTQkZo
1doFj/wwk6ljNtibYWYpPFDaP9f7XAtlm6RQ+0Vm/X6pwTUKZA9/5ABqJ9YYIaIg
TvOgX1e5RpCANBKjXvQpuEyHsk24QzG5Q8tMuqBeXALl+I8XDFAVgkNBmeObp5Nw
02lQqb53t5sg4CN3+prtG18c1xU+g7t1mb3so/86ZHrZxY08Ykf+7Y1yO1rOBqkc
fRkhegMVWYzzxtE82FI2gp9cwDRTix1CAwRSMjItQnHua5il5UvigkIQLmZW96kV
Hh/CxZMyevcLMZHUdPrpiuA4fZBh4EA0Z0JXQy69bAe3gnAbw0ky0zsC5RttXGol
cfPCHKCKgvGVMDw6b+f9NhQQKlzjCscA+CbCTvUGPnu4+7KCoo6Z1652t3N9AoRB
wVds4AHqeH3+H6v8bDJ5yQv2aAAFMSnvMyUqkD1Rt960Qgbl8TUb6JHvvWnjbjvF
cA5AvkxiPWCQNRiHRIZHfhwysQv5gizu2CY04cKkn9ynvpajjKa5LVQr68kR57sH
Q2ymJkKlQw4wXCTYBrfVsNvGG5XZBrfz8brt8rHHgAKdBD8jordICwEqZlhBYPct
UQr0DTT0i+lA8AgKDTK9hcSa1G5hOO/ekUKwTnvBMjckWju1P4NF1aTOUg09eYEo
Z6OTR4kGi5Usdc9I8DuTloeb26+o88Meiqu2NX3bOGH50KHEA4zVJxPhMf48dOvk
0JFOUM39n81OIJTVcis6RkyMmTWRR8NUdTdvwlNpuETq6i1iXrVCeDT3tBA+KinN
LcEbheYfk/3aZoivZoFfGYNlD4HSMooauG9eZSdzvGf700u/7qhUB8kZEik9uaXh
QycMFVGSEVFnADP7Eb7H5Z/pZVq5iyG/lKAHh5He2m6dpnu2Q1pO6qdF/KQ7b8kZ
-----END RSA PRIVATE KEY-----
</code></pre></div></div>
<p>Private key dapat digunakan untuk dua hal, signature dan dekripsi. Digital signature bisa diumpamakan dengan kita membubuhkan tandatangan atau stempel di suatu dokumen. Dokumen yang saya stempel dan tandatangani bisa meyakinkan orang bahwa dokumen tersebut memang benar-benar berasal dari saya. Demikian juga dengan dokumen digital (file), dapat kita tandatangani/stempel dengan menggunakan private key. File yang sudah diberikan digital signature tidak bisa diubah ditengah jalan tanpa ketahuan. Bila diubah, maka digital signaturenya tidak cocok lagi.</p>
<p>Private key memiliki pasangan yang dinamakan public key. Public key ini tidak rahasia, dan bisa kita sebarluaskan pada siapapun yang membutuhkannya. Bila orang lain ingin mengirim pesan rahasia pada kita, dia bisa mengacak (enkripsi) pesan tersebut dengan menggunakan public key. Pesan yang sudah diacak tersebut hanya bisa dibuka dengan private key yang sesuai.</p>
<p>Jadi, untuk memiliki identitas, kita harus memiliki pasangan private dan public key.</p>
<h2 id="trust-model">Trust Model</h2>
<p>Siapa saja bisa membuat pasangan key. Lalu bagaimana cara kita bisa tahu bahwa private key tertentu adalah milik orang tertentu?
Misalnya, Anda mendapat dokumen dari <code class="language-plaintext highlighter-rouge">Endy Muhardin</code> yang ditandatangani dengan private key <code class="language-plaintext highlighter-rouge">abcd-efgh-1234-5678</code>. Bagaimana kita yakin bahwa <code class="language-plaintext highlighter-rouge">abcd-efgh-1234-5678</code> adalah benar tandatangan dari <code class="language-plaintext highlighter-rouge">Endy Muhardin</code>?</p>
<p>Ada dua cara untuk mengatasi masalah ini,</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Web_of_trust">Web of Trust</a></li>
<li><a href="http://en.wikipedia.org/wiki/Public-key_infrastructure">Public Key Infrastructure</a></li>
</ul>
<h3 id="web-of-trust">Web of Trust</h3>
<p>Web of Trust bekerja dengan mekanisme referensi teman. Kita bisa tanyakan pada teman kita yang sudah pernah berinteraksi dengan <code class="language-plaintext highlighter-rouge">Endy Muhardin</code>, “Apakah benar <code class="language-plaintext highlighter-rouge">abcd-efgh-1234-5678</code> adalah tandatangan dari <code class="language-plaintext highlighter-rouge">Endy Muhardin</code> ?”. Bila teman kita tidak ada yang pernah berinteraksi, maka kita bisa tanyakan pada temannya teman, temannya teman dari teman, dan seterusnya sampai ada yang bisa memastikan identitas dari tandatangan <code class="language-plaintext highlighter-rouge">abcd-efgh-1234-5678</code> tersebut.</p>
<p>Kembali ke analogi internet banking, bagaimana kalau saya baru saja launching dan belum memiliki referensi? Nah tentunya membangun jaringan referensi butuh waktu lama. Bisa jadi pada minggu-minggu pertama setelah launching tidak ada yang berani pakai internet banking saya, karena belum ada teman kita yang memiliki referensi.</p>
<p>Untuk itu, kita menggunakan mekanisme kedua, yaitu Public Key Infrastructure.</p>
<h3 id="public-key-infrastructure">Public Key Infrastructure</h3>
<p>Pada sistem ini, kita mempercayakan verifikasi identitas pada sekelompok orang/perusahaan. Kelompok ini dikenal dengan istilah Certificate Authority (CA). CA bertugas memberikan sertifikasi pada identitas digital, mirip dengan pemberian peringkat bintang pada hotel/penginapan.</p>
<p>Dengan mekanisme ini, bila kita ingin menginap di suatu hotel yang belum pernah kita datangi sebelumnya, kita tinggal melihat peringkatnya. Kita yakin bahwa <a href="https://en.wikipedia.org/wiki/Hotel_rating">hotel bintang lima</a> pasti lebih baik daripada bintang empat, dan seterusnya. Keyakinan ini kita dapatkan karena kita percaya dengan prosedur verifikasi yang telah dilakukan oleh lembaga pemeringkat hotel.</p>
<p>Kembali ke aplikasi internet banking kita, untuk mendapatkan sertifikasi dari CA, ada beberapa langkah yang harus dilakukan:</p>
<ol>
<li>Membuat pasangan private dan public key</li>
<li>Membuat sertifikat digital</li>
<li>Mengajukan sertifikat tersebut ke CA untuk disetujui</li>
<li>CA akan melakukan verifikasi terhadap identitas kita. Bila sukses, sertifikat kita akan disetujui oleh CA.</li>
<li>Memasang sertifikat yang telah disetujui CA di <del>pintu hotel</del> website kita</li>
</ol>
<p>Detail teknis untuk melakukan langkah-langkah ini akan kita bahas di artikel berikutnya.</p>
<p>Sistem public key infrastructure ini juga punya kelemahan. Beberapa CA pernah ditembus keamanannya oleh <a href="http://en.wikipedia.org/wiki/Comodo_Group#2011_breach_incident">orang Iran</a> dan <a href="http://en.wikipedia.org/wiki/Diginotar#Issuance_of_fraudulent_certificates">Turki</a> sehingga bisa mengeluarkan sertifikat palsu. Kronologi lengkapnya bisa dibaca <a href="http://blog.gerv.net/2011/09/diginotar-compromise/">di sini</a>.</p>
<p>Walaupun demikian, kita tidak perlu khawatir, karena begitu hal tersebut terungkap, CA yang bersalah akan langsung mendapatkan <a href="http://www.wired.com/threatlevel/2011/09/diginotar-bankruptcy/">hukuman mati dari masyarakat</a>. Dengan demikian, para CA tentu akan berusaha menjaga keamanannya dengan baik.</p>
<h2 id="penggunaan-ssl">Penggunaan SSL</h2>
<p>SSL digunakan pada protokol yang kita gunakan setiap hari, yaitu HTTP, SMTP, IMAP, dan POP3. HTTP kita gunakan untuk aplikasi web, sedangkan tiga sisanya kita gunakan untuk email. Cara kerjanya relatif sama, sebagai berikut:</p>
<ol>
<li>Kita membuka browser dan mengetik alamat website, misalnya <code class="language-plaintext highlighter-rouge">https://mail.google.com</code></li>
<li>Mekanisme DNS akan menerjemahkan alamat <code class="language-plaintext highlighter-rouge">mail.google.com</code> menjadi alamat IP, misalnya <code class="language-plaintext highlighter-rouge">123.123.123.123</code>.</li>
<li>Browser melakukan request ke <code class="language-plaintext highlighter-rouge">123.123.123.123</code>. Karena URLnya https, maka browser meminta sertifikat dari server</li>
<li>Server mengirim sertifikat yang sudah ditandatangani oleh CA.</li>
<li>Browser memiliki daftar CA yang sudah disetujui olehnya. Dengan daftar tersebut, browser melakukan verifikasi keabsahan tandatangan CA.</li>
<li>Bila tandatangan CA valid, berarti CA sudah memastikan bahwa sertifikat tersebut memang benar milik server <code class="language-plaintext highlighter-rouge">mail.google.com</code>. Browser akan menampilkan halaman web dengan tanda gembok hijau di address bar. Pada tahap ini, identitas server sudah dikonfirmasi. Berarti kita sudah menggunakan fitur <em>authentication</em> dari SSL.</li>
<li>Bila CA yang menandatangani tidak terdaftar, maka browser akan menampilkan halaman peringatan.</li>
<li>Browser membuat kode rahasia untuk enkripsi data. Kode rahasia ini dienkripsi menggunakan public key yang ada di dalam sertifikat server, sehingga hanya bisa dibuka oleh server yang memiliki sertifikat.</li>
<li>Server melakukan dekripsi terhadap data kode rahasia dari browser.</li>
<li>Komunikasi data antara server dan browser selanjutnya akan dienkripsi dengan kode rahasia tersebut. Pada tahap ini, kita sudah menggunakan fitur <em>confidentiality</em> dari SSL.</li>
</ol>
<p>Untuk protokol email (SMTP, IMAP, POP3) mekanismenya sama. Cukup gantikan kata <code class="language-plaintext highlighter-rouge">browser</code> dengan <code class="language-plaintext highlighter-rouge">mail-client</code> pada penjelasan di atas.</p>
<p>Berikut contoh sertifikat SSL yang dipercayai browser, perhatikan gambar gemboknya.</p>
<p><a href="/images/uploads/2013/07/ssl/04-tomcat-https-green.png"><img src="/images/uploads/2013/07/ssl/04-tomcat-https-green.png" alt="Menu SSL Firefox " /></a></p>
<p>Dan ini sertifikat SSL yang tidak dipercayai karena CA tidak terdaftar di browser</p>
<p><a href="/images/uploads/2013/07/ssl/05-tomcat-https-red.png"><img src="/images/uploads/2013/07/ssl/05-tomcat-https-red.png" alt="Menu SSL Firefox " /></a></p>
<h3 id="daftar-ca-terpercaya">Daftar CA Terpercaya</h3>
<p>Berbagai aplikasi yang menggunakan SSL menyimpan daftar CA yang dipercayai oleh aplikasi tersebut. Daftar CA ini langsung ditanam di aplikasi (hardcode) sehingga hanya bisa ditambah dan dikurangi oleh pembuat aplikasi. Contohnya, kita bisa melihat daftar CA yang dipercayai Firefox melalui menu <code class="language-plaintext highlighter-rouge">Preferences > Advanced > Encryption</code> pada layar di bawah</p>
<p><a href="/images/uploads/2013/07/ssl/01-firefox-certificate.png"><img src="/images/uploads/2013/07/ssl/01-firefox-certificate.png" alt="Menu SSL Firefox " /></a></p>
<p>Kita bisa lihat daftar CA dengan menekan tombol View Certificates</p>
<p><a href="/images/uploads/2013/07/ssl/02-firefox-ca-list.png"><img src="/images/uploads/2013/07/ssl/02-firefox-ca-list.png" alt="Daftar CA Firefox " /></a></p>
<p>Demikian juga dengan browser Chrome yang ada di menu Settings, kemudian tekan Advanced.</p>
<p><a href="/images/uploads/2013/07/ssl/03-chrome-certificate.png"><img src="/images/uploads/2013/07/ssl/03-chrome-certificate.png" alt="Menu SSL Chrome " /></a></p>
<p>Daftar sertifikat bisa dilihat dengan menekan tombol <code class="language-plaintext highlighter-rouge">Manage Certificates</code></p>
<p><a href="/images/uploads/2013/07/ssl/04-chrome-ca-list.png"><img src="/images/uploads/2013/07/ssl/04-chrome-ca-list.png" alt="Daftar CA Chrome " /></a></p>
<p>Sistem operasi yang kita gunakan juga menyimpan daftar CA terpercaya. Untuk Ubuntu, lihat di folder <code class="language-plaintext highlighter-rouge">/etc/ssl/certs</code></p>
<p><a href="/images/uploads/2013/07/ssl/05-ubuntu-ca-list.png"><img src="/images/uploads/2013/07/ssl/05-ubuntu-ca-list.png" alt="Daftar CA Ubuntu " /></a></p>
<p>Bila terjadi sesuatu pada CA sehingga tidak bisa lagi dipercaya, maka para programmer Firefox, Chrome, Ubuntu, Microsoft, dan sebagainya akan menghapus CA bermasalah tersebut dari daftar CAnya, <a href="http://blog.mozilla.org/security/2011/09/02/diginotar-removal-follow-up/">seperti yang dilakukan Firefox</a> dan <a href="http://www.danielveazey.com/linux/diginotar-certificate-fix-released-for-ubuntu/">Ubuntu</a>. Biasanya penghapusan ini akan diikuti dengan pengumuman security update di internet sehingga semua user bisa melakukan update terhadap aplikasi yang terinstal di komputernya masing-masing.</p>
<p>Demikian artikel SSL bagian pertama. Nantikan artikel selanjutnya tentang <a href="http://software.endy.muhardin.com/aplikasi/membuat-self-signed-certificate/">cara membuat</a> dan <a href="http://software.endy.muhardin.com/aplikasi/membeli-sertifikat-ssl/">membeli sertifikat SSL</a>.</p>
Mengobati OOM2013-06-08T22:25:00+07:00https://software.endy.muhardin.com/java/mengobati-oom<p>Beberapa waktu belakangan ini, di milis mulai banyak bermunculan pertanyaan yang berkaitan dengan Out Of Memory Error (OOM).
Error ini biasanya terjadi bila data dalam aplikasi sudah besar (melebihi 2 GB), dan umumnya terjadi pada saat membuat report PDF/XLS/CSV, dsb.</p>
<p>Biasanya juga, <em>obat yang dikonsumsi</em> oleh penderita OOM ini ada dua:</p>
<ul>
<li>Mengatur alokasi memori dengan opsi <code class="language-plaintext highlighter-rouge">Xms</code> dan <code class="language-plaintext highlighter-rouge">Xmx</code></li>
<li>Menggunakan perintah <code class="language-plaintext highlighter-rouge">System.gc()</code></li>
</ul>
<p>Sayangnya, kedua <em>obat</em> ini tidak akan menyelesaikan masalah. Analoginya seperti kita kena infeksi otak, lalu minum Panadol. Sakit kepalanya mungkin akan hilang sementara waktu, tapi tidak lama kemudian justru akibatnya lebih fatal.</p>
<p>Lalu bagaimana solusinya?</p>
<!--more-->
<h1 id="salah-kaprah-tentang-oom">Salah Kaprah tentang OOM</h1>
<p>Pertama, saya luruskan dulu mengenai <code class="language-plaintext highlighter-rouge">System.gc</code>.
Intinya begini</p>
<blockquote>
<p>System.gc() hanya boleh dipakai dengan satu (dan hanya satu-satunya) syarat:
Yaitu pada waktu sudah bisa menjelaskan ke orang lain bagaimana cara kerja garbage collector dan sudah paham apa itu JVM Ergonomics.</p>
</blockquote>
<p>Next time mau mengetik System.gc, tanyakan pada diri sendiri, apakah saya sudah paham JVM Ergonomics dan bisa menjelaskannya ke orang lain? Kalau jawabannya belum, jangan diteruskan mengetik System.gc().</p>
<blockquote>
<p>Lalu apa itu <code class="language-plaintext highlighter-rouge">System.gc</code> dan kapan digunakan?</p>
</blockquote>
<p>Kan sudah dijelaskan di atas, pelajari cara kerja garbage collector dan apa itu JVM ergonomics.</p>
<p>Satu lagi salah kaprah yang harus dibenahi :
opsi <code class="language-plaintext highlighter-rouge">Xms</code> dan <code class="language-plaintext highlighter-rouge">Xmx</code> itu gunanya adalah <strong>UNTUK MEMAKSIMALKAN PENGGUNAAN MEMORI</strong>.
<code class="language-plaintext highlighter-rouge">Xms</code> dan <code class="language-plaintext highlighter-rouge">Xms</code> <strong>BUKAN SOLUSI UNTUK MASALAH OOM</strong>.</p>
<p>Masalah OOM itu ada di kode program kita sendiri, jadi carilah solusinya di kode program kita sendiri.
Penyebab OOM itu juga cuma satu : aplikasi kita menggunakan memori lebih besar daripada yang tersedia.
Besar pasak daripada tiang.</p>
<ol>
<li>Memori tersedia 4 GB, aplikasi kita pakai 4.1 GB => OOM</li>
<li>Memori tersedia 2 GB, aplikasi kita pakai 1.5 GB => No OOM padahal lebih kecil dari #1</li>
<li>RAM fisik ada 16 GB, tapi aplikasi kita cuma bisa memanfaatkan 2 GB => gunakan <code class="language-plaintext highlighter-rouge">Xms</code> dan <code class="language-plaintext highlighter-rouge">Xmx</code>.</li>
</ol>
<h1 id="penyebab-oom-dan-solusinya">Penyebab OOM dan Solusinya</h1>
<p>Beberapa penyebab umum terjadinya OOM :</p>
<h2 id="unbounded-query">Unbounded Query</h2>
<p>Unbounded query adalah query yang tidak kita batasi jumlahnya.
Contohnya <code class="language-plaintext highlighter-rouge">select * from tbl_penjualan</code>. Kalau data penjualan hanya 1000 transaksi, no problem. Kalau transaksinya ada 10 juta record dalam satu hari, ya pasti langsung kena OOM.</p>
<p>Solusinya : pastikan selalu menggunakan paging. Ganti querynya menjadi <code class="language-plaintext highlighter-rouge">select * from tbl_penjualan limit 0,20</code> sehingga cuma mengeluarkan 20 record, berapapun banyaknya data dalam tabel.</p>
<h2 id="generate-file">Generate File</h2>
<p>Seringkali kita ingin menghasilkan file report. Misalnya laporan penjualan bulan ini, berapapun data yang ada di tabel untuk periode bulan ini, harus ditulis ke file. File ini bisa berupa PDF, XLS, XML, CSV, dan sebagainya.</p>
<p>Format file tidak masalah. Yang menjadi masalah adalah cara membuatnya. Ada beberapa kesalahan umum :</p>
<ul>
<li>mengumpulkan data di variabel sebelum menulis file</li>
<li>tidak melepas data yang sudah diproses</li>
<li>tidak paham cara kerja report engine</li>
</ul>
<h3 id="mengumpulkan-data-di-variabel">Mengumpulkan data di variabel</h3>
<p>Contoh pseudocode seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o"><</span><span class="nc">Penjualan</span><span class="o">></span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><</span><span class="nc">Penjualan</span><span class="o">>();</span>
<span class="c1">// resultset adalah cursor, dia hanya baca apa yang ditarik saja</span>
<span class="nc">ResultSet</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="na">createStatement</span><span class="o">(</span><span class="s">"select * from tbl_penjualan"</span><span class="o">)</span>
<span class="o">.</span><span class="na">executeQuery</span><span class="o">();</span>
<span class="k">while</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">()){</span>
<span class="nc">Penjualan</span> <span class="n">p</span> <span class="o">=</span> <span class="n">konversiResultSetJadiPenjualan</span><span class="o">(</span><span class="n">rs</span><span class="o">);</span>
<span class="n">data</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Coding di atas akan mengumpulkan semua record di variabel <code class="language-plaintext highlighter-rouge">data</code>.
Bila ada 1 juta record yang dihasilkan oleh query dengan masing-masing record berukuran 1 KB,
maka variabel data tersebut akan berukuran 1 GB. Kalau Java VM kita berikan alokasi 2 GB, cuma butuh 3 user untuk menimbulkan OOM.
Menaikkan alokasi menjadi 8 GB melalui <code class="language-plaintext highlighter-rouge">Xms</code> dan <code class="language-plaintext highlighter-rouge">Xmx</code> tidak akan membantu banyak.</p>
<h3 id="tidak-melepas-data-yang-sudah-diproses">Tidak melepas data yang sudah diproses</h3>
<p>Ini biasanya terjadi kalau kita menulis sendiri file output. Perhatikan contoh kode berikut:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ResultSet</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="na">createStatement</span><span class="o">(</span><span class="s">"select * from tbl_penjualan"</span><span class="o">)</span>
<span class="o">.</span><span class="na">executeQuery</span><span class="o">();</span>
<span class="nc">File</span> <span class="n">output</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="s">"output.csv"</span><span class="o">);</span>
<span class="k">while</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">()){</span>
<span class="nc">String</span> <span class="n">baris</span> <span class="o">=</span> <span class="n">konversiResultSetJadiString</span><span class="o">(</span><span class="n">rs</span><span class="o">);</span>
<span class="n">output</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">baris</span><span class="o">);</span>
<span class="n">output</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Menulis file ada bermacam-macam. Ada yang menggunakan buffer ada yang tidak. Pastikan kita mengosongkan buffer setiap kali iterasi supaya tidak penuh, biasanya dengan method <code class="language-plaintext highlighter-rouge">flush</code> atau sejenisnya.</p>
<h3 id="tidak-paham-cara-kerja-report-engine">Tidak paham cara kerja report engine</h3>
<p>Dalam mendesain library yang dipakai orang banyak, biasanya si pembuat akan melakukan optimasi untuk penggunaan yang sering terjadi.
Misalnya 80% orang membuat PDF berukuran 10 halaman atau lebih sedikit, 20% sisanya membuat 1000 halaman atau lebih.
Pada situasi ini, pembuat library report yang baik pasti akan memudahkan pembuatan 10 halaman report karena itulah fitur yang paling sering digunakan.</p>
<p>Bila kita termasuk golongan yang 20%, kita harus belajar lebih giat supaya paham bagaimana mengatasi kondisi minoritas tersebut.</p>
<p>Sebagai contoh, kalau kita menggunakan Jasper Report, ada fitur yang disebut dengan <a href="http://community.jaspersoft.com/wiki/comparison-report-virtualizers">Virtualizer</a> untuk membuat report berukuran besar. Pastikan kita sudah memahami kasus-kasus khusus ini.</p>
<h2 id="inisialisasi-object-besar-di-dalam-loop">Inisialisasi object besar di dalam loop</h2>
<p>Perhatikan contoh kode di bawah</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">){</span>
<span class="nc">ApplicationContext</span> <span class="n">ctx</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ClassPathXmlApplicationContext</span><span class="o">(</span><span class="s">"konfig-spring.xml"</span><span class="o">);</span>
<span class="nc">ProdukDao</span> <span class="n">pd</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="nc">ProdukDao</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">pd</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ApplicationContext</code> merupakan object Spring Framework yang berisi seluruh konfigurasi aplikasi. Isinya :</p>
<ul>
<li>konfigurasi database</li>
<li>mapping ORM (kalau pakai ORM)</li>
<li>semua object yang dimanage Spring</li>
</ul>
<p>Intinya, ini adalah object yang besar sekali dan sangat kompleks. Object seperti ini biasanya hanya dibuat sekali saja sepanjang aplikasi dijalankan. Bila kita taruh dalam method seperti di atas, maka tiap kali ada user simpan data produk, object besar tersebut akan dibuat, sehingga menghabiskan memori.</p>
<p>Pesan moralnya, pahami library yang digunakan supaya tahu karakteristik dan cara kerjanya.</p>
<h2 id="penggunaan-collection-yang-kurang-benar">Penggunaan collection yang kurang benar</h2>
<p>Collection (Set, Map, List) adalah object yang menampung object lain. Kita harus memastikan bahwa object ini berada dalam scope yang benar. Lihat kode program berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Coba</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">noSurat</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><</span><span class="nc">Integer</span><span class="o">>();</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Integer</span> <span class="n">noTerakhir</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">bikinSuratBaru</span><span class="o">(){</span>
<span class="n">noTerakhir</span><span class="o">++;</span>
<span class="n">noSurat</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">noTerakhir</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada kode di atas, <code class="language-plaintext highlighter-rouge">noSurat</code> memiliki scope static, sehingga dia akan terus ada selama aplikasi berjalan. Bila aplikasi tidak mati dalam waktu yang lama, dan dalam sehari ada 1 juta surat baru, tinggal tunggu waktu saja sampai kena OOM.</p>
<h1 id="panduan-solusi-umum">Panduan Solusi Umum</h1>
<p>Selain yang disebutkan di atas, masih banyak penyebab lain, yang paling sering terjadi adalah yang dibahas di atas. Kalau penyebab OOM tidak kita atasi, berapapun memori yang kita sediakan (melalui <code class="language-plaintext highlighter-rouge">Xms</code> dan <code class="language-plaintext highlighter-rouge">Xmx</code>) tidak akan cukup. Inilah sebabnya saya katakan bahwa <code class="language-plaintext highlighter-rouge">Xms</code> dan <code class="language-plaintext highlighter-rouge">Xmx</code> itu bukan solusi untuk OOM. Yang harus kita lakukan adalah <strong>mengendalikan pemakaian</strong> memori, bukan <strong>menambah</strong> memori.</p>
<p>Jadi kalau menemukan OOM, coba dicek lagi kode programnya.
Tidak perlu pakai VisualVM atau tools2 lain, cukup code review aja.</p>
<p>Dibaca satu2 source codenya, untuk tiap baris, tanyakan pertanyaan ini :</p>
<ol>
<li>Method/statement ini akan dipanggil seberapa sering?</li>
<li>Looping ini akan berputar maksimal berapa kali?</li>
<li>Berapa kira2 ukuran tiap row yang ditarik dalam query? Sekali tarik berapa row?</li>
<li>Setelah saya tarik row, kapan dia selesai digunakan? Apakah nunggu PDF jadi dulu baru selesai (inilah kandidat OOM), atau tiap selesai satu halaman PDF sudah bisa direlease?</li>
</ol>
<p>Mudah-mudahan dengan adanya artikel ini tidak ada lagi orang yang mengobati OOM dengan <code class="language-plaintext highlighter-rouge">System.gc</code> atau <code class="language-plaintext highlighter-rouge">Xmx</code>.</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li><a href="http://java.dzone.com/news/how-fix-memory-leaks-java">How to fix memory leaks in Java</a></li>
</ul>
Mengetes Akses Database2013-06-05T11:49:00+07:00https://software.endy.muhardin.com/java/mengetes-akses-database<p>Pada bagian ini, kita akan mempersiapkan seperangkat kode program untuk mengetes aplikasi yang telah kita buat.</p>
<p>Artikel ini merupakan bagian kelima dan terakhir dari rangkaian artikel Spring JDBC, yaitu</p>
<ol>
<li><a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">Konfigurasi koneksi database</a></li>
<li><a href="http://software.endy.muhardin.com/java/struktur-aplikasi-java-dengan-spring-dan-maven/">Struktur Aplikasi</a></li>
<li><a href="http://software.endy.muhardin.com/java/insert-update-delete-dengan-spring-jdbc/">Insert, update, dan delete data</a></li>
<li><a href="http://software.endy.muhardin.com/java/query-dengan-spring-jdbc/">Query data</a></li>
<li><a href="http://software.endy.muhardin.com/java/mengetes-akses-database/">Mengetes Akses Database</a></li>
</ol>
<!--more-->
<h2 id="setup-test">Setup Test</h2>
<p>Seperti telah dijelaskan sebelumnya, test class kita akan terdiri dari dua bagian:</p>
<ol>
<li>Abstract superclass : berisi seluruh kode program pengetesan aplikasi</li>
<li>Concrete subclass : berisi kode program untuk melakukan inisialisasi</li>
</ol>
<p>Berikut adalah kerangka abstract superclass test untuk <code class="language-plaintext highlighter-rouge">Produk</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.service</span><span class="o">;</span>
<span class="c1">// import statement, generate menggunakan IDE</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">ProdukServiceTest</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">PenjualanService</span> <span class="nf">getPenjualanService</span><span class="o">();</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">DataSource</span> <span class="nf">getDataSource</span><span class="o">();</span>
<span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">bersihkanDataTest</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testSimpanUpdateHapusProduk</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="o">}</span>
<span class="c1">// test method lain tidak ditampilkan</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dan berikut ini adalah concrete subclass yang berfungsi melakukan inisialisasi konfigurasi Spring JDBC</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.service.springjdbc</span><span class="o">;</span>
<span class="c1">// import statement generate menggunakan IDE</span>
<span class="nd">@RunWith</span><span class="o">(</span><span class="nc">SpringJUnit4ClassRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@ContextConfiguration</span><span class="o">(</span><span class="s">"classpath*:com/muhardin/**/spring-jdbc-ctx.xml"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukServiceSpringJdbcTest</span> <span class="kd">extends</span> <span class="nc">ProdukServiceTest</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">PenjualanService</span> <span class="n">penjualanService</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">PenjualanService</span> <span class="nf">getPenjualanService</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">penjualanService</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">DataSource</span> <span class="nf">getDataSource</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="database-reset">Database Reset</h2>
<p>Pada kode program abstract superclass di atas, kita melihat ada method untuk membersihkan data test. Method ini diberikan annotation <code class="language-plaintext highlighter-rouge">@Before</code> supaya dia dijalankan <strong>sebelum masing-masing test</strong>.</p>
<p>Berikut adalah isi method tersebut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">bersihkanDataTest</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">DataSource</span> <span class="n">ds</span> <span class="o">=</span> <span class="n">getDataSource</span><span class="o">();</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="n">ds</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"delete from m_produk where kode like ?"</span><span class="o">;</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="s">"T-001%"</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">executeUpdate</span><span class="o">();</span>
<span class="n">conn</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Di method tersebut, kita menghapus semua produk yang kodenya diawali <code class="language-plaintext highlighter-rouge">T-001</code>. Ini adalah data yang kita insert selama proses tes. Agar tidak mengganggu tes lainnya, kita hapus data percobaan tersebut.</p>
<h2 id="test-insert-update-delete">Test Insert Update Delete</h2>
<p>Proses insert, update, dan delete mengubah data yang ada dalam database. Supaya kita tidak repot mengurus data sample yang sudah ada di database, ketiga proses ini kita tes dalam satu kesatuan. Dengan demikian, setelah selesai dijalankan, datanya kembali bersih seperti sebelumnya. Berikut kode programnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testSimpanUpdateHapusProduk</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span><span class="o">{</span>
<span class="nc">Produk</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Produk</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setHarga</span><span class="o">(</span><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="mi">125000</span><span class="o">));</span>
<span class="n">p</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="s">"T-001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Produk Test 001"</span><span class="o">);</span>
<span class="nc">PenjualanService</span> <span class="n">service</span> <span class="o">=</span> <span class="n">getPenjualanService</span><span class="o">();</span>
<span class="n">service</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="n">getDataSource</span><span class="o">().</span><span class="na">getConnection</span><span class="o">();</span>
<span class="nc">PreparedStatement</span> <span class="n">psCariById</span>
<span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span>
<span class="o">(</span><span class="s">"select * from m_produk where id = ?"</span><span class="o">);</span>
<span class="n">psCariById</span><span class="o">.</span><span class="na">setInt</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="nc">ResultSet</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">psCariById</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">();</span>
<span class="c1">// test nilai awal</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">());</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"T-001"</span><span class="o">,</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"kode"</span><span class="o">));</span>
<span class="c1">// update record</span>
<span class="n">p</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="s">"T-001x"</span><span class="o">);</span>
<span class="n">service</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="c1">// test query setelah update</span>
<span class="n">rs</span> <span class="o">=</span> <span class="n">psCariById</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">();</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">());</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"T-001x"</span><span class="o">,</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"kode"</span><span class="o">));</span>
<span class="c1">// test delete</span>
<span class="n">service</span><span class="o">.</span><span class="na">hapus</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="c1">// test query setelah hapus</span>
<span class="n">rs</span> <span class="o">=</span> <span class="n">psCariById</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">();</span>
<span class="n">assertFalse</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dari komentar yang ada dalam kode program, sudah jelas apa maksud dari masing-masing bagian.</p>
<h2 id="test-query">Test Query</h2>
<p>Selanjutnya kita melakukan tes terhadap query. Kita mulai dari yang sederhana dulu, yaitu tabel produk.
Berikut kode program pengetesannya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariProdukById</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">PenjualanService</span> <span class="n">service</span> <span class="o">=</span> <span class="n">getPenjualanService</span><span class="o">();</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">service</span><span class="o">.</span><span class="na">cariProdukById</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span>
<span class="n">assertNull</span><span class="o">(</span><span class="n">service</span><span class="o">.</span><span class="na">cariProdukById</span><span class="o">(</span><span class="mi">99</span><span class="o">));</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariProdukByKode</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">PenjualanService</span> <span class="n">service</span> <span class="o">=</span> <span class="n">getPenjualanService</span><span class="o">();</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">service</span><span class="o">.</span><span class="na">cariProdukByKode</span><span class="o">(</span><span class="s">"K-001"</span><span class="o">));</span>
<span class="n">assertNull</span><span class="o">(</span><span class="n">service</span><span class="o">.</span><span class="na">cariProdukByKode</span><span class="o">(</span><span class="s">"X-001"</span><span class="o">));</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testHitungSemuaProduk</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">PenjualanService</span> <span class="n">service</span> <span class="o">=</span> <span class="n">getPenjualanService</span><span class="o">();</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="nc">Long</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mi">3</span><span class="o">),</span>
<span class="nc">Long</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">service</span><span class="o">.</span><span class="na">hitungSemuaProduk</span><span class="o">()));</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariSemuaProduk</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">PenjualanService</span> <span class="n">service</span> <span class="o">=</span> <span class="n">getPenjualanService</span><span class="o">();</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="n">hasil</span> <span class="o">=</span> <span class="n">service</span><span class="o">.</span><span class="na">cariSemuaProduk</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">100</span><span class="o">);</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">hasil</span><span class="o">);</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">hasil</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="mi">3</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Produk</span> <span class="n">produk</span> <span class="o">:</span> <span class="n">hasil</span><span class="o">)</span> <span class="o">{</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">produk</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">produk</span><span class="o">.</span><span class="na">getKode</span><span class="o">());</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">produk</span><span class="o">.</span><span class="na">getNama</span><span class="o">());</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">produk</span><span class="o">.</span><span class="na">getHarga</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Logika pengetesan tidak kompleks. Kita query datanya menggunakan method di <code class="language-plaintext highlighter-rouge">ProdukDao</code> yang telah kita buat,
lalu kita bandingkan dengan kondisi yang seharusnya. Perbandingan dilakukan menggunakan method yang telah disediakan JUnit, yaitu method berawalan <code class="language-plaintext highlighter-rouge">assert</code>, misalnya <code class="language-plaintext highlighter-rouge">assertNotNull</code>, <code class="language-plaintext highlighter-rouge">assertEquals</code>, dan lainnya.</p>
<p>Yang harus diperhatikan di sini adalah, kita harus benar-benar tahu persis isi database supaya test ini bisa berjalan dengan baik. Ada banyak teknik yang bisa digunakan untuk memastikan isi database sebelum tes dijalankan, salah satunya menggunakan tools yang bernama <a href="http://www.dbunit.org/">DBUnit</a>. Lebih lanjut tentang cara menggunakan DBUnit bisa dibaca di <a href="http://software.endy.muhardin.com/java/ruthless-testing-4/">artikel ini</a>.</p>
<h2 id="test-relasi">Test Relasi</h2>
<p>Pengetesan terhadap relasi pada prinsipnya tidak berbeda. Hanya ada sedikit tambahan yaitu kita juga harus memastikan apakah relasinya berhasil terisi dengan sempurna. Berikut pengetesannya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariPenjualanDetailByProdukDanPeriode</span><span class="o">(){</span>
<span class="nc">Produk</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Produk</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="nc">Date</span> <span class="n">mulai</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTime</span><span class="o">(</span><span class="mi">2013</span><span class="o">,</span><span class="mi">1</span><span class="o">,</span><span class="mi">1</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">).</span><span class="na">toDate</span><span class="o">();</span>
<span class="nc">Date</span> <span class="n">sampai</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTime</span><span class="o">(</span><span class="mi">2013</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">1</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">).</span><span class="na">toDate</span><span class="o">();</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="n">hasil</span> <span class="o">=</span> <span class="n">getPenjualanService</span><span class="o">()</span>
<span class="o">.</span><span class="na">cariPenjualanDetailByProdukDanPeriode</span><span class="o">(</span><span class="n">p</span><span class="o">,</span>
<span class="n">mulai</span><span class="o">,</span> <span class="n">sampai</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">100</span><span class="o">);</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">hasil</span><span class="o">);</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">hasil</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="mi">2</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">PenjualanDetail</span> <span class="n">penjualanDetail</span> <span class="o">:</span> <span class="n">hasil</span><span class="o">)</span> <span class="o">{</span>
<span class="n">verifikasiPenjualanDetail</span><span class="o">(</span><span class="n">penjualanDetail</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada kode program di atas, terlihat bahwa kita melakukan looping yang di dalamnya ada verifikasi <code class="language-plaintext highlighter-rouge">PenjualanDetail</code>. Berikut isi methodnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">verifikasiPenjualanDetail</span>
<span class="o">(</span><span class="nc">PenjualanDetail</span> <span class="n">penjualanDetail</span><span class="o">)</span> <span class="o">{</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">penjualanDetail</span><span class="o">.</span><span class="na">getProduk</span><span class="o">().</span><span class="na">getHarga</span><span class="o">());</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">penjualanDetail</span><span class="o">.</span><span class="na">getPenjualan</span><span class="o">().</span><span class="na">getWaktuTransaksi</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kita cukup memastikan bahwa relasi yang dimiliki <code class="language-plaintext highlighter-rouge">PenjualanDetail</code> yaitu <code class="language-plaintext highlighter-rouge">Produk</code> dan <code class="language-plaintext highlighter-rouge">Penjualan</code> tidak null. Demikian juga variabel yang ada di dalam objectnya yaitu <code class="language-plaintext highlighter-rouge">harga</code> dan <code class="language-plaintext highlighter-rouge">waktuTransaksi</code>.</p>
<p>Demikianlah rangkaian artikel tentang penggunaan Spring JDBC. Kode program selengkapnya dapat diambil <a href="https://github.com/endymuhardin/belajar-akses-database-java/tree/spring-jdbc">di Github</a>.
Semoga bermanfaat.</p>
Query dengan Spring JDBC2013-06-03T20:08:00+07:00https://software.endy.muhardin.com/java/query-dengan-spring-jdbc<p>Pada artikel sebelumnya kita telah bisa mengubah data dalam database, baik menambah (insert), mengubah (update), maupun menghapus (data). Kali ini kita akan membahas tentang cara pengambilan data (select) dari dalam database.</p>
<p>Artikel ini merupakan bagian keempat dari rangkaian artikel Spring JDBC, yaitu</p>
<ol>
<li><a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">Konfigurasi koneksi database</a></li>
<li><a href="http://software.endy.muhardin.com/java/struktur-aplikasi-java-dengan-spring-dan-maven/">Struktur Aplikasi</a></li>
<li><a href="http://software.endy.muhardin.com/java/insert-update-delete-dengan-spring-jdbc/">Insert, update, dan delete data</a></li>
<li><a href="http://software.endy.muhardin.com/java/query-dengan-spring-jdbc/">Query data</a></li>
<li><a href="http://software.endy.muhardin.com/java/mengetes-akses-database/">Mengetes Akses Database</a></li>
</ol>
<!--more-->
<p>Pada dasarnya, untuk mengambil data dari database hanya ada dua varian yang kita gunakan, yaitu:</p>
<ul>
<li>mengambil data tunggal, gunakan method <code class="language-plaintext highlighter-rouge">queryForObject</code></li>
<li>mengambil data banyak, gunakan method <code class="language-plaintext highlighter-rouge">queryForList</code> atau <code class="language-plaintext highlighter-rouge">query</code> saja</li>
</ul>
<p>Kita lihat dulu implementasi yang paling mendasar, ambil semua data produk dari database. Berikut deklarasi SQLnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_CARI_SEMUA</span>
<span class="o">=</span> <span class="s">"select * from m_produk limit ?,?"</span><span class="o">;</span>
</code></pre></div></div>
<p>Perintah SQL di atas akan dipakai dalam method <code class="language-plaintext highlighter-rouge">cariSemua</code> sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="nf">cariSemua</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">query</span><span class="o">(</span><span class="no">SQL_CARI_SEMUA</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">ResultSetJadiProduk</span><span class="o">(),</span>
<span class="nc">PagingHelper</span><span class="o">.</span><span class="na">halamanJadiStart</span><span class="o">(</span><span class="n">halaman</span><span class="o">,</span> <span class="n">baris</span><span class="o">),</span>
<span class="n">baris</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada contoh di atas, kita menggunakan method <code class="language-plaintext highlighter-rouge">query</code> yang menerima tiga argumen, yaitu:</p>
<ul>
<li>SQL yang akan dijalankan, berupa String</li>
<li>Class yang bertugas mengubah ResultSet menjadi object yang kita inginkan, dalam hal ini object <code class="language-plaintext highlighter-rouge">Produk</code>. Class ini akan kita buat sendiri, yaitu class <code class="language-plaintext highlighter-rouge">ResultSetJadiProduk</code>.</li>
<li>argumen lain-lain (varargs). Ini merupakan fitur Java sejak versi 5 yang artinya kita bisa memasukkan argumen sebanyak-banyaknya dalam method. Argumen ini akan dikonversi menjadi List untuk diproses oleh method <code class="language-plaintext highlighter-rouge">query</code></li>
</ul>
<p>Jadi, bukan salah ketik kalau di atas saya sebut tiga argumen padahal kita memasukkan empat variabel ke dalamnya.</p>
<p>SQL select tentu tidak perlu kita bahas lagi, mari masuk ke class <code class="language-plaintext highlighter-rouge">ResultSetJadiProduk</code></p>
<h2 id="membuat-mapper">Membuat Mapper</h2>
<p>Class <code class="language-plaintext highlighter-rouge">ResultSetJadiProduk</code> bertugas mengkonversi hasil query ke database yang ada dalam class <code class="language-plaintext highlighter-rouge">ResultSet</code> menjadi object <code class="language-plaintext highlighter-rouge">Produk</code>. Implementasinya tidak rumit, ini dia.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">class</span> <span class="nc">ResultSetJadiProduk</span> <span class="kd">implements</span> <span class="nc">RowMapper</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">mapRow</span><span class="o">(</span><span class="nc">ResultSet</span> <span class="n">rs</span><span class="o">,</span> <span class="kt">int</span> <span class="n">i</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{</span>
<span class="nc">Produk</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Produk</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">((</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">rs</span><span class="o">.</span><span class="na">getObject</span><span class="o">(</span><span class="s">"id"</span><span class="o">));</span>
<span class="n">p</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"kode"</span><span class="o">));</span>
<span class="n">p</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"nama"</span><span class="o">));</span>
<span class="n">p</span><span class="o">.</span><span class="na">setHarga</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getBigDecimal</span><span class="o">(</span><span class="s">"harga"</span><span class="o">));</span>
<span class="k">return</span> <span class="n">p</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kita membuatnya sebagai class di dalam class (inner class). Kalau lupa dengan strukturnya, silahkan baca lagi bab dua tentang Struktur Aplikasi.</p>
<p>Class ini harus implement interface <code class="language-plaintext highlighter-rouge">RowMapper<T></code> milik Spring. <code class="language-plaintext highlighter-rouge">T</code> diganti dengan class yang menjadi tujuan konversi. Interface <code class="language-plaintext highlighter-rouge">RowMapper<T></code> ini mewajibkan kita membuat method <code class="language-plaintext highlighter-rouge">mapRow</code>. Isi method tersebut sudah cukup jelas sehingga tidak perlu dijelaskan.</p>
<p>Class ini nantinya bisa kita gunakan juga di query yang menghasilkan satu object <code class="language-plaintext highlighter-rouge">Produk</code> seperti <code class="language-plaintext highlighter-rouge">cariById</code> dan <code class="language-plaintext highlighter-rouge">cariByKode</code>. Berikut implementasinya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">cariById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">queryForObject</span><span class="o">(</span><span class="no">SQL_CARI_BY_ID</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">ResultSetJadiProduk</span><span class="o">(),</span> <span class="n">id</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">EmptyResultDataAccessException</span> <span class="n">err</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">cariByKode</span><span class="o">(</span><span class="nc">String</span> <span class="n">kode</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">queryForObject</span><span class="o">(</span><span class="no">SQL_CARI_BY_KODE</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">ResultSetJadiProduk</span><span class="o">(),</span> <span class="n">kode</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">EmptyResultDataAccessException</span> <span class="n">err</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kedua method ini sama saja prinsipnya dengan <code class="language-plaintext highlighter-rouge">cariSemua</code> yang sudah kita bahas sebelumnya.</p>
<h2 id="mengambil-data-berelasi">Mengambil data berelasi</h2>
<p>Setelah berhasil mengambil data dari satu produk, mari kita coba untuk berurusan dengan data berelasi. Sebetulnya prinsipnya sama saja, yaitu membuatkan class konversi. Bedanya hanya terletak pada query SQL yang menggunakan join, tidak ada hubungannya dengan Spring JDBC.</p>
<p>Pada service interface, kita memiliki fitur rekap transaksi untuk satu produk tertentu, yang dimuat dalam method <code class="language-plaintext highlighter-rouge">cariPenjualanDetailByProdukDanPeriode</code>. Method tersebut memanggil method <code class="language-plaintext highlighter-rouge">cariByProdukDanPeriode</code> dalam class <code class="language-plaintext highlighter-rouge">PenjualanDetailDao</code>. Berikut implementasinya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">PenjualanDetailDao</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_CARI_BY_PRODUK_DAN_PERIODE</span>
<span class="o">=</span> <span class="s">"select pd.*, p.waktu_transaksi, "</span>
<span class="o">+</span> <span class="s">"produk.kode as kode_produk, produk.nama as nama_produk,"</span>
<span class="o">+</span> <span class="s">"produk.harga as harga_produk "</span>
<span class="o">+</span> <span class="s">"from t_penjualan_detail pd "</span>
<span class="o">+</span> <span class="s">"inner join t_penjualan p on pd.id_penjualan = p.id "</span>
<span class="o">+</span> <span class="s">"inner join m_produk produk on pd.id_produk = produk.id "</span>
<span class="o">+</span> <span class="s">"where pd.id_produk = ? "</span> <span class="o">+</span>
<span class="o">+</span> <span class="s">"and (p.waktu_transaksi between ? and ?) "</span> <span class="o">+</span>
<span class="o">+</span> <span class="s">"limit ?,?"</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="nf">cariByProdukDanPeriode</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">,</span>
<span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">query</span><span class="o">(</span><span class="no">SQL_CARI_BY_PRODUK_DAN_PERIODE</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">ResultSetJadiPenjualanDetail</span><span class="o">(),</span>
<span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span>
<span class="n">mulai</span><span class="o">,</span>
<span class="n">sampai</span><span class="o">,</span>
<span class="nc">PagingHelper</span><span class="o">.</span><span class="na">halamanJadiStart</span><span class="o">(</span><span class="n">halaman</span><span class="o">,</span> <span class="n">baris</span><span class="o">),</span>
<span class="n">baris</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Konversi dari <code class="language-plaintext highlighter-rouge">ResultSet</code> menjadi <code class="language-plaintext highlighter-rouge">PenjualanDetail</code> dilakukan dalam class <code class="language-plaintext highlighter-rouge">ResultSetJadiPenjualanDetail</code> berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">class</span> <span class="nc">ResultSetJadiPenjualanDetail</span>
<span class="kd">implements</span> <span class="nc">RowMapper</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">PenjualanDetail</span> <span class="nf">mapRow</span><span class="o">(</span><span class="nc">ResultSet</span> <span class="n">rs</span><span class="o">,</span> <span class="kt">int</span> <span class="n">i</span><span class="o">)</span>
<span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{</span>
<span class="nc">PenjualanDetail</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PenjualanDetail</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">((</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">rs</span><span class="o">.</span><span class="na">getObject</span><span class="o">(</span><span class="s">"id"</span><span class="o">));</span>
<span class="n">p</span><span class="o">.</span><span class="na">setHarga</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getBigDecimal</span><span class="o">(</span><span class="s">"harga"</span><span class="o">));</span>
<span class="n">p</span><span class="o">.</span><span class="na">setJumlah</span><span class="o">((</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">rs</span><span class="o">.</span><span class="na">getObject</span><span class="o">(</span><span class="s">"jumlah"</span><span class="o">));</span>
<span class="c1">// relasi ke produk</span>
<span class="nc">Produk</span> <span class="n">px</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Produk</span><span class="o">();</span>
<span class="n">px</span><span class="o">.</span><span class="na">setId</span><span class="o">((</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">rs</span><span class="o">.</span><span class="na">getObject</span><span class="o">(</span><span class="s">"id_produk"</span><span class="o">));</span>
<span class="n">px</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"kode_produk"</span><span class="o">));</span>
<span class="n">px</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"nama_produk"</span><span class="o">));</span>
<span class="n">px</span><span class="o">.</span><span class="na">setHarga</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getBigDecimal</span><span class="o">(</span><span class="s">"harga_produk"</span><span class="o">));</span>
<span class="n">p</span><span class="o">.</span><span class="na">setProduk</span><span class="o">(</span><span class="n">px</span><span class="o">);</span>
<span class="c1">// relasi ke penjualan</span>
<span class="nc">Penjualan</span> <span class="n">jual</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Penjualan</span><span class="o">();</span>
<span class="n">jual</span><span class="o">.</span><span class="na">setId</span><span class="o">((</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">rs</span><span class="o">.</span><span class="na">getObject</span><span class="o">(</span><span class="s">"id_penjualan"</span><span class="o">));</span>
<span class="n">jual</span><span class="o">.</span><span class="na">setWaktuTransaksi</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getDate</span><span class="o">(</span><span class="s">"waktu_transaksi"</span><span class="o">));</span>
<span class="n">p</span><span class="o">.</span><span class="na">setPenjualan</span><span class="o">(</span><span class="n">jual</span><span class="o">);</span>
<span class="k">return</span> <span class="n">p</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Selain class <code class="language-plaintext highlighter-rouge">PenjualanDetail</code> itu sendiri, kita juga membuatkan object <code class="language-plaintext highlighter-rouge">Produk</code> dan <code class="language-plaintext highlighter-rouge">Penjualan</code> yang kemudian akan dipasang pada object <code class="language-plaintext highlighter-rouge">PenjualanDetail</code>.</p>
<h2 id="pagination">Pagination</h2>
<p>Dalam mengambil data yang berjumlah banyak seperti data transaksi, biasanya kita akan melakukan <em>pagination</em>, yaitu membagi data menjadi beberapa halaman. Di MySQL, kita menggunakan keyword <code class="language-plaintext highlighter-rouge">LIMIT</code> untuk melakukan hal ini. Keyword <code class="language-plaintext highlighter-rouge">LIMIT</code> menerima dua argumen, yaitu nomer record pertama yang mau diambil dan jumlah record yang mau diambil. Jadi bila kita ingin mengambil record 11 - 15, kita menggunakan keyword <code class="language-plaintext highlighter-rouge">LIMIT 11, 5</code>.</p>
<p>Ini agak berbeda dengan argumen yang diterima dalam method pencarian kita. Yang diminta di situ adalah nomer halaman dan jumlah record per halaman. Jadi kalau misalnya data kita berjumlah 56 record dan kita ingin setiap halaman berisi 10 record, maka data tersebut akan terbagi menjadi 6 halaman. Bila kita ingin mengambil halaman terakhir, kita memberikan argumen <code class="language-plaintext highlighter-rouge">6</code> dan <code class="language-plaintext highlighter-rouge">10</code> ke dalam method pencarian.</p>
<p>Tentunya harus ada konversi dari nomer halaman menjadi nomer baris. Ini kita lakukan di class <code class="language-plaintext highlighter-rouge">PagingHelper</code> yang isinya sebagai berikut.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">PagingHelper</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">Integer</span> <span class="nf">halamanJadiStart</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span>
<span class="nc">Integer</span> <span class="n">baris</span><span class="o">){</span>
<span class="k">if</span> <span class="o">(</span><span class="n">halaman</span> <span class="o"><</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="o">(</span><span class="n">halaman</span> <span class="o">-</span> <span class="mi">1</span><span class="o">)</span> <span class="o">*</span> <span class="n">baris</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="header-detail">Header Detail</h2>
<p>Dalam aplikasi, pasti ada fitur untuk menampilkan daftar transaksi dalam periode tertentu.
Seperti sudah kita bahas, satu transaksi terdiri dari satu header dan beberapa detail.
Data ini tentu ingin kita ambil semua.</p>
<p>Caranya sederhana :</p>
<ol>
<li>Query dulu headernya: <code class="language-plaintext highlighter-rouge">select * from t_penjualan where id = ?</code></li>
<li>Query detailnya: <code class="language-plaintext highlighter-rouge">select * from t_penjualan_detail where id_penjualan = ?</code></li>
<li>Gabungkan keduanya</li>
</ol>
<p>Berikut contohnya, pada waktu kita ingin mencari <code class="language-plaintext highlighter-rouge">Penjualan</code> berdasarkan <code class="language-plaintext highlighter-rouge">id</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">Penjualan</span> <span class="nf">cariById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">Penjualan</span> <span class="n">p</span> <span class="o">=</span> <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">queryForObject</span><span class="o">(</span><span class="no">SQL_CARI_BY_ID</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">ResultSetJadiPenjualan</span><span class="o">(),</span> <span class="n">id</span><span class="o">);</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="n">daftarDetail</span> <span class="o">=</span> <span class="n">penjualanDetailDao</span>
<span class="o">.</span><span class="na">cariByPenjualan</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setDaftarPenjualanDetail</span><span class="o">(</span><span class="n">daftarDetail</span><span class="o">);</span>
<span class="k">return</span> <span class="n">p</span><span class="o">;</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">EmptyResultDataAccessException</span> <span class="n">err</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Di sana, kita memanggil method <code class="language-plaintext highlighter-rouge">cariByPenjualan</code> yang ada di class <code class="language-plaintext highlighter-rouge">PenjualanDetailDao</code>. Berikut kode programnya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="nf">cariByPenjualan</span><span class="o">(</span><span class="nc">Penjualan</span> <span class="n">p</span><span class="o">){</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="n">hasil</span>
<span class="o">=</span> <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">query</span><span class="o">(</span><span class="no">SQL_CARI_BY_ID_PENJUALAN</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">ResultSetJadiPenjualanDetail</span><span class="o">(),</span> <span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="c1">// set relasi ke penjualan</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">PenjualanDetail</span> <span class="n">penjualanDetail</span> <span class="o">:</span> <span class="n">hasil</span><span class="o">)</span> <span class="o">{</span>
<span class="n">penjualanDetail</span><span class="o">.</span><span class="na">setPenjualan</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">hasil</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kembali ke <code class="language-plaintext highlighter-rouge">PenjualanDao</code>, setelah kita mendapatkan <code class="language-plaintext highlighter-rouge">List<PenjualanDetail></code> dari method di atas, kita pasang di object penjualan yang sudah kita dapatkan di baris ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">p</span><span class="o">.</span><span class="na">setDaftarPenjualanDetail</span><span class="o">(</span><span class="n">daftarDetail</span><span class="o">);</span>
</code></pre></div></div>
<p>Demikianlah cara kita mengambil data dari database menggunakan Spring JDBC.
Hal ini tentu tidak rumit asalkan kita sudah paham dasar-dasar SQL termasuk cara melakukan join antar tabel.
Jadi kesimpulannya, untuk mengambil data menggunakan Spring JDBC, yang perlu kita lakukan hanyalah:</p>
<ol>
<li>Membuat SQL, lengkap dengan join bila perlu. Contohnya bisa dilihat di class <code class="language-plaintext highlighter-rouge">PenjualanDetailDao</code> yang memiliki banyak join.</li>
<li>Membuat class untuk mengkonversi dari <code class="language-plaintext highlighter-rouge">ResultSet</code> menjadi object yang kita inginkan, misalnya <code class="language-plaintext highlighter-rouge">Produk</code> atau <code class="language-plaintext highlighter-rouge">Penjualan</code></li>
</ol>
<p>Pada bagian selanjutnya, kita tinggal <a href="http://software.endy.muhardin.com/java/mengetes-akses-database/">mengetes kode program</a> yang telah kita buat ini.</p>
Insert Update Delete dengan Spring JDBC2013-05-31T13:26:00+07:00https://software.endy.muhardin.com/java/insert-update-delete-dengan-spring-jdbc<p>Setelah pada artikel sebelumnya kita membuat konfigurasi database dan membuat kerangka aplikasi, kali ini kita akan membuat implementasi akses database menggunakan Spring JDBC.</p>
<p>Artikel ini merupakan bagian ketiga dari rangkaian artikel Spring JDBC, yaitu</p>
<ol>
<li><a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">Konfigurasi koneksi database</a></li>
<li><a href="http://software.endy.muhardin.com/java/struktur-aplikasi-java-dengan-spring-dan-maven/">Struktur Aplikasi</a></li>
<li><a href="http://software.endy.muhardin.com/java/insert-update-delete-dengan-spring-jdbc/">Insert, update, dan delete data</a></li>
<li><a href="http://software.endy.muhardin.com/java/query-dengan-spring-jdbc/">Query data</a></li>
<li><a href="http://software.endy.muhardin.com/java/mengetes-akses-database/">Mengetes Akses Database</a></li>
</ol>
<!--more-->
<h1 id="setup-dao">Setup DAO</h1>
<p>Supaya bisa diinisialisasi oleh Spring Framework, kita harus menambahkan annotation <code class="language-plaintext highlighter-rouge">@Repository</code> di class <code class="language-plaintext highlighter-rouge">ProdukDao</code> dan juga class DAO lainnya. Pembahasan lebih detail tentang cara kerja Spring Framework dan fungsinya annotation <code class="language-plaintext highlighter-rouge">@Repository</code> dapat dibaca <a href="http://software.endy.muhardin.com/java/memahami-dependency-injection/">di artikel ini</a>.</p>
<p>Selanjutnya, kita akan menambahkan beberapa variabel yang nantinya akan kita butuhkan, yaitu <code class="language-plaintext highlighter-rouge">JdbcTemplate</code> dan perintah SQL yang ingin dijalankan.</p>
<h2 id="jdbc-template">JDBC Template</h2>
<p><code class="language-plaintext highlighter-rouge">JdbcTemplate</code> adalah class utama dalam Spring JDBC. Semua operasi database dilakukan melalui JdbcTemplate. Kita juga butuh rekannya yang bernama <code class="language-plaintext highlighter-rouge">NamedParameterJdbcTemplate</code>. Kelebihan dari <code class="language-plaintext highlighter-rouge">NamedParameterJdbcTemplate</code> ini, dia bisa menerima SQL dengan variabel yang diberi nama. Tanpa dia, kita cuma bisa menjalankan SQL yang variabelnya ditandai dengan <code class="language-plaintext highlighter-rouge">?</code>, sehingga membingungkan dan rawan terjadi kesalahan kalau jumlah variabelnya banyak.</p>
<p>Kedua object ini kita deklarasikan menjadi object/instance variable, supaya bisa digunakan oleh semua method. Inisialisasinya membutuhkan object <code class="language-plaintext highlighter-rouge">DataSource</code>, sehingga sebaiknya kita lakukan inisialisasi di dalam setter injection. Bagi yang belum paham apa itu setter injection silahkan baca dulu <a href="http://software.endy.muhardin.com/java/memahami-dependency-injection/">artikel ini</a>.</p>
<p>Berikut adalah kode programnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDao</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">JdbcTemplate</span> <span class="n">jdbcTemplate</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">NamedParameterJdbcTemplate</span> <span class="n">namedParameterJdbcTemplate</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setDataSource</span><span class="o">(</span><span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">jdbcTemplate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JdbcTemplate</span><span class="o">(</span><span class="n">dataSource</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">namedParameterJdbcTemplate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">NamedParameterJdbcTemplate</span><span class="o">(</span><span class="n">dataSource</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="sql">SQL</h2>
<p>Agar lebih rapi, semua perintah SQL yang kita akan gunakan di dalam method sebaiknya kita deklarasikan sebagai konstanta. Berikut adalah berbagai SQL yang kita gunakan.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDao</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_INSERT</span> <span class="o">=</span> <span class="s">"insert into m_produk (kode,nama,harga) values (:kode,:nama,:harga)"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_CARI_BY_ID</span> <span class="o">=</span> <span class="s">"select * from m_produk where id = ?"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_CARI_BY_KODE</span> <span class="o">=</span> <span class="s">"select * from m_produk where kode = ?"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_HITUNG_SEMUA</span> <span class="o">=</span> <span class="s">"select count(*) from m_produk"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_CARI_SEMUA</span> <span class="o">=</span> <span class="s">"select * from m_produk limit ?,?"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_HITUNG_BY_NAMA</span> <span class="o">=</span> <span class="s">"select count(*) from m_produk where lower(nama) like ?"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_CARI_BY_NAMA</span> <span class="o">=</span> <span class="s">"select * from m_produk where lower(nama) like ? limit ?,?"</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Harap diperhatikan bahwa saya hanya menampilkan potongan kode program yang berkaitan dengan penjelasan saat ini saja, berikut sedikit tambahan supaya jelas di mana penempatannya. Untuk isi kode program yang lengkap silahkan lihat <a href="https://github.com/endymuhardin/belajar-akses-database-java/tree/spring-jdbc/src/main/java/com/muhardin/endy/training/java/aksesdb/dao/springjdbc">di Github</a>.</p>
<h1 id="insert-data">Insert Data</h1>
<p>Setelah kita punya <code class="language-plaintext highlighter-rouge">JdbcTemplate</code> dan perintah SQL, kita bisa gunakan untuk menyimpan data ke database.
Mari kita implement method <code class="language-plaintext highlighter-rouge">simpan</code>.</p>
<h2 id="menggunakan-parameter-domain-object">Menggunakan parameter domain object</h2>
<p>Class <code class="language-plaintext highlighter-rouge">Produk</code> memiliki properti yang bernama <code class="language-plaintext highlighter-rouge">kode</code>, <code class="language-plaintext highlighter-rouge">nama</code>, dan <code class="language-plaintext highlighter-rouge">harga</code>. Karena kita mengikuti aturan penamaan yang baku, maka kita buatkan method getter dan setter. Class yang dibuat mengikuti standar ini bisa langsung diproses oleh Spring JDBC. Dia akan secara otomatis memasangkan properti yang namanya sama dengan yang ada di perintah SQL. Jadi perintah SQL seperti ini</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">m_produk</span> <span class="p">(</span><span class="n">kode</span><span class="p">,</span><span class="n">nama</span><span class="p">,</span><span class="n">harga</span><span class="p">)</span> <span class="k">values</span> <span class="p">(:</span><span class="n">kode</span><span class="p">,:</span><span class="n">nama</span><span class="p">,:</span><span class="n">harga</span><span class="p">)</span>
</code></pre></div></div>
<p>Akan diisikan dengan nilai yang didapat dari pemanggilan method berikut:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Produk</span> <span class="n">p</span><span class="o">;</span> <span class="c1">// nantinya p diisi dari method parameter</span>
<span class="n">p</span><span class="o">.</span><span class="na">getKode</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">getNama</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">getHarga</span><span class="o">();</span>
</code></pre></div></div>
<p>Spring JDBC juga akan secara otomatis mendeteksi tipe data baik itu <code class="language-plaintext highlighter-rouge">Integer</code>, <code class="language-plaintext highlighter-rouge">String</code>, <code class="language-plaintext highlighter-rouge">BigDecimal</code>, dan sebagainya.</p>
<p>Berikut adalah implementasi method <code class="language-plaintext highlighter-rouge">simpan</code> yang menggunakan konsep di atas.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SqlParameterSource</span> <span class="n">namedParameters</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BeanPropertySqlParameterSource</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="nc">KeyHolder</span> <span class="n">keyHolder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GeneratedKeyHolder</span><span class="o">();</span>
<span class="n">namedParameterJdbcTemplate</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="no">SQL_INSERT</span><span class="o">,</span> <span class="n">namedParameters</span><span class="o">,</span> <span class="n">keyHolder</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">keyHolder</span><span class="o">.</span><span class="na">getKey</span><span class="o">().</span><span class="na">intValue</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">BeanPropertySqlParameterSource</code> adalah class dari Spring JDBC yang berfungsi mengambil parameter dari Java object seperti telah dijelaskan di atas. Selain mengambil dari object dengan getter dan setter, ada lagi class lain yang bernama <code class="language-plaintext highlighter-rouge">MapSqlParameterSource</code>, digunakan untuk mengambil parameter dari object bertipe <code class="language-plaintext highlighter-rouge">java.util.Map</code>.</p>
<h2 id="mendapatkan-nilai-yang-auto-generated">Mendapatkan nilai yang auto generated</h2>
<p>Di tabel <code class="language-plaintext highlighter-rouge">m_produk</code>, primary key ada di kolom <code class="language-plaintext highlighter-rouge">id</code>. Nilainya digenerate otomatis oleh database pada saat kita melakukan insert. Kita perlu mengetahui nilai berapa yang dibuatkan database untuk data yang barusan kita insert agar bisa dipasang di object <code class="language-plaintext highlighter-rouge">Produk</code> yang kita insert. Untuk menampung nilai yang otomatis dibuatkan database, kita menggunakan class <code class="language-plaintext highlighter-rouge">GeneratedKeyHolder</code>.</p>
<p>Setelah kita melakukan insert, <code class="language-plaintext highlighter-rouge">keyHolder</code> akan menampung nilai. Nilai inilah yang kita pasang di object produk dengan kode program berikut.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">namedParameterJdbcTemplate</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="no">SQL_INSERT</span><span class="o">,</span> <span class="n">namedParameters</span><span class="o">,</span> <span class="n">keyHolder</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">keyHolder</span><span class="o">.</span><span class="na">getKey</span><span class="o">().</span><span class="na">intValue</span><span class="o">());</span>
</code></pre></div></div>
<h2 id="menggunakan-simplejdbcinsert">Menggunakan SimpleJdbcInsert</h2>
<p>Object <code class="language-plaintext highlighter-rouge">produk</code> di atas hanya memiliki 4 property di Java dan 4 kolom di database. Bila propertynya banyak, tentu kita akan kerepotan menulis variabel dalam perintah SQL. Untuk membantu kita, Spring JDBC menyediakan fitur <code class="language-plaintext highlighter-rouge">SimpleJdbcInsert</code>. Cara kerjanya, dia akan <em>menanyakan</em> ke database kolom apa saja yang ada di tabel tertentu berikut tipe data untuk masing-masing kolom. Setelah itu, informasi tersebut digunakan untuk membuatkan SQL insert buat kita, sehingga kita tidak perlu membuat sendiri.</p>
<p>Berikut adalah cara inisialisasi <code class="language-plaintext highlighter-rouge">SimpleJdbcInsert</code>, dilakukan di setter injection.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDao</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">SimpleJdbcInsert</span> <span class="n">insertProduk</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setDataSource</span><span class="o">(</span><span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">insertProduk</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleJdbcInsert</span><span class="o">(</span><span class="n">dataSource</span><span class="o">)</span>
<span class="o">.</span><span class="na">withTableName</span><span class="o">(</span><span class="s">"m_produk"</span><span class="o">)</span>
<span class="o">.</span><span class="na">usingGeneratedKeyColumns</span><span class="o">(</span><span class="s">"id"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kita menyebutkan juga kolom mana yang nilainya digenerate database, sehingga dia bisa mengambilkannya. Selanjutnya, mari kita ganti implementasi method <code class="language-plaintext highlighter-rouge">simpan</code> dengan memanfaatkan <code class="language-plaintext highlighter-rouge">SimpleJdbcInsert</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SqlParameterSource</span> <span class="n">produkParameter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BeanPropertySqlParameterSource</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="nc">Number</span> <span class="n">idBaru</span> <span class="o">=</span> <span class="n">insertProduk</span><span class="o">.</span><span class="na">executeAndReturnKey</span><span class="o">(</span><span class="n">produkParameter</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">idBaru</span><span class="o">.</span><span class="na">intValue</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="insert-data-header-dan-detail">Insert Data Header dan Detail</h2>
<blockquote>
<p>Kenapa kita perlu mengambil primary key yang digenerate database?</p>
</blockquote>
<p>Jawabannya karena kita membutuhkannya dalam skenario header dan detail. Di tabel detail, ada relasi foreign key ke tabel header. Sehingga untuk mengisi foreign key tersebut, kita harus mengetahui dulu primary key header.</p>
<p>Selain masalah foreign key ini, sisanya sama dengan pembahasan insert sebelumnya. Berikut implementasi method <code class="language-plaintext highlighter-rouge">simpan(Penjualan p)</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Penjualan</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SqlParameterSource</span> <span class="n">namedParameters</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BeanPropertySqlParameterSource</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="nc">KeyHolder</span> <span class="n">keyHolder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GeneratedKeyHolder</span><span class="o">();</span>
<span class="n">namedParameterJdbcTemplate</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="no">SQL_INSERT</span><span class="o">,</span> <span class="n">namedParameters</span><span class="o">,</span> <span class="n">keyHolder</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">keyHolder</span><span class="o">.</span><span class="na">getKey</span><span class="o">().</span><span class="na">intValue</span><span class="o">());</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">PenjualanDetail</span> <span class="n">detail</span> <span class="o">:</span> <span class="n">p</span><span class="o">.</span><span class="na">getDaftarPenjualanDetail</span><span class="o">())</span> <span class="o">{</span>
<span class="n">detail</span><span class="o">.</span><span class="na">setPenjualan</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="n">penjualanDetailDao</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">detail</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Seperti kita lihat pada kode program di atas, kita insert ke tabel <code class="language-plaintext highlighter-rouge">t_penjualan</code>, kemudian kita ambil nilai <code class="language-plaintext highlighter-rouge">id</code>. Nilai tersebut kita isikan ke tiap object detail untuk selanjutnya kita insert juga menggunakan method <code class="language-plaintext highlighter-rouge">simpan(PenjualanDetail pd)</code> di class <code class="language-plaintext highlighter-rouge">PenjualanDetailDao</code>. Berikut implementasinya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="kd">final</span> <span class="nc">PenjualanDetail</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">KeyHolder</span> <span class="n">keyHolder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GeneratedKeyHolder</span><span class="o">();</span>
<span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="k">new</span> <span class="nc">PreparedStatementCreator</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">PreparedStatement</span> <span class="nf">createPreparedStatement</span><span class="o">(</span><span class="nc">Connection</span> <span class="n">conn</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="no">SQL_INSERT</span><span class="o">,</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[]{</span><span class="s">"id"</span><span class="o">});</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setInt</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getPenjualan</span><span class="o">().</span><span class="na">getId</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setInt</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getProduk</span><span class="o">().</span><span class="na">getId</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setBigDecimal</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getHarga</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setInt</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getJumlah</span><span class="o">());</span>
<span class="k">return</span> <span class="n">ps</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">},</span> <span class="n">keyHolder</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">keyHolder</span><span class="o">.</span><span class="na">getKey</span><span class="o">().</span><span class="na">intValue</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada waktu menyimpan <code class="language-plaintext highlighter-rouge">PenjualanDetail</code>, kita menggunakan teknik yang agak berbeda, tidak menggunakan <code class="language-plaintext highlighter-rouge">SimpleJdbcInsert</code> maupun <code class="language-plaintext highlighter-rouge">NamedParameterJdbcTemplate</code>. Ini disebabkan karena banyak kolom yang namanya tidak mengikuti aturan penamaan, yaitu <code class="language-plaintext highlighter-rouge">id_penjualan</code> dan <code class="language-plaintext highlighter-rouge">id_produk</code>. Karena itu kita menggunakan cara yang lebih manual, membuat sendiri <code class="language-plaintext highlighter-rouge">PreparedStatement</code> dan kemudian mengisi parameternya.</p>
<p>Walaupun demikian, kita tidak mengeksekusi <code class="language-plaintext highlighter-rouge">PreparedStatement</code> tersebut, karena urusan mengeksekusi akan ditangani Spring JDBC supaya bisa dibungkus dalam transaction. Kenapa harus dibungkus dalam transaction? Jawabannya bisa dibaca di <a href="http://software.endy.muhardin.com/java/database-transaction/">artikel ini</a>.</p>
<h1 id="update-data">Update Data</h1>
<p>Pada prinsipnya, update data tidak berbeda dengan insert data. Kita dapat melakukannya dengan cara yang sama seperti insert data di atas. Tapi karena kita ingin melihat fitur-fitur Spring JDBC, baiklah kita lakukan dengan cara yang sedikit berbeda.</p>
<h2 id="menggunakan-parameter-bertipe-map">Menggunakan parameter bertipe Map</h2>
<p>Kalau di atas kita sudah menggunakan parameter menggunakan <code class="language-plaintext highlighter-rouge">?</code> dan domain object, kali ini kita akan menggunakan <code class="language-plaintext highlighter-rouge">Map</code>. Cara ini digunakan bila nama variabel di SQL berbeda dengan nama variabel di domain object, sehingga kita tidak bisa menggunakan <code class="language-plaintext highlighter-rouge">BeanPropertySqlParameterSource</code>. Kita juga tidak mau menggunakan <code class="language-plaintext highlighter-rouge">?</code> karena rawan terjadi salah ketik.</p>
<p>Kita ubah sedikit method <code class="language-plaintext highlighter-rouge">simpan(Produk p )</code> agar bisa menangani penyimpanan data produk baru (insert) maupun penyimpanan data produk lama (update). Berikut kode program method <code class="language-plaintext highlighter-rouge">simpan(Produk p )</code> yang baru.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SqlParameterSource</span> <span class="n">produkParameter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BeanPropertySqlParameterSource</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="nc">Number</span> <span class="n">idBaru</span> <span class="o">=</span> <span class="n">insertProduk</span><span class="o">.</span><span class="na">executeAndReturnKey</span><span class="o">(</span><span class="n">produkParameter</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">idBaru</span><span class="o">.</span><span class="na">intValue</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="nc">SqlParameterSource</span> <span class="n">produkParameter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MapSqlParameterSource</span><span class="o">()</span>
<span class="o">.</span><span class="na">addValue</span><span class="o">(</span><span class="s">"id_produk"</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
<span class="o">.</span><span class="na">addValue</span><span class="o">(</span><span class="s">"kode_produk"</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getKode</span><span class="o">())</span>
<span class="o">.</span><span class="na">addValue</span><span class="o">(</span><span class="s">"nama_produk"</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getNama</span><span class="o">())</span>
<span class="o">.</span><span class="na">addValue</span><span class="o">(</span><span class="s">"harga_produk"</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getHarga</span><span class="o">());</span>
<span class="n">namedParameterJdbcTemplate</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="no">SQL_UPDATE_PRODUK</span><span class="o">,</span> <span class="n">produkParameter</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Agar lebih jelas, kita tampilkan juga deklarasi <code class="language-plaintext highlighter-rouge">SQL_UPDATE_PRODUK</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_UPDATE_PRODUK</span>
<span class="o">=</span> <span class="s">"update m_produk set kode = :kode_produk, nama = :nama_produk, harga = :harga_produk where id = :id_produk"</span><span class="o">;</span>
</code></pre></div></div>
<p>Penggunaan <code class="language-plaintext highlighter-rouge">MapSqlParameterSource</code> dapat dilihat di blok <code class="language-plaintext highlighter-rouge">else</code>. Argumen kiri dari method <code class="language-plaintext highlighter-rouge">addValue</code> adalah nama variabel dalam <code class="language-plaintext highlighter-rouge">SQL_UPDATE_PRODUK</code>, sedangkan argumen kanan adalah nilai yang ingin diisikan.</p>
<h1 id="delete-data">Delete Data</h1>
<p>Bila kita sudah menguasai insert dan update, maka delete seharusnya tidak menjadi masalah. Berikut kode program untuk menghapus data produk.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">hapus</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="no">SQL_HAPUS</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Berikut deklarasi <code class="language-plaintext highlighter-rouge">SQL_HAPUS</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SQL_HAPUS</span> <span class="o">=</span> <span class="s">"delete from m_produk where id = ?"</span><span class="o">;</span>
</code></pre></div></div>
<p>Menghapus <code class="language-plaintext highlighter-rouge">penjualan</code> yang memiliki relasi header detail juga tidak sulit. Pastikan kita menghapus detailnya dulu sebelum menghapus headernya agar tidak terjadi pelanggaran <a href="http://en.wikipedia.org/wiki/Referential_integrity">referential integrity</a>.</p>
<p>Berikut contoh pesan error bila kita menghapus data produk yang sudah dipakai dalam transaksi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback;
SQL [delete from m_produk where id = ?];
Cannot delete or update a parent row:
a foreign key constraint fails (`belajar`.`t_penjualan_detail`,
CONSTRAINT `t_penjualan_detail_ibfk_2` FOREIGN KEY (`id_produk`)
REFERENCES `m_produk` (`id`));
nested exception is
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot delete or update a parent row:
a foreign key constraint fails (`belajar`.`t_penjualan_detail`,
CONSTRAINT `t_penjualan_detail_ibfk_2` FOREIGN KEY (`id_produk`)
REFERENCES `m_produk` (`id`))
</code></pre></div></div>
<p>Pada pesan error di atas, data <code class="language-plaintext highlighter-rouge">produk</code> tidak bisa dihapus karena sudah digunakan sebagai relasi foreign key dari <code class="language-plaintext highlighter-rouge">penjualan detail</code>.</p>
<p>Demikianlah penjelasan tentang insert, update, dan delete data. Nantikan <a href="http://software.endy.muhardin.com/java/query-dengan-spring-jdbc/">bagian selanjutnya</a> yang membahas tentang query select.</p>
Struktur Aplikasi Java dengan Spring dan Maven2013-05-28T15:06:00+07:00https://software.endy.muhardin.com/java/struktur-aplikasi-java-dengan-spring-dan-maven<p>Pada <a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">bagian sebelumnya</a> kita telah membahas konfigurasi awal
dan studi kasus yang akan digunakan untuk menunjukkan fitur Spring JDBC.</p>
<p>Di artikel bagian kedua ini, kita akan membahas tentang kerangka aplikasi yang akan dibuat.
Sebelum membuat implementasi detail, sangat penting kita buat dulu kerangkanya supaya jelas
apa saja bagian-bagian dalam aplikasi dan bagaimana mereka saling terhubung.</p>
<p>Artikel ini merupakan bagian kedua dari rangkaian artikel Spring JDBC, yaitu</p>
<ol>
<li><a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">Konfigurasi koneksi database</a></li>
<li><a href="http://software.endy.muhardin.com/java/struktur-aplikasi-java-dengan-spring-dan-maven/">Struktur Aplikasi</a></li>
<li><a href="http://software.endy.muhardin.com/java/insert-update-delete-dengan-spring-jdbc/">Insert, update, dan delete data</a></li>
<li><a href="http://software.endy.muhardin.com/java/query-dengan-spring-jdbc/">Query data</a></li>
<li><a href="http://software.endy.muhardin.com/java/mengetes-akses-database/">Mengetes Akses Database</a></li>
</ol>
<!--more-->
<h2 id="daftar-class-yang-akan-dibuat">Daftar class yang akan dibuat</h2>
<p>Class yang akan dibuat kita bagi menjadi empat fungsi utama, yaitu:</p>
<ul>
<li>domain object : class yang mewakili struktur data dalam aplikasi kita</li>
<li>interface business service : class yang mendefinisikan daftar fitur-fitur dalam aplikasi</li>
<li>implementasi business service : implementasi dari interface business service. Kalau di interface hanya ada nama method, argumen, dan tipe data kembalian (return value), di sini sudah ada implementasi konkritnya, yaitu bagaimana query database, logika perhitungan, dan sebagainya.</li>
<li>automated test : memeriksa apakah method kita berjalan benar itu melelahkan. Jadi kita buatkan kode program untuk mengetesnya, sehingga tes yang sama bisa dijalankan berulang-ulang tanpa membuat kita lelah. Lebih lanjut tentang automated test bisa dibaca di <a href="http://software.endy.muhardin.com/java/ruthless-testing-1/">artikel lain yang membahas masalah ini</a>.</li>
</ul>
<h3 id="domain-object">Domain Object</h3>
<p>Sesuai dengan skema database, kita akan membuat tiga class, yaitu:</p>
<ul>
<li>Produk</li>
<li>Penjualan</li>
<li>PenjualanDetail</li>
</ul>
<p>Buat yang sudah pernah coding JDBC biasanya akan bertanya,</p>
<blockquote>
<p>Kenapa repot-repot membuat domain class, kemudian harus konversi bolak balik?
Kan bisa saja kita kirim <code class="language-plaintext highlighter-rouge">ResultSet</code> ke tampilan, ataupun insert langsung dari array ke <code class="language-plaintext highlighter-rouge">PreparedStatement</code>.</p>
</blockquote>
<p>Pertanyaan ini biasanya muncul dari programmer PHP yang terbiasa langsung menampilkan kembalian <code class="language-plaintext highlighter-rouge">mysql_fetch_array</code> dalam looping tabel.</p>
<p>Ada beberapa alasan:</p>
<ol>
<li>
<p>Sebenarnya bisa saja kita buat aplikasi dengan menggunakan tipe data yang disediakan Java seperti Integer, String, Map, List, dan lainnya. Tapi akibatnya kode program kita menjadi sulit dimengerti. Coba bandingkan, lebih mudah dimengerti <code class="language-plaintext highlighter-rouge">public void simpan(Produk p)</code> atau <code class="language-plaintext highlighter-rouge">public void simpan(Map p)</code>? Dengan membuat tipe data sesuai istilah yang digunakan di domain kita, maka kode program akan lebih mudah dipahami.</p>
</li>
<li>
<p>Java merupakan bahasa yang <a href="http://en.wikipedia.org/wiki/Strong_and_weak_typing">strongly-typed</a>, dia memeriksa tipe data/class dari tiap variabel. Pada ilustrasi di atas, method <code class="language-plaintext highlighter-rouge">public void simpan(Map p)</code> akan menerima apapun data yang kita masukkan ke dalam variabel <code class="language-plaintext highlighter-rouge">p</code>. Kalau ada kesalahan dalam nama variabel (misalnya nama ditulis name), baru akan terdeteksi pada waktu aplikasi dijalankan. Berbeda dengan <code class="language-plaintext highlighter-rouge">public void simpan(Produk p)</code> yang akan langsung menimbulkan pesan error apabila kita isi dengan tipe data selain <code class="language-plaintext highlighter-rouge">Produk</code>. Bug yang ditemukan pada waktu coding (compile-time) akan jauh lebih cepat diperbaiki daripada bug yang baru ditemukan pada waktu aplikasi dijalankan (runtime). Programmer PHP ada benarnya juga. Di bahasa PHP memang domain class ini tidak diperlukan, karena PHP tidak ada pemeriksaan compile-time. Tapi karena kita menggunakan Java, ada baiknya kita manfaatkan pemeriksaan compile-time ini.</p>
</li>
<li>
<p>Memisahkan antara layer database dan layer antarmuka. Apabila ada perubahan skema database, asalkan fitur di tampilan tidak berubah, kita cukup mengubah mapping domain object dan skema database. Tidak perlu mengubah kode program di layer antarmuka.</p>
</li>
<li>
<p>Pustaka siap pakai untuk validasi. Di Java, ada yang namanya <a href="http://jcp.org/en/jsr/detail?id=303">JSR-303</a>, yaitu suatu pustaka yang berguna untuk validasi. Dengan <a href="http://www.mkyong.com/spring-mvc/spring-3-mvc-and-jsr303-valid-example/">menggunakan JSR-303</a> ini kita tidak perlu lagi melakukan pengecekan <code class="language-plaintext highlighter-rouge">if(produk.getKode() == null)</code>. Cukup kita gunakan deklarasi <code class="language-plaintext highlighter-rouge">@NotNull private String kode;</code> dalam class <code class="language-plaintext highlighter-rouge">Produk</code></p>
</li>
</ol>
<h3 id="interface-business-service">Interface Business Service</h3>
<p>Interface di Java artinya class yang methodnya abstrak semua. Lebih detail tentang method abstrak bisa dibaca di <a href="http://software.endy.muhardin.com/java/interface-abstract/">artikel ini</a>. Ada beberapa alasan kenapa kita harus memisahkan interface dan implementasinya, antara lain:</p>
<ul>
<li>
<p>pada waktu <a href="http://software.endy.muhardin.com/java/remoting-dengan-spring/">membuat aplikasi client-server</a>, kita cukup memberikan domain object dan interface ini kepada programmer aplikasi client. Sedangkan implementasinya (yang berisi kode program akses database) tetap di server. Ini akan meringankan ukuran aplikasi client, karena tidak perlu menyertakan implementasi (beserta library pendukungnya yang biasanya besar) yang tidak dia gunakan.</p>
</li>
<li>
<p>kita bebas mengubah strategi implementasi (misalnya ganti database dari MySQL menjadi PostgreSQL) tanpa perlu mengganggu aplikasi client</p>
</li>
<li>
<p>fitur declarative transaction yang dimiliki Spring akan lebih optimal bekerja bila kita memisahkan interface dan implementasi.</p>
</li>
</ul>
<p>Interface ini cukup satu class saja, yaitu <code class="language-plaintext highlighter-rouge">AplikasiPenjualanService</code>.</p>
<h3 id="implementasi-business-service">Implementasi Business Service</h3>
<p>Ini merupakan implementasi dari interface <code class="language-plaintext highlighter-rouge">AplikasiPenjualanService</code>. Pada prakteknya, ada dua variasi yang biasa saya gunakan dalam membuat implementasi, yaitu:</p>
<ul>
<li>cukup membuat class implementasi service saja</li>
<li>membuat class implementasi service dan juga class data access object (DAO)</li>
</ul>
<p>Kapan memilih variasi yang mana?</p>
<ul>
<li>Bila menggunakan framework Spring Data JPA, kita harus pakai DAO karena frameworknya minta seperti itu</li>
<li>Selain Spring Data JPA, bebas mau pakai yang mana. Pilih saja yang lebih rapi dan mudah maintenance. Untuk aplikasi kecil, class implementasi service saja sudah cukup. Kalau aplikasinya besar, akan lebih mudah membaca 10 class DAO yang masing-masingnya terdiri dari 100 baris kode daripada 1000 baris dalam satu class implementasi service. Walaupun demikian, tidak ada pertimbangan teknis yang signifikan (seperti isu performance dan lainnya) antara pakai DAO atau tidak.</li>
</ul>
<h3 id="automated-test">Automated Test</h3>
<p>Ini adalah kode program yang fungsinya mengetes kode program lainnya, dalam hal ini class implementasi dan class DAO. Konsep dasar tentang automated testing dibahas di <a href="http://software.endy.muhardin.com/java/ruthless-testing-1/">artikel ini</a>. Sedangkan untuk pengetesan database dibahas <a href="http://software.endy.muhardin.com/java/ruthless-testing-4/">di sini</a>.</p>
<p>Pada contoh aplikasi ini, kita menghadapi tantangan khusus, yaitu bagaimana caranya menggunakan test case yang sama untuk konfigurasi berbeda. Nantinya aplikasi ini akan dikembangkan untuk mendemonstrasikan akses database menggunakan framework lain seperti Hibernate, Spring Data JPA, dan JDBC polos tanpa framework. Logika pengetesan akan sama persis, yaitu:</p>
<ul>
<li>test insert</li>
<li>test update</li>
<li>test delete</li>
<li>test cari berdasarkan id</li>
<li>test ambil semua data dari tabel tertentu</li>
<li>test cari data dengan kriteria tertentu</li>
</ul>
<p>Tabel database yang diakses sama, sample data sama, bahkan nama method yang dijalankan juga sama. Bedanya hanyalah class implementasi mana yang digunakan dan konfigurasi mana yang dipakai.</p>
<p>Untuk itu, kita akan menggunakan teknik khusus yang disebut <code class="language-plaintext highlighter-rouge">abstract junit test case</code>. Secara garis besar, langkahnya seperti ini:</p>
<ol>
<li>Buat semua method test di superclass. Superclass ini memiliki abstract method, sehingga dengan sendirinya dia juga abstract.</li>
<li>Untuk mendapatkan class implementasi dan melakukan inisialisasi konfigurasi, gunakan abstract method</li>
<li>Buat subclass untuk masing-masing implementasi (Spring JDBC, Hibernate, dst) yang hanya berisi implementasi dari abstract method tersebut.</li>
</ol>
<p>Agar lebih jelas, silahkan lihat <a href="https://github.com/endymuhardin/belajar-akses-database-java/blob/spring-jdbc/src/test/java/com/muhardin/endy/training/java/aksesdb/service/ProdukServiceTest.java">superclassnya</a> dan <a href="https://github.com/endymuhardin/belajar-akses-database-java/blob/spring-jdbc/src/test/java/com/muhardin/endy/training/java/aksesdb/service/springjdbc/ProdukServiceSpringJdbcTest.java">subclass untuk Spring JDBC</a>.</p>
<h2 id="struktur-folder">Struktur folder</h2>
<p>Sekian banyak class, bagaimana penempatannya? Silahkan lihat struktur folder berikut:</p>
<p><a href="/images/uploads/2013/05/struktur-aplikasi-spring-maven/01-struktur-folder-top-level.png"><img src="/images/uploads/2013/05/struktur-aplikasi-spring-maven/01-struktur-folder-top-level.png" alt="Top Level Folder " /></a></p>
<p>Tidak ada yang istimewa dari struktur di atas, cuma struktur folder standar Maven. Mari kita lihat source code aplikasi.</p>
<p><a href="/images/uploads/2013/05/struktur-aplikasi-spring-maven/02-struktur-folder-main-java.png"><img src="/images/uploads/2013/05/struktur-aplikasi-spring-maven/02-struktur-folder-main-java.png" alt="Source Folder Java " /></a></p>
<p>Di sini kita bisa lihat class sudah diatur ke dalam package berbeda sesuai fungsinya, yaitu domain, service, dao. Untuk implementasi service dengan Spring JDBC kita buatkan package tersendiri. Selanjutnya kita lihat lokasi file konfigurasi.</p>
<p><a href="/images/uploads/2013/05/struktur-aplikasi-spring-maven/03-struktur-folder-main-resources.png"><img src="/images/uploads/2013/05/struktur-aplikasi-spring-maven/03-struktur-folder-main-resources.png" alt="Source Folder Konfigurasi " /></a></p>
<p>File konfigurasi ditaruh dalam package. Sebetulnya ditaruh di top level juga boleh, ini hanya sekedar kebiasaan saja.</p>
<p><a href="/images/uploads/2013/05/struktur-aplikasi-spring-maven/04-struktur-folder-test.png"><img src="/images/uploads/2013/05/struktur-aplikasi-spring-maven/04-struktur-folder-test.png" alt="Source Test Folder " /></a></p>
<p>Lokasi penempatan test class bisa dilihat di atas. Abstract class yang saya ceritakan di atas terlihat di package <code class="language-plaintext highlighter-rouge">com.muhardin.endy.training.java.aksesdb.service</code>, sedangkan implementasi konfigurasinya ada di subpackage <code class="language-plaintext highlighter-rouge">springjdbc</code> di bawahnya.</p>
<p>Setelah kita melihat penempatan file dan folder, mari kita lihat kerangka kode program di masing-masing class/file. Supaya bisa mendapatkan <em>big-picture</em>, kita akan lihat kerangka class dan method saja. Implementasinya menyusul pada bagian selanjutnya.</p>
<h1 id="domain-object-1">Domain Object</h1>
<h2 id="class-produk">Class Produk</h2>
<p>Class ini merupakan padanan tabel m_produk di database.
Dia memiliki beberapa property sesuai dengan kolom di database.
Berikut penjelasannya</p>
<table>
<thead>
<tr>
<th>Nama Property</th>
<th>Nama Kolom Database</th>
<th>Tipe Data Java</th>
<th>Tipe Data MySQL</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>id</td>
<td>Integer</td>
<td>integer</td>
</tr>
<tr>
<td>kode</td>
<td>kode</td>
<td>String</td>
<td>varchar</td>
</tr>
<tr>
<td>nama</td>
<td>nama</td>
<td>String</td>
<td>varchar</td>
</tr>
<tr>
<td>harga</td>
<td>harga</td>
<td>BigDecimal</td>
<td>decimal(19,2)</td>
</tr>
</tbody>
</table>
<p>Berikut kode program untuk class Produk.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.domain</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.math.BigDecimal</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Produk</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">kode</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">nama</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">harga</span><span class="o">;</span>
<span class="c1">// getter dan setter generate dari IDE</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="class-penjualan">Class Penjualan</h2>
<p>Mapping Java ke SQL</p>
<table>
<thead>
<tr>
<th>Nama Property</th>
<th>Nama Kolom Database</th>
<th>Tipe Data Java</th>
<th>Tipe Data MySQL</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>id</td>
<td>Integer</td>
<td>integer</td>
</tr>
<tr>
<td>waktuTransaksi</td>
<td>waktu_transaksi</td>
<td>Date</td>
<td>datetime</td>
</tr>
</tbody>
</table>
<p>Kode program class Penjualan</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.domain</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Penjualan</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">waktuTransaksi</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="n">daftarPenjualanDetail</span>
<span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">>();</span>
<span class="c1">// getter dan setter generate dari IDE</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="class-penjualan-detail">Class Penjualan Detail</h2>
<p>Mapping Java ke SQL</p>
<table>
<thead>
<tr>
<th>Nama Property</th>
<th>Nama Kolom Database</th>
<th>Tipe Data Java</th>
<th>Tipe Data MySQL</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>id</td>
<td>Integer</td>
<td>integer</td>
</tr>
<tr>
<td>penjualan</td>
<td>id_penjualan</td>
<td>Penjualan</td>
<td>integer foreign key</td>
</tr>
<tr>
<td>produk</td>
<td>id_produk</td>
<td>Produk</td>
<td>integer foreign key</td>
</tr>
<tr>
<td>jumlah</td>
<td>jumlah</td>
<td>Integer</td>
<td>integer</td>
</tr>
<tr>
<td>harga</td>
<td>harga</td>
<td>BigDecimal</td>
<td>decimal(19,2)</td>
</tr>
</tbody>
</table>
<p>Kode program class PenjualanDetail</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.domain</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.math.BigDecimal</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PenjualanDetail</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Penjualan</span> <span class="n">penjualan</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Produk</span> <span class="n">produk</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">harga</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">jumlah</span><span class="o">;</span>
<span class="c1">// getter dan setter generate dari IDE</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Yang perlu diperhatikan di sini adalah perbedaan cara perlakuan relasi antara Java dan database.
Di Java, kita perlu mendefinisikan relasi di dua tempat, yaitu variabel <code class="language-plaintext highlighter-rouge">daftarPenjualanDetail</code> di class <code class="language-plaintext highlighter-rouge">Penjualan</code>
dan variabel <code class="language-plaintext highlighter-rouge">penjualan</code> di class <code class="language-plaintext highlighter-rouge">PenjualanDetail</code>. Sedangkan di database, relasi ini cukup didefinisikan melalui foreign key
<code class="language-plaintext highlighter-rouge">id_penjualan</code> di tabel <code class="language-plaintext highlighter-rouge">t_penjualan_detail</code>.</p>
<p>Perbedaan lain, di database relasi ini cukup diwakili satu nilai saja, yaitu nilai foreign key.
Sedangkan di Java diwakili satu class penuh (<code class="language-plaintext highlighter-rouge">Produk</code> atau <code class="language-plaintext highlighter-rouge">Penjualan</code>) yang di dalamnya memuat banyak nilai.</p>
<p>Untuk menjembatani perbedaan ini, kita perlu membuat mapper untuk mengubah data dari database menjadi object Java dan sebaliknya. Contoh kode program untuk mapper ini akan dibahas pada bagian selanjutnya.</p>
<h1 id="interface-business-service-1">Interface Business Service</h1>
<p>Ini adalah daftar fitur yang ada di aplikasi, didefinisikan berupa class/interface dan method.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.service</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">PenjualanService</span> <span class="o">{</span>
<span class="c1">// service berkaitan dengan produk</span>
<span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">);</span>
<span class="nc">Produk</span> <span class="nf">cariProdukById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">);</span>
<span class="nc">Produk</span> <span class="nf">cariProdukByKode</span><span class="o">(</span><span class="nc">String</span> <span class="n">kode</span><span class="o">);</span>
<span class="nc">Long</span> <span class="nf">hitungSemuaProduk</span><span class="o">();</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="nf">cariSemuaProduk</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">);</span>
<span class="nc">Long</span> <span class="nf">hitungProdukByNama</span><span class="o">(</span><span class="nc">String</span> <span class="n">nama</span><span class="o">);</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="nf">cariProdukByNama</span><span class="o">(</span><span class="nc">String</span> <span class="n">nama</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">);</span>
<span class="c1">// service yang berkaitan dengan transaksi</span>
<span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Penjualan</span> <span class="n">p</span><span class="o">);</span>
<span class="nc">Penjualan</span> <span class="nf">cariPenjualanById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">);</span>
<span class="nc">Long</span> <span class="nf">hitungPenjualanByPeriode</span><span class="o">(</span><span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">);</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Penjualan</span><span class="o">></span> <span class="nf">cariPenjualanByPeriode</span><span class="o">(</span><span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">);</span>
<span class="nc">Long</span> <span class="nf">hitungPenjualanDetailByProdukDanPeriode</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">);</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="nf">cariPenjualanDetailByProdukDanPeriode</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h1 id="implementasi-business-service-1">Implementasi Business Service</h1>
<p>Implementasi dari interface di atas kita bagi menjadi dua kategori, yaitu class implementasi service yang nantinya akan memanggil class DAO. Pertimbangan dan alasan mengapa begini sudah dijelaskan di atas.</p>
<h2 id="class-servicespringjdbc">Class ServiceSpringJdbc</h2>
<p>Class ini sebetulnya hanya memanggil class DAO saja, jadi baiklah kita tampilkan seluruh isinya di sini.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.service.springjdbc</span><span class="o">;</span>
<span class="c1">// import statement generate dari IDE</span>
<span class="nd">@Service</span> <span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PenjualanServiceSpringJdbc</span> <span class="kd">implements</span> <span class="nc">PenjualanService</span><span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">ProdukDao</span> <span class="n">produkDao</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">PenjualanDao</span> <span class="n">penjualanDao</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">PenjualanDetailDao</span> <span class="n">penjualanDetailDao</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="n">produkDao</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">cariProdukById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">produkDao</span><span class="o">.</span><span class="na">cariById</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">cariProdukByKode</span><span class="o">(</span><span class="nc">String</span> <span class="n">kode</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">produkDao</span><span class="o">.</span><span class="na">cariByKode</span><span class="o">(</span><span class="n">kode</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">hitungSemuaProduk</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">produkDao</span><span class="o">.</span><span class="na">hitungSemua</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="nf">cariSemuaProduk</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">produkDao</span><span class="o">.</span><span class="na">cariSemua</span><span class="o">(</span><span class="n">halaman</span><span class="o">,</span> <span class="n">baris</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">hitungProdukByNama</span><span class="o">(</span><span class="nc">String</span> <span class="n">nama</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">produkDao</span><span class="o">.</span><span class="na">hitungByNama</span><span class="o">(</span><span class="n">nama</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="nf">cariProdukByNama</span><span class="o">(</span><span class="nc">String</span> <span class="n">nama</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">produkDao</span><span class="o">.</span><span class="na">cariByNama</span><span class="o">(</span><span class="n">nama</span><span class="o">,</span> <span class="n">halaman</span><span class="o">,</span> <span class="n">baris</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Penjualan</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span>
<span class="n">penjualanDao</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Penjualan</span> <span class="nf">cariPenjualanById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">penjualanDao</span><span class="o">.</span><span class="na">cariById</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">hitungPenjualanByPeriode</span><span class="o">(</span><span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">penjualanDao</span><span class="o">.</span><span class="na">hitungByPeriode</span><span class="o">(</span><span class="n">mulai</span><span class="o">,</span> <span class="n">sampai</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Penjualan</span><span class="o">></span> <span class="nf">cariPenjualanByPeriode</span><span class="o">(</span><span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">,</span>
<span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">penjualanDao</span><span class="o">.</span><span class="na">cariByPeriode</span><span class="o">(</span><span class="n">mulai</span><span class="o">,</span> <span class="n">sampai</span><span class="o">,</span> <span class="n">halaman</span><span class="o">,</span> <span class="n">baris</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">hitungPenjualanDetailByProdukDanPeriode</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">,</span>
<span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">penjualanDetailDao</span><span class="o">.</span><span class="na">hitungByProdukDanPeriode</span><span class="o">(</span><span class="n">p</span><span class="o">,</span> <span class="n">mulai</span><span class="o">,</span> <span class="n">sampai</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="nf">cariPenjualanDetailByProdukDanPeriode</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">,</span>
<span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">penjualanDetailDao</span><span class="o">.</span><span class="na">cariByProdukDanPeriode</span><span class="o">(</span><span class="n">p</span><span class="o">,</span> <span class="n">mulai</span><span class="o">,</span> <span class="n">sampai</span><span class="o">,</span> <span class="n">halaman</span><span class="o">,</span> <span class="n">baris</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="class-dao">Class DAO</h2>
<p>Class DAO akan kita bahas secara mendetail di bagian selanjutnya. Pada kesempatan ini kita hanya tampilkan deklarasi class dan method saja supaya jelas mana method yang dipanggil dari implementasi service di atas.</p>
<h3 id="produkdao">ProdukDAO</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.dao.springjdbc</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">cariById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">cariByKode</span><span class="o">(</span><span class="nc">String</span> <span class="n">kode</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">hitungSemua</span><span class="o">()</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="nf">cariSemua</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">hitungByNama</span><span class="o">(</span><span class="nc">String</span> <span class="n">nama</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="nf">cariByNama</span><span class="o">(</span><span class="nc">String</span> <span class="n">nama</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">private</span> <span class="kd">class</span> <span class="nc">ResultSetJadiProduk</span> <span class="kd">implements</span> <span class="nc">RowMapper</span><span class="o"><</span><span class="nc">Produk</span><span class="o">></span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">mapRow</span><span class="o">(</span><span class="nc">ResultSet</span> <span class="n">rs</span><span class="o">,</span> <span class="kt">int</span> <span class="n">i</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="penjualandao">PenjualanDao</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.dao.springjdbc</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PenjualanDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Penjualan</span> <span class="n">p</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">Penjualan</span> <span class="nf">cariById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">hitungByPeriode</span><span class="o">(</span><span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Penjualan</span><span class="o">></span> <span class="nf">cariByPeriode</span><span class="o">(</span><span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">private</span> <span class="kd">class</span> <span class="nc">ResultSetJadiPenjualan</span> <span class="kd">implements</span> <span class="nc">RowMapper</span><span class="o"><</span><span class="nc">Penjualan</span><span class="o">></span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Penjualan</span> <span class="nf">mapRow</span><span class="o">(</span><span class="nc">ResultSet</span> <span class="n">rs</span><span class="o">,</span> <span class="kt">int</span> <span class="n">i</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="penjualandetaildao">PenjualanDetailDao</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.dao.springjdbc</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PenjualanDetailDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="kd">final</span> <span class="nc">PenjualanDetail</span> <span class="n">p</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="nf">cariByPenjualan</span><span class="o">(</span><span class="nc">Penjualan</span> <span class="n">p</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">hitungByProdukDanPeriode</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="nf">cariByProdukDanPeriode</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">mulai</span><span class="o">,</span> <span class="nc">Date</span> <span class="n">sampai</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">halaman</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">baris</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">private</span> <span class="kd">class</span> <span class="nc">ResultSetJadiPenjualanDetail</span> <span class="kd">implements</span> <span class="nc">RowMapper</span><span class="o"><</span><span class="nc">PenjualanDetail</span><span class="o">></span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">PenjualanDetail</span> <span class="nf">mapRow</span><span class="o">(</span><span class="nc">ResultSet</span> <span class="n">rs</span><span class="o">,</span> <span class="kt">int</span> <span class="n">i</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h1 id="automated-test-1">Automated Test</h1>
<p>Seperti dijelaskan di atas, automated test kita bagi menjadi dua kategori, yaitu abstract class yang menampung semua logika pengetesan, dan concrete class yang menyediakan konfigurasi.</p>
<h2 id="abstract-base-class">Abstract Base Class</h2>
<h3 id="produkservicetest">ProdukServiceTest</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.service</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">ProdukServiceTest</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">PenjualanService</span> <span class="nf">getPenjualanService</span><span class="o">();</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">DataSource</span> <span class="nf">getDataSource</span><span class="o">();</span>
<span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">bersihkanDataTest</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testSimpanProduk</span><span class="o">()</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariProdukById</span><span class="o">()</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariProdukByKode</span><span class="o">()</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testHitungSemuaProduk</span><span class="o">()</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariSemuaProduk</span><span class="o">()</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testHitungProdukByNama</span><span class="o">()</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariProdukByNama</span><span class="o">()</span> <span class="o">{}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="penjualanservicetest">PenjualanServiceTest</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.service</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">PenjualanServiceTest</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">PenjualanService</span> <span class="nf">getPenjualanService</span><span class="o">();</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">DataSource</span> <span class="nf">getDataSource</span><span class="o">();</span>
<span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">bersihkanDataTest</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testSimpanPenjualan</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariPenjualanById</span><span class="o">(){}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testHitungPenjualanByPeriode</span><span class="o">(){}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariPenjualanByPeriode</span><span class="o">(){}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testHitungPenjualanDetailByProdukDanPeriode</span><span class="o">(){}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCariPenjualanDetailByProdukDanPeriode</span><span class="o">(){}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Seperti kita lihat di atas, kedua class tersebut memiliki dua abstract method, yaitu:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">public abstract PenjualanService getPenjualanService()</code></li>
<li><code class="language-plaintext highlighter-rouge">public abstract DataSource getDataSource();</code></li>
</ul>
<p>Kedua object <code class="language-plaintext highlighter-rouge">PenjualanService</code> dan <code class="language-plaintext highlighter-rouge">DataSource</code> didapatkan dari konfigurasi Spring.
Konfigurasi Spring dibuat berdasarkan teknologi yang digunakan.
Konfigurasi Spring JDBC berbeda dengan konfigurasi Hibernate ataupun Spring Data JPA.</p>
<p>Dengan teknik ini, bila di kemudian hari kita membuat implementasi dengan Hibernate atau Spring Data JPA,
kita tidak perlu lagi copy-paste test class, cukup buat subclass yang menyediakan kedua object tersebut.</p>
<p>Berikut adalah subclassnya</p>
<h2 id="implementasi-test-business-service">Implementasi Test Business Service</h2>
<p>Karena hanya beberapa baris dan tidak butuh banyak penjelasan, kita tampilkan di sini full source code, bukan hanya kerangkanya saja.</p>
<h3 id="produkservicespringjdbctest">ProdukServiceSpringJdbcTest</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.service.springjdbc</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="nd">@RunWith</span><span class="o">(</span><span class="nc">SpringJUnit4ClassRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@ContextConfiguration</span><span class="o">(</span><span class="s">"classpath*:com/muhardin/**/spring-jdbc-ctx.xml"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukServiceSpringJdbcTest</span> <span class="kd">extends</span> <span class="nc">ProdukServiceTest</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">PenjualanService</span> <span class="n">penjualanService</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">PenjualanService</span> <span class="nf">getPenjualanService</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">penjualanService</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">DataSource</span> <span class="nf">getDataSource</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="penjualanservicespringjdbctest">PenjualanServiceSpringJdbcTest</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.muhardin.endy.training.java.aksesdb.service.springjdbc</span><span class="o">;</span>
<span class="c1">// import generate dari IDE</span>
<span class="nd">@RunWith</span><span class="o">(</span><span class="nc">SpringJUnit4ClassRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@ContextConfiguration</span><span class="o">(</span><span class="s">"classpath*:com/muhardin/**/spring-jdbc-ctx.xml"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PenjualanServiceSpringJdbcTest</span> <span class="kd">extends</span> <span class="nc">PenjualanServiceTest</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">PenjualanService</span> <span class="n">penjualanService</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">PenjualanService</span> <span class="nf">getPenjualanService</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">penjualanService</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">DataSource</span> <span class="nf">getDataSource</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Seperti kita lihat di atas, <code class="language-plaintext highlighter-rouge">dataSource</code> dan <code class="language-plaintext highlighter-rouge">penjualanService</code> disediakan melalui Dependency Injection.</p>
<p>Cara kerjanya sebagai berikut:</p>
<ol>
<li>Kita jalankan JUnit melalui IDE atau Maven. IDE atau Maven akan membaca semua file dalam folder <code class="language-plaintext highlighter-rouge">src/test/java</code> dan memproses semua class yang namanya berakhiran <code class="language-plaintext highlighter-rouge">Test</code> seperti <code class="language-plaintext highlighter-rouge">PenjualanServiceSpringJdbcTest</code>. IDE/Maven juga memproses <code class="language-plaintext highlighter-rouge">PenjualanServiceTest</code>, tapi karena classnya abstract maka tidak diproses lebih lanjut.</li>
<li>JUnit melihat annotation <code class="language-plaintext highlighter-rouge">@RunWith</code>, jadi dia tidak menjalankan sendiri melainkan menyuruh <code class="language-plaintext highlighter-rouge">SpringJUnit4ClassRunner</code> untuk menjalankan test</li>
<li><code class="language-plaintext highlighter-rouge">SpringJUnit4ClassRunner</code> membaca annotation <code class="language-plaintext highlighter-rouge">@ContextConfiguration</code>, lalu menggunakan nilai di dalamnya untuk melakukan inisialisasi <code class="language-plaintext highlighter-rouge">ApplicationContext</code>, kemudian mengisi variabel yang ditandai dengan <code class="language-plaintext highlighter-rouge">@Autowired</code></li>
<li>Karena <code class="language-plaintext highlighter-rouge">PenjualanServiceSpringJdbcTest</code> merupakan subclass dari <code class="language-plaintext highlighter-rouge">PenjualanServiceTest</code>, maka dia akan mewarisi semua method <code class="language-plaintext highlighter-rouge">@Test</code> yang dimiliki <code class="language-plaintext highlighter-rouge">PenjualanServiceTest</code>. Method <code class="language-plaintext highlighter-rouge">@Test</code> ini akan dijalankan oleh IDE/Maven.</li>
<li>Pada waktu method <code class="language-plaintext highlighter-rouge">@Test</code> dijalankan, bila perlu object <code class="language-plaintext highlighter-rouge">PenjualanService</code>, maka akan didapat dengan cara memanggil method <code class="language-plaintext highlighter-rouge">getPenjualanService</code>. Karena methodnya sudah dibuatkan implementasinya (tidak abstract lagi) dan sudah ada isinya, maka method <code class="language-plaintext highlighter-rouge">@Test</code> dapat bekerja dengan baik.</li>
</ol>
<p>Demikianlah bagian kedua dari tutorial mengakses database menggunakan Spring JDBC. Pada bagian ini kita sudah melihat bagaimana cara pengaturan file/folder dan interaksi antar class/method. Di bagian selanjutnya kita akan lihat <a href="http://software.endy.muhardin.com/java/insert-update-delete-dengan-spring-jdbc/">bagaimana cara menjalankan perintah SQL</a>.</p>
Konfigurasi Koneksi Database dengan Spring2013-05-27T17:02:00+07:00https://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring<p>Di Java, ada banyak cara untuk mengakses database, diantaranya:</p>
<ul>
<li><a href="http://docs.oracle.com/javase/tutorial/jdbc/">JDBC polos tanpa framework apapun</a></li>
<li><a href="http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/jdbc.html">JDBC dengan Spring (Spring JDBC)</a></li>
<li>JDBC dengan <a href="http://blog.mybatis.org/">iBatis/MyBatis</a></li>
<li><a href="http://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/">Hibernate</a></li>
<li><a href="http://www.datanucleus.org/products/datanucleus/jdo/guides/tutorial_rdbms.html">JDO</a></li>
<li><a href="http://docs.oracle.com/javaee/6/tutorial/doc/bnbpz.html">JPA</a></li>
<li><a href="http://www.springsource.org/spring-data/jpa">Spring Data JPA</a></li>
</ul>
<p>Masing-masing memiliki kelebihan dan kekurangan masing-masing yang tidak akan kita bahas di artikel ini.
Kali ini kita hanya akan membahas metode Spring JDBC dan perbandingannya dengan JDBC murni.</p>
<p>Artikel ini merupakan bagian pertama dari rangkaian artikel Spring JDBC, yaitu</p>
<ol>
<li><a href="http://software.endy.muhardin.com/java/konfigurasi-koneksi-database-dengan-spring/">Konfigurasi koneksi database</a></li>
<li><a href="http://software.endy.muhardin.com/java/struktur-aplikasi-java-dengan-spring-dan-maven/">Struktur Aplikasi</a></li>
<li><a href="http://software.endy.muhardin.com/java/insert-update-delete-dengan-spring-jdbc/">Insert, update, dan delete data</a></li>
<li><a href="http://software.endy.muhardin.com/java/query-dengan-spring-jdbc/">Query data</a></li>
<li><a href="http://software.endy.muhardin.com/java/mengetes-akses-database/">Mengetes Akses Database</a></li>
</ol>
<p>Keseluruhan kode program dapat dilihat di <a href="https://github.com/endymuhardin/belajar-akses-database-java">repository belajar-akses-database-java di Github saya</a>, khususnya <a href="https://github.com/endymuhardin/belajar-akses-database-java/tree/spring-jdbc">branch spring-jdbc</a>.</p>
<!--more-->
<h2 id="perbandingan-jdbc-polos-dan-spring-jdbc">Perbandingan JDBC polos dan Spring JDBC</h2>
<p>Menggunakan JDBC polos tanpa library memang mudah, karena tidak perlu pusing mempelajari library lain. Tapi ada beberapa keterbatasan dan kesulitan, diantaranya:</p>
<ul>
<li>semua method throws Exception, sehingga kode program kita menjadi kotor dengan try-catch</li>
<li>tidak ada manajemen koneksi database, kita harus buka-tutup sendiri</li>
<li>tidak ada declarative transaction, kita harus secara manual melakukan begin-commit/rollback</li>
</ul>
<p>Dengan berbagai keterbatasan tersebut, ada baiknya kita menggunakan bantuan Spring Framework, yaitu modul <code class="language-plaintext highlighter-rouge">spring-jdbc</code> untuk memudahkan berbagai kegiatan di atas. Tentunya masih banyak hal yang harus kita lakukan sendiri, tidak seperti penggunaan library Object Relational Mapping (ORM) seperti Hibernate atau JPA, di antaranya:</p>
<ul>
<li>mapping dari result set menjadi Java object</li>
<li>mapping dari Java object menjadi isi parameter PreparedStatement</li>
<li>cache</li>
<li>cascading operation</li>
<li>generate primary key secara otomatis</li>
<li>dsb</li>
</ul>
<p>Ada beberapa tahapan dalam menggunakan Spring JDBC, yaitu :</p>
<ol>
<li>Konfigurasi Koneksi Database</li>
<li>Membuat class Data Access Object (DAO)</li>
<li>Membuat class implementasi business process/service</li>
<li>Membuat test otomatis menggunakan JUnit</li>
</ol>
<h1 id="studi-kasus">Studi Kasus</h1>
<p>Agar lebih konkrit, kita akan menggunakan skema database yang umum digunakan di aplikasi bisnis,
yaitu memiliki tabel :</p>
<ul>
<li>Master Data / Referensi</li>
<li>Header Transaksi</li>
<li>Detail Transaksi</li>
</ul>
<p>Berikut adalah skema database yang akan kita gunakan:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">m_produk</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">int</span> <span class="k">primary</span> <span class="k">key</span> <span class="n">auto_increment</span><span class="p">,</span>
<span class="n">kode</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">nama</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">harga</span> <span class="nb">decimal</span><span class="p">(</span><span class="mi">19</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span>
<span class="p">)</span> <span class="n">engine</span><span class="o">=</span><span class="n">InnoDB</span> <span class="p">;</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">t_penjualan</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">int</span> <span class="k">primary</span> <span class="k">key</span> <span class="n">auto_increment</span><span class="p">,</span>
<span class="n">waktu_transaksi</span> <span class="nb">datetime</span> <span class="k">not</span> <span class="k">null</span>
<span class="p">)</span> <span class="n">engine</span><span class="o">=</span><span class="n">InnoDB</span> <span class="n">AUTO_INCREMENT</span><span class="o">=</span><span class="mi">100</span><span class="p">;</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">t_penjualan_detail</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">int</span> <span class="k">primary</span> <span class="k">key</span> <span class="n">auto_increment</span><span class="p">,</span>
<span class="n">id_penjualan</span> <span class="nb">int</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">id_produk</span> <span class="nb">int</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">harga</span> <span class="nb">decimal</span><span class="p">(</span><span class="mi">19</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">jumlah</span> <span class="nb">int</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="k">foreign</span> <span class="k">key</span><span class="p">(</span><span class="n">id_penjualan</span><span class="p">)</span> <span class="k">references</span> <span class="n">t_penjualan</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">on</span> <span class="k">delete</span> <span class="k">cascade</span><span class="p">,</span>
<span class="k">foreign</span> <span class="k">key</span><span class="p">(</span><span class="n">id_produk</span><span class="p">)</span> <span class="k">references</span> <span class="n">m_produk</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">on</span> <span class="k">delete</span> <span class="k">restrict</span>
<span class="p">)</span> <span class="n">engine</span><span class="o">=</span><span class="n">InnoDB</span> <span class="n">AUTO_INCREMENT</span><span class="o">=</span><span class="mi">100</span><span class="p">;</span>
</code></pre></div></div>
<p>Untuk keperluan test, jangan lupa kita sertakan beberapa baris data.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">m_produk</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="n">kode</span><span class="p">,</span><span class="n">nama</span><span class="p">,</span><span class="n">harga</span><span class="p">)</span> <span class="k">values</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'K-001'</span><span class="p">,</span> <span class="s1">'Keyboard USB'</span><span class="p">,</span> <span class="mi">150000</span><span class="p">),</span>
<span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">'M-001'</span><span class="p">,</span> <span class="s1">'Mouse USB'</span><span class="p">,</span> <span class="mi">50000</span><span class="p">),</span>
<span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s1">'L-001'</span><span class="p">,</span> <span class="s1">'Laptop'</span><span class="p">,</span> <span class="mi">10000000</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">t_penjualan</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="n">waktu_transaksi</span><span class="p">)</span> <span class="k">values</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="s1">'2013-01-01 20:30:30'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="s1">'2013-01-02 15:15:15'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="s1">'2013-02-02 09:09:09'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">t_penjualan_detail</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="n">id_penjualan</span><span class="p">,</span> <span class="n">id_produk</span><span class="p">,</span> <span class="n">harga</span><span class="p">,</span> <span class="n">jumlah</span><span class="p">)</span> <span class="k">values</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">150000</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
<span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">50000</span><span class="p">,</span><span class="mi">5</span><span class="p">),</span>
<span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">150000</span><span class="p">,</span><span class="mi">3</span><span class="p">),</span>
<span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">50000</span><span class="p">,</span><span class="mi">3</span><span class="p">),</span>
<span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">10000000</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span>
</code></pre></div></div>
<p>Tabel dan data di atas kita masukkan ke database dengan rincian sebagai berikut:</p>
<ul>
<li>Jenis Database : MySQL</li>
<li>Server Database : localhost</li>
<li>Nama Database : belajar</li>
<li>Username Database : root</li>
<li>Password Database : admin</li>
</ul>
<p>Selanjutnya kita akan mengkonfigurasi Spring supaya bisa terkoneksi dengan database tersebut.</p>
<h1 id="konfigurasi-koneksi-database">Konfigurasi Koneksi Database</h1>
<h2 id="dependensi">Dependensi</h2>
<p>Kita membutuhkan beberapa library, dinyatakan dalam dependensi Maven sebagai berikut:</p>
<h3 id="driver-database-mysql">Driver database MySQL</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>mysql<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>mysql-connector-java<span class="nt"></artifactId></span>
<span class="nt"><version></span>${mysql.version}<span class="nt"></version></span>
<span class="nt"><scope></span>runtime<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<h3 id="library-manajemen-koneksi-database-database-connection-pooling">Library manajemen koneksi database (database connection pooling)</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>commons-dbcp<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>commons-dbcp<span class="nt"></artifactId></span>
<span class="nt"><version></span>${commons-dbcp.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<h3 id="spring-jdbc">Spring JDBC</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-context<span class="nt"></artifactId></span>
<span class="nt"><version></span>${org.springframework.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-jdbc<span class="nt"></artifactId></span>
<span class="nt"><version></span>${org.springframework.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-test<span class="nt"></artifactId></span>
<span class="nt"><version></span>${org.springframework.version}<span class="nt"></version></span>
<span class="nt"><scope></span>test<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Selanjutnya, kita membuat konfigurasi database untuk Spring. Kita beri nama saja file konfigurasinya <code class="language-plaintext highlighter-rouge">spring-jdbc-ctx.xml</code>
dan kita letakkan di folder <code class="language-plaintext highlighter-rouge">src/main/resources</code>. File ini berisi :</p>
<ul>
<li>konfigurasi data source untuk koneksi ke database. Kita menggunakan pustaka <code class="language-plaintext highlighter-rouge">commons-dbcp</code> untuk menangani connection pooling ke database.</li>
<li>transaction manager. Ini dibutuhkan supaya kita tidak perlu lagi membuat coding untuk rangkaian <code class="language-plaintext highlighter-rouge">begin-commit/rollback</code>.</li>
<li>component scan. Ini dibutuhkan agar object DAO dan Service kita otomatis dideteksi dan diinisialisasi oleh Spring</li>
</ul>
<h2 id="koneksi-database">Koneksi Database</h2>
<p>Berikut konfigurasi koneksi database</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"dataSource"</span> <span class="na">class=</span><span class="s">"org.apache.commons.dbcp.BasicDataSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"driverClassName"</span> <span class="na">value=</span><span class="s">"com.mysql.jdbc.Driver"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"url"</span> <span class="na">value=</span><span class="s">"jdbc:mysql://localhost/belajar"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">value=</span><span class="s">"root"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">value=</span><span class="s">"admin"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"maxActive"</span> <span class="na">value=</span><span class="s">"80"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"maxWait"</span> <span class="na">value=</span><span class="s">"40000"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"maxIdle"</span> <span class="na">value=</span><span class="s">"20"</span> <span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Penjelasannya sebagai berikut:</p>
<h3 id="database-connection-pooling">Database Connection Pooling</h3>
<p>Koneksi ke database sebetulnya merupakan operasi yang <em>mahal</em>. Kenapa <em>mahal</em>?
Karena setiap kali kita membuat koneksi, ada serangkaian kegiatan yang harus dilakukan oleh database server seperti:</p>
<ul>
<li>memeriksa username dan password</li>
<li>memeriksa apakah komputer kita (dilihat dari alamat IP atau nama host) diijinkan untuk masuk</li>
<li>memeriksa apakah database, tabel, dan tindakan yang kita lakukan memiliki ijin akses yang mencukupi</li>
</ul>
<p>Oleh karena itu, idealnya koneksi database dibuat sekali saja dan digunakan terus sepanjang aplikasi berjalan.
Tentunya kalau koneksi database hanya satu, setiap request dari user akan mengantri.
Untuk itu kita buat banyak koneksi sekaligus yang nantinya akan <em>dipinjamkan</em> pada request yang membutuhkan.
Teknik ini disebut dengan istilah database connection pooling.</p>
<p>Library yang kita gunakan untuk itu adalah <a href="http://commons.apache.org/proper/commons-dbcp/configuration.html">Apache Commons DBCP</a>, yang ditandai dengan penggunaan class <code class="language-plaintext highlighter-rouge">org.apache.commons.dbcp.BasicDataSource</code> di atas.</p>
<p>Ada banyak hal yang bisa disetting, tapi kita akan fokus ke beberapa saja yaitu:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">driverClassName</code> : nama class untuk koneksi ke database. Ini harus sesuai dengan merek dan versi database yang digunakan</li>
<li><code class="language-plaintext highlighter-rouge">url</code> : informasi koneksi database. Biasanya berisi alamat server (IP atau Hostname) dan nama database</li>
<li><code class="language-plaintext highlighter-rouge">username</code> : username untuk connect ke database</li>
<li><code class="language-plaintext highlighter-rouge">password</code> : passwordnya user tersebut</li>
</ul>
<p>Keempat informasi di atas adalah informasi umum yang kita butuhkan apapun metode koneksi database yang kita gunakan, tidak terkait dengan penggunaan Apache Commons DBCP. Konfigurasi berikut barulah berkaitan dengan Apache Commons DBCP:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">maxActive</code> : jumlah koneksi yang boleh aktif secara berbarengan. Ini harus disetting dibawah angka yang kita ijinkan di database server. Misalnya di MySQL kita ijinkan 100 koneksi berbarengan, maka angkanya harus dibawah 100. Jangan juga dihabiskan 100, untuk berjaga-jaga siapa tahu kita butuh koneksi langsung ke MySQL tanpa lewat aplikasi (misalnya untuk keperluan debug). Pertimbangkan juga apabila ada aplikasi lain yang menggunakan database yang sama.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">maxIdle</code> : ada kalanya aplikasi kita sedang sepi dari request user sehingga banyak koneksi database yang menganggur (idle). Angka <code class="language-plaintext highlighter-rouge">maxIdle</code> ini menentukan berapa koneksi yang akan tetap dipegang walaupun idle. Bila ada 20 koneksi idle, padahal <code class="language-plaintext highlighter-rouge">maxIdle</code> berisi 15, maka 5 koneksi akan ditutup. Ini merupakan trade-off. Bila terlalu banyak idle, maka memori database server akan terpakai untuk koneksi yang standby ini. Tapi bila terlalu sedikit, pada waktu aplikasi mendadak diserbu user, akan butuh waktu lama untuk dia membuatkan lagi koneksi baru.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">maxWait</code> : bila semua koneksi sebanyak <code class="language-plaintext highlighter-rouge">maxActive</code> sedang terpakai semua, request berikutnya akan menunggu salah satu selesai menggunakan koneksi. Nilai <code class="language-plaintext highlighter-rouge">maxWait</code> menentukan berapa milidetik request tersebut menunggu. Bila lebih dari <code class="language-plaintext highlighter-rouge">maxWait</code> dan belum juga kebagian koneksi, maka request tersebut akan mendapatkan <code class="language-plaintext highlighter-rouge">Exception</code>. Konfigurasi ini perlu diperhatikan karena nilai defaultnya adalah <code class="language-plaintext highlighter-rouge">indefinitely</code> yaitu menunggu selamanya.</p>
</li>
</ul>
<p>Saya pernah mendapatkan masalah karena setting default ini. Aplikasi bengong seolah hang. Dicek ke log file tidak ada error. Ternyata masalahnya ada query yang kurang optimal sehingga memakan waktu lama. Pada saat banyak request yang menjalankan query tersebut, request lain menunggu lama tanpa ada pemberitahuan, sehingga terkesan hang. Setelah nilai <code class="language-plaintext highlighter-rouge">maxWait</code> saya ganti menjadi 30 detik, mulai banyak error message bermunculan dari request yang menunggu > 30 detik. Dengan adanya error message, query bermasalah tersebut menjadi terlihat sehingga bisa diperbaiki.</p>
<blockquote>
<p>Pesan moral pertama : pesan error itu penting untuk mengetahui sumber masalah. Dalam bugfixing, yang paling penting adalah menemukan masalah. Kalau masalah sudah ditemukan, siapa saja bisa memperbaiki. Jadi kalau aplikasi kita bermasalah, prioritas pertama kita adalah membuat dia mengeluarkan pesan error yang jelas.</p>
</blockquote>
<p>Baca <a href="http://software.endy.muhardin.com/java/tips-melaporkan-error/">artikel ini</a> untuk mengetahui apa yang dimaksud dengan pesan error yang jelas.</p>
<blockquote>
<p>Pesan moral kedua : Dalam bugfixing, sering kali kita tidak langsung mendapatkan masalah utama. Pada kasus di atas, pertama kali saya menemukan bahwa perilaku defaultnya Commons DBCP adalah menunggu koneksi dengan sabar sampai selamanya. Setelah ini diubah, barulah saya menemukan masalah utama, yaitu ada query yang tidak optimal.</p>
</blockquote>
<h2 id="transaction-manager">Transaction Manager</h2>
<p>Setelah terhubung ke database, selanjutnya kita akan mengkonfigurasi transaction manager. Ini adalah fitur dari Spring Framework yang membebaskan kita dari coding manual untuk urusan transaction. Bila tidak menggunakan ini, maka kode program kita akan tampak seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">){</span>
<span class="nc">Connection</span> <span class="n">conn</span><span class="o">;</span> <span class="c1">// inisialisasi koneksi di sini</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">conn</span><span class="o">.</span><span class="na">setAutocommit</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"insert into m_produk ... "</span><span class="o">;</span>
<span class="n">conn</span><span class="o">.</span><span class="na">createStatement</span><span class="o">().</span><span class="na">executeUpdate</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="n">conn</span><span class="o">.</span><span class="na">commit</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">err</span><span class="o">){</span>
<span class="n">conn</span><span class="o">.</span><span class="na">rollback</span><span class="o">();</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">conn</span><span class="o">.</span><span class="na">setAutocommit</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="n">conn</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Untuk dua baris perintah seperti di atas, kita harus menambahkan 8 baris hanya untuk mengurus transaction <strong>pada setiap method</strong>. Bayangkan kalau aplikasi kita punya 100 method, maka kode program untuk mengelola transaksi saja sudah 800 baris. Dengan fitur transaction manager, maka method di atas bisa ditulis ulang seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simpan</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">){</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"insert into m_produk ... "</span><span class="o">;</span>
<span class="n">conn</span><span class="o">.</span><span class="na">createStatement</span><span class="o">().</span><span class="na">executeUpdate</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Jauh lebih bersih dan rapi. Kode program jadi mudah dibaca dan akibatnya tentu memudahkan kita pada waktu bugfix, perubahan, ataupun penambahan fitur.</p>
<p>Nah ini dia konfigurasi transaction manager:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"transactionManager"</span> <span class="na">class=</span><span class="s">"org.springframework.jdbc.datasource.DataSourceTransactionManager"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dataSource"</span> <span class="na">ref=</span><span class="s">"dataSource"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><tx:annotation-driven/></span>
</code></pre></div></div>
<p>Class transaction manager yang digunakan berbeda tergantung metode akses database yang digunakan dan juga bagaimana cara kita mendeploy aplikasi. Beberapa pilihan transaction manager yang lain antara lain:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">DataSourceTransactionManager</code> : digunakan untuk koneksi database menggunakan <code class="language-plaintext highlighter-rouge">javax.sql.DataSource</code>. Ini artinya koneksi langsung dari aplikasi ke database server.</li>
<li><code class="language-plaintext highlighter-rouge">JtaTransactionManager</code> : digunakan bila aplikasi kita dideploy di application server yang memiliki transaction manager sendiri (seperti Glassfish, JBoss, Websphere, Weblogic, dsb) <em>dan menggunakan</em> transaction manager yang disediakan tersebut. Bila kita deploy di Tomcat, hampir pasti kita tidak menggunakan JTA. Bila kita deploy ke Glassfish dan menggunakan konfigurasi <code class="language-plaintext highlighter-rouge">dataSource</code> Apache Commons DBCP, berarti kita juga tidak menggunakan JTA.</li>
<li><code class="language-plaintext highlighter-rouge">HibernateTransactionManager</code> : seperti ditunjukkan oleh namanya, gunakan ini bila kita menggunakan Hibernate</li>
<li><code class="language-plaintext highlighter-rouge">JpaTransactionManager</code> : ini juga sudah jelas dari namanya. Bila kita pakai JPA, gunakan transaction manager ini.</li>
</ul>
<p>Demikian tutorial cara konfigurasi koneksi database. Pada bagian selanjutnya, kita akan <a href="http://software.endy.muhardin.com/java/struktur-aplikasi-java-dengan-spring-dan-maven/">menyiapkan kerangka aplikasinya dulu</a>.</p>
Mendebug Aplikasi AJAX2013-05-22T16:46:00+07:00https://software.endy.muhardin.com/java/mendebug-aplikasi-ajax<p>Di jaman modern ini, penggunaan AJAX sudah sangat mendunia, sehingga jarang sekali kita temui aplikasi yang tidak menggunakan AJAX. Demikian juga dengan template aplikasi standar yang digunakan di ArtiVisi, yaitu <a href="https://github.com/endymuhardin/belajar-restful">Belajar RESTful</a>.</p>
<p>Bagi yang baru mendengar atau sering dengar tapi belum paham, berikut penjelasan singkat mengenai AJAX.</p>
<ul>
<li>AJAX adalah salah satu teknik pemrograman web</li>
<li>AJAX bukanlah nama library atau framework, dia adalah cara membuat aplikasi</li>
<li>
<p>Pada aplikasi non-AJAX, siklusnya sebagai berikut :</p>
<ol>
<li>Ketik URL</li>
<li>Tampil HTML</li>
<li>Lakukan sesuatu (isi form + tekan submit)</li>
<li>Browser mengirim data</li>
<li>Server mengembalikan HTML full satu halaman</li>
</ol>
</li>
<li>Pada aplikasi AJAX, request ke server dilakukan oleh javascript, bukan oleh form submit.</li>
<li>Response dari server biasanya hanya berupa data dalam format XML atau JSON, bukan data + tampilan seperti HTML</li>
<li>Data yang dikirim server digunakan javascript hanya untuk mengubah sebagian halaman tampilan, bukan seluruh halaman seperti aplikasi non-AJAX.</li>
</ul>
<p>Karena perbedaan cara kerja aplikasi AJAX, maka cara debug errornya juga berbeda. Untuk mendebug aplikasi AJAX, kita perlu melengkapi browser yang kita gunakan supaya bisa memantau request dan response yang dilakukan aplikasi.</p>
<p>Pada artikel ini, kita akan membahas bagaimana cara mendebug aplikasi AJAX. Sebagai contoh kasus, kita akan gunakan aplikasi <a href="https://github.com/endymuhardin/belajar-restful">Belajar RESTful</a>.</p>
<!--more-->
<h2 id="studi-kasus">Studi Kasus</h2>
<p>Sebagai contoh, kita akan mendebug form entri user berikut</p>
<p><a href="/images/uploads/2013/05/debug-ajax/01-screen-form-user.png"><img src="/images/uploads/2013/05/debug-ajax/01-screen-form-user.png" alt="Form Entri User " /></a></p>
<p>dan tampilan daftar user berikut</p>
<p><a href="/images/uploads/2013/05/debug-ajax/02-screen-list-user.png"><img src="/images/uploads/2013/05/debug-ajax/02-screen-list-user.png" alt="Daftar User " /></a></p>
<h2 id="persenjataan">Persenjataan</h2>
<p>Agar bisa mendebug aplikasi AJAX, browser kita harus bisa menampilkan lalu lintas request dan response. Bila menggunakan Firefox, instal plugin <a href="https://addons.mozilla.org/en-US/firefox/addon/firebug/">Firebug</a>. Bila menggunakan Chrome, kemampuan ini sudah ada di menu Tools > Developer Tools sehingga kita tidak perlu menginstal apa-apa.</p>
<p>Setelah terinstal, pantau tab Network untuk melihat detail request dan response.</p>
<p>Berikut contohnya di Firebug</p>
<p><a href="/images/uploads/2013/05/debug-ajax/03-network-tab-firebug.png"><img src="/images/uploads/2013/05/debug-ajax/03-network-tab-firebug.png" alt="Network Tab Firebug " /></a></p>
<p>Dan ini tampilannya di Chrome Developer Tools</p>
<p><a href="/images/uploads/2013/05/debug-ajax/04-network-tab-chrome.png"><img src="/images/uploads/2013/05/debug-ajax/04-network-tab-chrome.png" alt="Network Tab Chrome Developer Tools " /></a></p>
<p>Seiring waktu, tab ini bisa penuh, kita bisa bersihkan menggunakan tombol Clear.</p>
<p><a href="/images/uploads/2013/05/debug-ajax/05-clear-log.png"><img src="/images/uploads/2013/05/debug-ajax/05-clear-log.png" alt="Tombol Clear " /></a></p>
<p>Beberapa aplikasi AJAX yang mengikuti kaidah REST mengirim data ke server dalam format JSON dengan HTTP method yang sesuai fungsi seperti GET, POST, PUT, DELETE. Seringkali kita juga perlu memanipulasi HTTP request header dan melihat isi HTTP response header. Untuk itu, kita perlu menambah plugin lagi di browser.</p>
<p>Silahkan install <a href="https://addons.mozilla.org/En-us/firefox/addon/poster/">Poster</a> untuk Firefox, atau <a href="https://chrome.google.com/webstore/detail/rest-console/cokgbflfommojglbmbpenpphppikmonn?hl=en">Rest Console</a> untuk Chrome.</p>
<h2 id="mendebug-form-submit">Mendebug Form Submit</h2>
<p>Di aplikasi Belajar RESTful, ketika kita mengisi form dan menekan tombol Simpan, prosesnya agak berbeda dengan aplikasi web tradisional.
Pada aplikasi ini, semua form data dikumpulkan dan dikonversi menjadi format JSON, dan kemudian dikirim dalam HTTP request body. Ini dilakukan menggunakan Javascript.</p>
<p>Di aplikasi web tradisional, data form ini disubmit dalam format HTTP request parameter dan tidak melibatkan Javascript sama sekali.</p>
<p>Karena ada proses konversi menjadi JSON ini, selama proses development kita ingin melihat dan memeriksa apakah nama variabel dan datanya sudah benar. Untuk itu kita bisa menggunakan Chrome Developer Tools, seperti bisa dilihat di screenshot berikut.</p>
<p><a href="/images/uploads/2013/05/debug-ajax/06-debug-siklus-cdt.png"><img src="/images/uploads/2013/05/debug-ajax/06-debug-siklus-cdt.png" alt="Debug Request di Chrome Developer Tools " /></a></p>
<p>Screen debug di Firebug bentuknya seperti ini</p>
<p><a href="/images/uploads/2013/05/debug-ajax/09-debug-siklus-firebug.png"><img src="/images/uploads/2013/05/debug-ajax/09-debug-siklus-firebug.png" alt="Debug Request di Firebug " /></a></p>
<p>Hal-hal yang perlu kita amati adalah :</p>
<ul>
<li>
<p>Request</p>
<ul>
<li>Request Method : GET, POST, PUT, atau DELETE</li>
<li>Request Header, terutama Accept dan Content-Type</li>
<li>Request Parameter</li>
<li>Data yang dikirim (bila ada)</li>
</ul>
</li>
<li>
<p>Response</p>
<ul>
<li>Response Status : Sukses (200, 201, 204) atau Gagal (4xx atau 5xx)</li>
<li>Response Header, terutama Content-Type dan Location</li>
<li>Response Data (bila ada)</li>
</ul>
</li>
</ul>
<p>Berikut adalah data-data tersebut di Chrome Developer Tools</p>
<p><a href="/images/uploads/2013/05/debug-ajax/07-detail-request-cdt.png"><img src="/images/uploads/2013/05/debug-ajax/07-detail-request-cdt.png" alt="Detail Request Chrome Developer Tools " /></a></p>
<p><a href="/images/uploads/2013/05/debug-ajax/08-detail-response-cdt.png"><img src="/images/uploads/2013/05/debug-ajax/08-detail-response-cdt.png" alt="Detail Response Chrome Developer Tools " /></a></p>
<p>Di Firebug, header dan data dipisahkan</p>
<p><a href="/images/uploads/2013/05/debug-ajax/10-detail-request-firebug.png"><img src="/images/uploads/2013/05/debug-ajax/10-detail-request-firebug.png" alt="Tab Header " /></a></p>
<p>Request data ditampilkan di tab terpisah</p>
<p><a href="/images/uploads/2013/05/debug-ajax/11-detail-post-data-firebug.png"><img src="/images/uploads/2013/05/debug-ajax/11-detail-post-data-firebug.png" alt="Tab Request Data " /></a></p>
<p>Pada waktu membaca data response, kita bisa melihat data aslinya</p>
<p><a href="/images/uploads/2013/05/debug-ajax/12-detail-response-firebug.png"><img src="/images/uploads/2013/05/debug-ajax/12-detail-response-firebug.png" alt="Detail Response Firebug " /></a></p>
<p>dan bisa juga melihat yang sudah diformat dengan rapi</p>
<p><a href="/images/uploads/2013/05/debug-ajax/13-detail-json-firebug.png"><img src="/images/uploads/2013/05/debug-ajax/13-detail-json-firebug.png" alt="Detail JSON Firebug " /></a></p>
<h2 id="mendebug-rest-server">Mendebug REST Server</h2>
<p>Pada waktu kita mengisi form dan menekan tombol Submit, client mengirim HTTP request dan data berbentuk JSON. Kemudian server memproses data yang diterima dan mengirim kembalian berupa JSON juga. Agar development lebih mudah, kita ingin melakukan request dan melihat response secara langsung, tanpa direpotkan dengan tampilan HTML dan coding Javascript. Kita bisa menggunakan plugin browser untuk melakukan hal ini.</p>
<p>Beberapa hal yang harus kita ketahui untuk mengetes proses di sisi server adalah:</p>
<ul>
<li>URL</li>
<li>HTTP Method</li>
<li>Tipe data yang dikirim</li>
<li>Data yang akan dikirim</li>
</ul>
<h3 id="menggunakan-chrome">Menggunakan Chrome</h3>
<p>Berikut adalah tampilan plugin Chrome, yaitu REST Console yang sudah terisi data di atas.</p>
<p><a href="/images/uploads/2013/05/debug-ajax/14-request-form-rest-console.png"><img src="/images/uploads/2013/05/debug-ajax/14-request-form-rest-console.png" alt="Request Form REST Console " /></a></p>
<p>Selanjutnya kita tinggal menekan tombol yang sesuai dengan HTTP Method yang dibutuhkan. Bila sukses, kita akan mendapatkan tampilan seperti ini</p>
<p><a href="/images/uploads/2013/05/debug-ajax/15-response-sukses-rest-console.png"><img src="/images/uploads/2013/05/debug-ajax/15-response-sukses-rest-console.png" alt="Response Sukses REST Console " /></a></p>
<p>Bila gagal, kita akan melihat tampilan seperti ini</p>
<p><a href="/images/uploads/2013/05/debug-ajax/16-response-error-rest-console.png"><img src="/images/uploads/2013/05/debug-ajax/16-response-error-rest-console.png" alt="Response Gagal REST Console " /></a></p>
<h3 id="menggunakan-firefox">Menggunakan Firefox</h3>
<p>Hal yang sama bisa dilakukan dengan Poster di Firefox. Berikut adalah form yang digunakan untuk entri data.</p>
<p><a href="/images/uploads/2013/05/debug-ajax/17-request-form-poster.png"><img src="/images/uploads/2013/05/debug-ajax/17-request-form-poster.png" alt="Request Form Poster " /></a></p>
<p>Bila sukses, kita mendapatkan hasil seperti ini</p>
<p><a href="/images/uploads/2013/05/debug-ajax/18-response-sukses-poster.png"><img src="/images/uploads/2013/05/debug-ajax/18-response-sukses-poster.png" alt="Response Sukses Poster " /></a></p>
<p>Bila gagal berikut tampilannya</p>
<p><a href="/images/uploads/2013/05/debug-ajax/19-response-error-poster.png"><img src="/images/uploads/2013/05/debug-ajax/19-response-error-poster.png" alt="Response Gagal Poster " /></a></p>
<p>Dengan menggunakan REST Console dan Poster, kita dapat mengurus coding di sisi server tanpa perlu dipusingkan dengan coding di sisi client (HTML, CSS, dan JS).</p>
Integrasi Aplikasi2013-05-20T21:20:00+07:00https://software.endy.muhardin.com/java/integrasi-aplikasi<p>Aplikasi jaman sekarang jarang sekali yang berdiri sendiri.
Umumnya aplikasi yang kita buat harus terhubung dengan aplikasi yang dibuat orang lain.
Beberapa contohnya:</p>
<ul>
<li>kita membuat aplikasi mobile, terhubung ke social network seperti Facebook dan Twitter</li>
<li>kita membuat aplikasi bisnis, harus terhubung ke aplikasi akunting yang sudah lebih dulu ada</li>
<li>kita membuat aplikasi pembayaran tagihan, harus terhubung ke penyedia tagihan seperti PLN, Telkom, dsb</li>
</ul>
<p>Pada artikel ini, kita akan membahas beberapa strategi untuk menghubungkan aplikasi yang kita buat dengan aplikasi lain.</p>
<!--more-->
<p>Secara umum, ada empat strategi untuk menghubungkan aplikasi satu dengan lainnya:</p>
<ul>
<li>pemanggilan prosedur/function</li>
<li>messaging</li>
<li>sharing database</li>
<li>kirim/terima file</li>
</ul>
<p>Mari kita bahas satu persatu.</p>
<h2 id="pemanggilan-prosedur">Pemanggilan prosedur</h2>
<p>Pada metode ini, aplikasi yang akan melayani (kita sebut aplikasi server),
harus menyediakan mekanisme agar bisa dipanggil aplikasi lain (kita sebut aplikasi client).</p>
<p>Ada beberapa protokol yang populer digunakan dalam strategi ini, misalnya:</p>
<ul>
<li>SOAP</li>
<li>REST</li>
<li>ISO-8583</li>
<li>XMLRPC</li>
</ul>
<p>Ciri utama dalam strategi ini adalah:</p>
<ul>
<li>ada kesepakatan nama method yang akan dipanggil</li>
<li>ada kesepakatan jumlah dan tipe data yang dikirim dan diterima</li>
<li>client dan server harus online dalam waktu yang berbarengan.</li>
<li>setelah client memanggil server, dia akan menunggu sampai ada balasan dari server. Sebelum terima balasan, client tidak bisa melanjutkan. Fenomena ini dikenal dengan istilah synchronous invocation.</li>
</ul>
<p>Berikut beberapa contoh implementasi dari strategi ini:</p>
<ul>
<li><a href="http://software.endy.muhardin.com/java/remoting-dengan-spring/">Remoting dengan Spring Framework</a></li>
<li><a href="http://web.archive.org/web/20110324211627/http://martinusadyh.web.id/tulisanku/berkenalan-dengan-iso-8583-menggunakan-java">Transaksi Finansial dengan ISO-8583</a></li>
</ul>
<h2 id="messaging">Messaging</h2>
<p>Konsep messaging sebetulnya sudah tidak asing lagi bagi kita. Sering kita gunakan sehari-hari dalam bentuk email, chatting, sms, dan sejenisnya.</p>
<p>Beberapa ciri dari messaging antara lain:</p>
<ul>
<li>pengirim dan penerima tidak harus online berbarengan. Bisa saja pengirim mengirim pesan pada saat penerima offline, setelah itu pengirim juga offline. Pada waktu penerima online, dia akan menerima pesan yang ditujukan kepadanya pada waktu dia sedang offline.</li>
<li>ada satu pihak yang berfungsi sebagai penyimpan message. Dalam dunia messaging, pihak ini disebut sebagai message broker.</li>
<li>karena sifatnya yang memungkinkan tidak online barengan, maka biasanya ada mekanisme status delivery</li>
<li>biasanya ada juga masa kadaluarsa message di dalam database broker. Sebagai contoh, bila handphone kita mati lebih dari tiga hari, maka sms yang ditujukan ke handphone tersebut dan belum sampai di handphone kita akan dihapus oleh operator telekomunikasi.</li>
<li>tidak ada aturan mengenai format dan tipe data. Pengirim bebas mengirim apa saja. Penerima yang harus melakukan validasi terhadap format message</li>
<li>tidak ada mekanisme notifikasi ketika ada message baru. Penerima harus secara periodik mengecek (polling) ke broker untuk mengetahui ada-tidaknya message baru</li>
</ul>
<p>Dalam Java, ada beberapa pilihan untuk implementasi strategi messaging, diantaranya:</p>
<ul>
<li><a href="http://software.endy.muhardin.com/java/integrasi-pusat-cabang-2/">melalui email</a></li>
<li><a href="http://www.playsms.org/">melalui sms</a></li>
<li><a href="http://software.endy.muhardin.com/java/intro-jms/">menggunakan protokol JMS</a></li>
<li>menggunakan protokol AMQP</li>
</ul>
<h2 id="sharing-database">Sharing Database</h2>
<p>Pada strategi ini, pengirim dan penerima menggunakan database sebagai media pertukaran data. Pengirim menaruh datanya ke suatu tabel di database untuk kemudian dibaca oleh penerima.</p>
<p>Karena menggunakan database, maka mekanisme ini memiliki beberapa ciri khusus interaksi dengan database, yaitu:</p>
<ul>
<li>transaction. Pengirim bisa memastikan datanya terkirim secara utuh. Bila terjadi error di tengah pengiriman data, maka data yang sudah terlanjur ditulis bisa dibatalkan</li>
<li>tipe data bisa di-enforce. Kita bisa menggunakan tipe data angka, text, dan tanggal. Bila pengirim salah memasukkan data, maka database server akan menolaknya.</li>
</ul>
<p>Mirip dengan messaging, tidak ada mekanisme notifikasi ketika ada data baru yang ditaruh pengirim. Penerima harus secara periodik mengecek (polling) ke database untuk mengetahui ada-tidaknya data baru. Untuk memudahkan polling, biasanya tiap data diberikan kolom status untuk membedakan mana data yang sudah diproses dan mana yang belum.</p>
<p>Bila kita menggunakan Spring Integration, kita bisa langsung menggunakan <a href="http://static.springsource.org/spring-integration/reference/htmlsingle/#jdbc">implementasi yang tersedia</a> sehingga tidak perlu lagi membuatkan mekanisme pengecekan data baru. <a href="https://github.com/SpringSource/spring-integration-samples/tree/master/basic/jdbc">Contoh pemakaian juga sudah disediakan</a>, tinggal diikuti.</p>
<h2 id="file-transfer">File Transfer</h2>
<p>Sama seperti messaging, dengan strategi ini pengirim dan penerima tidak harus online berbarengan. Yang membedakannya dengan messaging adalah pada mekanisme ini biasanya format file dan struktur data di dalamnya sudah ditentukan. Demikian juga dengan lokasi penyimpanan file dan aturan penamaannya.</p>
<p>Berbeda dengan sharing database, mekanisme file transfer tidak memiliki mekanisme untuk memastikan data sampai dengan utuh. Kita sendiri yang harus memastikan keutuhan data, misalnya dengan mengirim file checksum bersama dengan file berisi data. File checksum ini bisa digunakan untuk memastikan file data terkirim secara utuh.</p>
<p>Pada mekanisme ini kita juga tidak bisa tahu progress pengiriman data. Apakah pengirim masih dalam proses mengirim atau sudah selesai? Kita tidak tahu secara pasti. Biasanya diakali dengan cara memeriksa ukuran file dalam selang waktu tertentu. Bila ukurannya masih terus bertambah, berarti proses pengiriman masih berjalan. Bila ukurannya tidak membesar lagi, ada indikasi bahwa pengiriman sudah selesai.</p>
<p>Untungnya prosedur pengecekan ini sudah dibuatkan <a href="http://static.springsource.org/spring-integration/reference/htmlsingle/#files">implementasinya</a> oleh rekan-rekan Spring Integration, sehingga kita tinggal mengikuti <a href="https://github.com/SpringSource/spring-integration-samples/tree/master/basic/file">contoh yang tersedia</a>.</p>
<p>Demikianlah sedikit informasi mengenai integrasi antar aplikasi. Semoga bermanfaat :)</p>
Training CMMI2013-05-10T14:13:00+07:00https://software.endy.muhardin.com/manajemen/training-cmmi<p>Saya akan mengisi <a href="http://brainmatics.com/cmmi-capability-maturity-model-integration/">training CMMI di Brainmatics</a> minggu depan, Senin - Rabu, 13 - 15 Mei 2013. Buat yang belum paham apa itu CMMI, bisa baca <a href="http://software.endy.muhardin.com/manajemen/apa-itu-cmmi/">artikel saya mengenai masalah ini</a>.</p>
<p>Sebenarnya, materi tentang CMMI banyak bertebaran secara gratis di internet. Bahkan spesifikasi resminya gratis bisa diunduh di <a href="http://cmmiinstitute.com/">websitenya Carnegie Mellon University</a>. Lalu buat apa ikut training lagi kalau di internet sudah banyak materinya?</p>
<!--more-->
<p>Berikut beberapa alasannya:</p>
<ul>
<li>Materi CMMI yang ada di website sangat abstrak dan penjelasannya bersifat umum.</li>
<li>Agar bisa diimplementasikan di perusahaan, aturan CMMI ini harus dibuatkan dulu padanan/mapping dengan proses bisnis di internal perusahaan.</li>
<li>Proses bisnis di internal perusahaan belum tentu selengkap yang diminta CMMI, sehingga harus didesain dan dibuat dulu.</li>
<li>Seringkali terjadi, cara mendesain proses bisnis yang baik juga belum dikuasai. Ini akan kita bahas pada sesi training.</li>
<li>Setelah dibuat, proses bisnis harus diimplementasikan ke seluruh perusahaan. Implementasi proses bisnis ada metodologinya sendiri.</li>
<li>Di website CMMI, dijelaskan proses appraisal, yaitu proses untuk menilai perusahaan apakah sudah sesuai dengan aturan CMMI. Penjelasan di kertas tentu jauh berbeda dengan pengalaman nyata yang saya alami pada waktu implementasi CMMI.</li>
<li>Ada banyak <a href="http://software.endy.muhardin.com/manajemen/meluruskan-mitos-cmmi/">mitos dan urban legend tentang CMMI</a>, terutama yang berkaitan dengan metodologi agile seperti Scrum, Kanban, XP, dan lainnya. Pada sesi training, kita akan bahas mana yang mitos mana yang nyata.</li>
</ul>
<p>Silabus lengkap bisa dilihat di <a href="http://brainmatics.com/cmmi-capability-maturity-model-integration/">website Brainmatics</a>. Tapi banyak jargon teknis di sana yang baru bisa kita pahami kalau sudah mengerti CMMI. Untuk yang masih awam, berikut adalah hal yang akan kita pelajari/lakukan selama training:</p>
<ol>
<li>Apa itu CMMI
<ul>
<li>sejarah</li>
<li>use case</li>
<li>manfaat buat kita</li>
</ul>
</li>
<li>Isi perut CMMI
<ul>
<li>apa itu process area</li>
<li>apa itu goals</li>
<li>apa itu practices</li>
<li>apa yang dimaksud generic goals/practices</li>
<li>apa yang dimaksud specific goals/practices</li>
<li>apa itu maturity level</li>
<li>apa yang dimaksud staged representation dan continuous representation</li>
<li>bagaimana proses appraisal</li>
</ul>
</li>
<li>Metodologi implementasi
<ul>
<li>menentukan target maturity level yang realistis untuk dicapai perusahaan kita</li>
<li>melakukan gap analysis, antara kondisi perusahaan kita sekarang dan kondisi yang diminta CMMI</li>
<li>membuat mapping antara kebutuhan CMMI dan proses bisnis di perusahaan kita</li>
<li>cara mendesain proses bisnis perusahaan yang efektif dan komprehensif</li>
<li>cara implementasi proses bisnis yang sudah dibuat</li>
<li>tips dan trik dalam proses appraisal</li>
</ul>
</li>
</ol>
<p>Trainingnya akan berbentuk workshop. Saya akan menjelaskan teori secara singkat, lalu kita lanjutkan dengan praktek dan diskusi. Target training ini adalah, peserta pulang dengan membawa :</p>
<ul>
<li>strategi implementasi</li>
<li>daftar proses bisnis yang harus dibuat/direvisi</li>
<li>contoh dokumen dan template</li>
<li>action plan yang harus mereka lakukan di perusahaannya untuk berhasil mencapai target maturity level yang diinginkan.</li>
</ul>
<p>Jadi, tunggu apa lagi, ayo segera daftar. Sampai ketemu di sesi training :D</p>
Akses VM Proxmox Tanpa Applet2013-02-28T14:22:00+07:00https://software.endy.muhardin.com/linux/akses-vm-proxmox-tanpa-applet<p>Jika kita ingin mengakses virtual machine di dalam Proxmox,
ada beberapa opsi yang bisa kita gunakan, yaitu:</p>
<ul>
<li>SSH langsung ke IP virtual machine tersebut.</li>
<li>Login ke web management Proxmox, kemudian pilih virtual machine, dan jalankan VNC console yang berupa aplikasi Java applet.</li>
<li>SSH ke mesin host, dan kemudian masuk ke console virtual machine</li>
<li>Membuat VNC server yang bisa diakses dari luar</li>
</ul>
<p>Kadangkala, kita tidak bisa menjalankan opsi pertama, karena belum menginstal SSH server di VM.
Opsi kedua sering tidak lancar di Linux.
Opsi ketiga hanya bisa dijalankan untuk OpenVZ, saya belum ketemu caranya untuk KVM.
Dengan demikian, kita harus menggunakan cara keempat.</p>
<!--more-->
<p>Berikut adalah caranya. Pertama kita ssh dulu ke mesin host.
Setelah itu kita tampilkan daftar semua VM yang bertipe KVM
dengan perintah <code class="language-plaintext highlighter-rouge">qm list</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># qm list
VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID
101 vm110 running 512 30.00 16474
103 vm103 stopped 512 80.00 0
104 vm104 running 256 80.00 16256
107 vm107 running 2000 50.00 16348
113 vm113 stopped 512 32.00 0
115 vm115 running 8000 100.00 16316
116 vm116 running 1000 80.00 16460
119 vm119 running 1000 20.00 16390
120 vm120 running 1000 40.00 16284
166 vm166 running 1024 150.00 16440
190 vm190 running 512 20.00 16270
199 vm199 running 1024 5.00 16300
</code></pre></div></div>
<p>Kita bisa membuka VNC server yang mengarah ke VM dengan ID tertentu
dengan perintah berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nc -l -p portvnc -c "qm vncproxy vmid passwdvnc"
</code></pre></div></div>
<p>Misalnya, bila kita ingin mengakses VMID <code class="language-plaintext highlighter-rouge">166</code> di port <code class="language-plaintext highlighter-rouge">10166</code> dengan password <code class="language-plaintext highlighter-rouge">abc321</code>,
maka perintahnya adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nc -l -p 10166 -c "qm vncproxy 166 abc321"
</code></pre></div></div>
<p>Setelah itu, kita bisa mengakses VM tersebut menggunakan VNC client seperti pada screenshot.</p>
<p><a href="/images/uploads/2013/02/28/vnc-kvm.png"><img src="/images/uploads/2013/02/28/vnc-kvm.png" alt="Konfigurasi VNC Client " /></a></p>
<p>Kosongkan saja username dan password, nanti kita akan dimintai password oleh aplikasinya.</p>
<p>Hal yang sama bisa dilakukan juga dengan OpenVZ.</p>
<p>Pertama, cari dulu VM yang mau diakses dengan perintah <code class="language-plaintext highlighter-rouge">vzlist</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># vzlist
CTID NPROC STATUS IP_ADDR HOSTNAME
102 229 running 202.202.202.200 vm102
105 40 running 202.202.202.201 vm105
106 27 running 202.202.202.202 vm106
108 21 running 202.202.202.203 vm108
110 46 running 202.202.202.204 vm110
111 61 running 202.202.202.205 vm111
112 16 running 202.202.202.206 vm112
114 39 running 202.202.202.207 vm114
117 18 running 202.202.202.208 vm117
121 43 running 202.202.202.209 vm121
130 32 running 202.202.202.210 vm130
</code></pre></div></div>
<p>Kemudian gunakan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vncterm -c vzctl enter VMID
</code></pre></div></div>
<p>Misalnya, bila ingin mengakses VM 111, maka perintahnya adalah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vncterm -rfbport 10111 -c vzctl enter 104
</code></pre></div></div>
<p>Referensi :</p>
<ul>
<li><a href="http://forum.proxmox.com/threads/612-External-VNC-viewer">Forum Proxmox</a></li>
<li><a href="http://honglus.blogspot.com/2011/02/access-guest-vm-console-via-text-mode.html">UNIX/LINUX TECH NOTES</a></li>
</ul>
Setup Proxmox dengan 1 IP Public2013-02-12T20:05:00+07:00https://software.endy.muhardin.com/linux/setup-proxmox-dengan-1-ip-public<p>Jaman sekarang, membuat server untuk cloud tidak lagi sulit.
Cukup cari server nganggur, unduh <a href="http://pve.proxmox.com/">Proxmox</a>,
kemudian instal.</p>
<p>Instalasinya pun tidak sulit, dia cuma menanyakan mau diberi IP berapa,
password untuk user root, dan selesai sudah.
Selanjutnya, server ini tinggal kita taruh di data center.</p>
<p>Dengan Proxmox ini, kita dapat membuat virtual machine (VM) sebanyak-banyaknya
yang mampu ditangani oleh hardware servernya.
Pembuatan VM ini dilakukan melalui antarmuka berbasis web,
sehingga kita tidak perlu lagi menghafalkan perintah-perintah command line.</p>
<p>Walaupun VM bisa dibuat sesuka hati, seringkali IP public yang tersedia terbatas jumlahnya.
Punya VM banyak tapi tidak bisa diakses dari luar tentu percuma saja.
Pada artikel ini, kita akan membahas cara supaya satu IP public bisa dimanfaatkan oleh banyak VM.</p>
<!--more-->
<h1 id="konfigurasi-host">Konfigurasi Host</h1>
<p>Agar ada gambaran, berikut adalah konfigurasi awal setelah instalasi.
Tentunya pembaca maklum bahwa semua IP yang ditulis di sini adalah palsu belaka.</p>
<ul>
<li>IP public (digunakan oleh Proxmox Host) : 202.202.202.202</li>
<li>Network Device : eth0, dibridge menjadi vmbr0</li>
<li>Versi Proxmox : 2-2-24</li>
</ul>
<p>Berikut adalah hasil keluaran perintah <code class="language-plaintext highlighter-rouge">ifconfig</code> dalam host :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eth0 Link encap:Ethernet HWaddr 00:21:5e:27:05:30
inet6 addr: fe80::221:5eff:fe27:530/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:643194 errors:0 dropped:0 overruns:0 frame:0
TX packets:225604 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:196913694 (187.7 MiB) TX bytes:35911168 (34.2 MiB)
Interrupt:16 Memory:ce000000-ce012800
vmbr0 Link encap:Ethernet HWaddr 00:21:5e:27:05:30
inet addr:202.202.202.202 Bcast:202.202.202.203 Mask:255.255.255.248
inet6 addr: fe80::221:5eff:fe27:530/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:571124 errors:0 dropped:0 overruns:0 frame:0
TX packets:206100 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:177290314 (169.0 MiB) TX bytes:31457704 (30.0 MiB)
</code></pre></div></div>
<p>Dan ini adalah isi file <code class="language-plaintext highlighter-rouge">/etc/network/interfaces</code> dalam host</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iface eth0 inet manual
auto vmbr0
iface vmbr0 inet static
address 202.202.202.202
netmask 255.255.255.248
gateway 202.202.202.201
bridge_ports eth0
bridge_stp off
bridge_fd 0
</code></pre></div></div>
<h1 id="konfigurasi-vm">Konfigurasi VM</h1>
<p>Kita mempunyai satu VM dengan konfigurasi berikut :</p>
<ul>
<li>IP Address : 192.168.100.100</li>
<li>Jenis Interface : venet</li>
</ul>
<p>Berikut adalah output dari <code class="language-plaintext highlighter-rouge">ifconfig</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
venet0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
inet addr:127.0.0.2 P-t-P:127.0.0.2 Bcast:0.0.0.0 Mask:255.255.255.255
UP BROADCAST POINTOPOINT RUNNING NOARP MTU:1500 Metric:1
RX packets:33307 errors:0 dropped:0 overruns:0 frame:0
TX packets:16717 errors:0 dropped:8 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:49576164 (49.5 MB) TX bytes:916607 (916.6 KB)
venet0:0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
inet addr:192.168.100.100 P-t-P:192.168.100.100 Bcast:0.0.0.0 Mask:255.255.255.255
UP BROADCAST POINTOPOINT RUNNING NOARP MTU:1500 Metric:1
</code></pre></div></div>
<h1 id="akses-dari-dalam-vm-ke-internet">Akses dari dalam VM ke Internet</h1>
<p>Yang pertama kita kerjakan adalah bagaimana caranya supaya VM ini bisa mengakses internet.
Caranya adalah memasang NAT di Proxmox Host supaya paket data dari network <code class="language-plaintext highlighter-rouge">192.168.100.0/24</code>
diijinkan untuk keluar dan melalui proses Network Address Translation (NAT).</p>
<p>Perintahnya adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o vmbr0 -j MASQUERADE
</code></pre></div></div>
<p>Perintah ini tentunya tidak asing bagi yang sering mengkonfigurasi internet gateway dengan Linux.
Caranya sama persis, seolah-olah ada banyak komputer dengan IP <code class="language-plaintext highlighter-rouge">192.168.100.xxx</code> yang ingin mengakses internet.</p>
<p>Setelah ini dijalankan, kita coba tes akses internet dari dalam VM.
Jalankan perintah berikut dari Proxmox Host untuk masuk ke dalam VM.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vzctl enter 107
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">107</code> adalah ID dari VM yang ingin kita masuki.</p>
<p>Begitu sudah masuk, coba ping ke alamat internet.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ping google.com
PING google.com (173.194.38.137) 56(84) bytes of data.
64 bytes from sin04s01-in-f9.1e100.net (173.194.38.137): icmp_req=1 ttl=57 time=12.2 ms
64 bytes from sin04s01-in-f9.1e100.net (173.194.38.137): icmp_req=2 ttl=57 time=12.3 ms
^C
--- google.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 12.257/12.293/12.330/0.116 ms
</code></pre></div></div>
<p>Tentunya kita harus pastikan dulu Proxmox Host bisa ping ke Google.
Bila Proxmox Host saja tidak bisa akses internet, ya jangan harap VM bisa akses internet.
Ini kan bukan acara sulap.</p>
<p>Baiklah, VM kita sudah bisa mengakses internet, lanjut ke langkah berikut.</p>
<h1 id="mengakses-vm-dari-internet">Mengakses VM dari Internet</h1>
<p>Minimal kita ingin agar VM kita tersebut bisa diakses melalui SSH.
Service SSH di Linux biasanya jalan di port <code class="language-plaintext highlighter-rouge">22</code>.
Karena port <code class="language-plaintext highlighter-rouge">22</code> di Proxmox Host sudah dipakai oleh dirinya sendiri,
kita harus pilih port lain, misalnya <code class="language-plaintext highlighter-rouge">10722</code>.
Teknik ini disebut dengan istilah <em>port forwarding</em></p>
<p>Perintahnya juga sama dengan perintah untuk NAT dari internet ke jaringan LAN,
tentu rekan-rekan yang biasa mengkonfigurasi internet gateway tidak merasa asing.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -t nat -A PREROUTING -i vmbr0 -p tcp -m tcp --dport 10722 -j DNAT --to-destination 192.168.100.100:22
</code></pre></div></div>
<p>Setelah itu, kita test dari komputer kita</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh root@202.202.202.202 -p 10722
</code></pre></div></div>
<p>Seharusnya kita sudah bisa login ke dalam VM.</p>
<p>Selanjutnya, kalau ingin membuat <em>port forwarding</em> untuk port lain caranya sama.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -t nat -A PREROUTING -i vmbr0 -p tcp -m tcp --dport 10780 -j DNAT --to-destination 192.168.100.100:80
</code></pre></div></div>
<p>Perintah di atas akan membuka port <code class="language-plaintext highlighter-rouge">10780</code> di IP public Proxmox Host
dan data yang datang ke sana diarahkan ke port <code class="language-plaintext highlighter-rouge">80</code> di VM <code class="language-plaintext highlighter-rouge">107</code> yang memiliki IP private <code class="language-plaintext highlighter-rouge">192.168.100.100</code>.</p>
<p>Setelah semua konfigurasi dicek dan berjalan dengan baik, simpan konfigurasi <code class="language-plaintext highlighter-rouge">iptables</code> ke folder yang kita inginkan menggunakan perintah <code class="language-plaintext highlighter-rouge">iptables-save</code>. Ini kita lakukan supaya konfigurasinya tidak hilang pada waktu reboot.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables-save > /etc/iptables/rules.v4
</code></pre></div></div>
<p>Pada waktu komputer shutdown, konfigurasi <code class="language-plaintext highlighter-rouge">iptables</code> yang sudah kita pasang akan hilang. Oleh karena itu kita perlu install paket <code class="language-plaintext highlighter-rouge">iptables-persistent</code> yang akan membaca dan memasang file hasil save kita di <code class="language-plaintext highlighter-rouge">/etc/iptables/rules.v4</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install iptables-persistent
</code></pre></div></div>
<p>Demikianlah, satu IP public yang kita miliki bisa dipakai oleh banyak VM yang ada dalam satu server tersebut.</p>
<p>Konsep konfigurasi ini dikenal juga dengan istilah Network Address Translation(NAT). Lebih jauh tentang NAT bisa dibaca <a href="http://software.endy.muhardin.com/linux/network-address-translation/">di artikel ini</a></p>
Mencari Relasi Foreign Key ke Tabel MySQL2013-02-07T15:24:00+07:00https://software.endy.muhardin.com/java/mencari-relasi-foreign-key-ke-tabel-mysql<p>Di aplikasi yang kita buat, biasanya ada fitur untuk menghapus data atau record tertentu.
Bila kita menggunakan database relasional yang memiliki fitur <em>referential integrity</em>,
kita akan dicegah bila data yang ingin kita hapus memiliki relasi ke data lain.</p>
<p>Contohnya, kita punya tabel <code class="language-plaintext highlighter-rouge">Produk</code>, yang kolom <code class="language-plaintext highlighter-rouge">id</code>nya digunakan sebagai <em>foreign key</em>
oleh tabel <code class="language-plaintext highlighter-rouge">Penjualan</code>.
Dengan demikian, pada waktu ingin menghapus data <code class="language-plaintext highlighter-rouge">Produk</code>, terlebih dulu kita cek ke tabel
<code class="language-plaintext highlighter-rouge">Penjualan</code>, apakah ada data transaksi yang berelasi ke <code class="language-plaintext highlighter-rouge">Produk</code> yang ingin kita hapus.</p>
<p>Akan menjadi masalah bila aplikasi kita sudah besar, tabelnya ada ratusan.
Bagaimana cara kita mencari tabel mana saja yang memiliki relasi ke tabel <code class="language-plaintext highlighter-rouge">Produk</code>?
Tentunya tidak mungkin dicek satu-persatu.</p>
<p>Dengan bantuan paman Google, berikut adalah cara mendapatkannya untuk database MySQL.</p>
<!--more-->
<p>Pertama, login dulu ke MySQL</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql -u root -p
Password:
mysql>
</code></pre></div></div>
<p>Kemudian, gunakan database <code class="language-plaintext highlighter-rouge">information_schema</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>use information_schema
</code></pre></div></div>
<p>Terakhir, jalankan query SQL berikut</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">TABLE_NAME</span><span class="p">,</span><span class="k">COLUMN_NAME</span><span class="p">,</span><span class="k">CONSTRAINT_NAME</span><span class="p">,</span>
<span class="n">REFERENCED_TABLE_NAME</span><span class="p">,</span><span class="n">REFERENCED_COLUMN_NAME</span>
<span class="k">FROM</span> <span class="n">KEY_COLUMN_USAGE</span>
<span class="k">WHERE</span> <span class="n">REFERENCED_TABLE_NAME</span> <span class="o">=</span> <span class="s1">'produk'</span>
<span class="k">AND</span> <span class="n">REFERENCED_COLUMN_NAME</span> <span class="o">=</span> <span class="s1">'id'</span>
<span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'db_penjualan'</span><span class="p">;</span>
</code></pre></div></div>
<p>Inilah hasilnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+-------------------------------------------------------------------------------------------+
| TABLE_NAME | COLUMN_NAME | CONSTRAINT_NAME | REFERENCED_TABLE_NAME | REFERENCED_COLUMN_NAME |
+-------------------------------------------------------------------------------------------+
| penjualan | id_produk | fk_penjualan_produk | produk | id |
| stok | id_produk | fk_stok_produk | produk | id |
+-------------------------------------------------------------------------------------------+
</code></pre></div></div>
<p>Penjelasan:</p>
<ul>
<li>Table Name : Nama Tabel yang memiliki referensi</li>
<li>Column Name : Nama Kolom yang merupakan <em>foreign key</em></li>
<li>Constraint Name : Nama constraint <em>foreign key</em></li>
<li>Referenced Table Name : Nama tabel yang dituju</li>
<li>Referenced Column Name : Nama kolom yang dituju <em>foreign key</em></li>
</ul>
<p>Demikian sekilas tutorial. Semoga bermanfaat dalam membuat validasi.</p>
Mematikan Location Service di Facebook Android2013-02-04T11:23:00+07:00https://software.endy.muhardin.com/aplikasi/mematikan-location-service-di-facebook-android<p>Jaman sekarang, berbagai layanan internet semakin ingin tahu saja
di mana kita sedang berada. Memang ada layanan yang dikhususkan untuk
mempublikasikan lokasi kita, seperti misalnya <a href="https://foursquare.com/">Foursquare</a> atau
<a href="https://latitude.google.com">Google Latitude</a>.
Tapi untuk kedua layanan tersebut, kita sendiri yang secara sukarela mempublikasikan lokasi kita.</p>
<p>Banyak aplikasi social lain yang sering kita gunakan, dan berusaha untuk mengetahui posisi kita setiap saat.
Tidak terkecuali aplikasi sejuta umat, yaitu <a href="http://facebook.com">Facebook</a>.
Setiap kali kita chat atau posting sesuatu, Facebook langsung memasang lokasi kita di posting atau pesan
yang kita taruh di Facebook.</p>
<p>Nah, pada artikel ini, kita akan membahas cara mematikan fitur tersebut
pada <a href="https://play.google.com/store/apps/details?id=com.facebook.katana">aplikasi Facebook yang berjalan di Android</a>.</p>
<!--more-->
<p>Ada dua tempat yang mempublikasikan lokasi kita, yaitu:</p>
<ul>
<li>Chat (Facebook Messenger)</li>
<li>Posting Status</li>
</ul>
<h1 id="facebook-chat">Facebook Chat</h1>
<p>Bila kita sedang chat dengan teman di Facebook, maka di aplikasi akan tampil informasi lokasi kita
berikut dengan tombol panah untuk mematikan/menyalakan informasi lokasi tersebut.
Pada menu chat ini, tidak sulit untuk mematikannya, cukup sentuh saja icon tersebut hingga berganti warna
menjadi abu-abu. Biru artinya aktif, abu-abu artinya tidak aktif.</p>
<p>Lebih jelasnya bisa dilihat pada gambar berikut:</p>
<p><a href="/images/uploads/2013/02/04/messenger-location.png"><img src="/images/uploads/2013/02/04/messenger-location.png" alt="messenger-location" /></a></p>
<p>Biasanya, sekali dimatikan akan tetap mati sampai kita nyalakan lagi.
Tapi untuk amannya, jangan lupa dicek sebelum kirim tombol <code class="language-plaintext highlighter-rouge">Send</code>.</p>
<h1 id="posting-status">Posting Status</h1>
<p>Nah untuk menu posting status, Facebook lebih rewel. Pertama, kita lihat dulu screenshot aplikasi
pada saat kita ingin memposting sesuatu.</p>
<p><a href="/images/uploads/2013/02/04/posting-location.png"><img src="/images/uploads/2013/02/04/posting-location.png" alt="posting-location" /></a></p>
<p>Kita bisa lihat bahwa lokasi kita tercantum di sana. Untuk mematikannya, kita tekan icon bola dunia.
Aplikasi akan menampilkan menu pemilihan lokasi. Kita klik tanda silang di pojok kanan bawah seperti pada screenshot berikut.</p>
<p><a href="/images/uploads/2013/02/04/location-menu.png"><img src="/images/uploads/2013/02/04/location-menu.png" alt="location-menu" /></a></p>
<p>Selanjutnya, layar akan kembali ke menu posting. Kita bisa lihat bahwa informasi lokasi sudah tidak ada.</p>
<p><a href="/images/uploads/2013/02/04/after-disable.png"><img src="/images/uploads/2013/02/04/after-disable.png" alt="after-disable" /></a></p>
<p>Yang menyebalkan, informasi lokasi ini akan kembali aktif pada waktu kita mau posting lagi.
Sehingga kita harus mematikannya tiap kali mau posting.</p>
Memahami Dependency Injection2013-01-24T18:14:00+07:00https://software.endy.muhardin.com/java/memahami-dependency-injection<p><a href="http://www.springsource.org/spring-framework">Spring Framework</a>
merupakan framework yang sangat populer
dan banyak digunakan orang di seluruh dunia.
Jargon utama yang sering kita dengar bersamaan dengan Spring Framework
adalah prinsip <em>Dependency Injection</em>. Ini adalah teknik pemrograman
yang digadang-gadang mampu merapikan aplikasi yang kita buat sehingga
mudah dipahami dan dikelola.</p>
<p>Tapi apakah yang dimaksud dengan <em>Dependency Injection</em> atau <em>Inversion of Control</em> itu?
<!--more--></p>
<h1 id="contoh-kasus">Contoh Kasus</h1>
<p>Sebagai programmer, kita akan lebih mudah memahami suatu konsep
bila sudah melihat sendiri contoh kode program dan aplikasinya.
Untuk itu, kita akan membuat sebuah contoh kasus sederhana,
yaitu menyimpan data ke tabel <code class="language-plaintext highlighter-rouge">produk</code> dalam database.</p>
<p>Untuk titik awal, misalnya kita memiliki class <code class="language-plaintext highlighter-rouge">Produk</code> sebagai berikut:</p>
<h2 id="produkjava">Produk.java</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Produk</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">kode</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">nama</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">harga</span><span class="o">;</span>
<span class="c1">// getter dan setter tidak ditampilkan</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Supaya rapi, kode program untuk Create (insert), Read (select),
Update, dan Delete (CRUD), akan kita kumpulkan di dalam class <code class="language-plaintext highlighter-rouge">ProdukDao</code> sebagai berikut:</p>
<h2 id="produkdaojava">ProdukDao.java</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDao</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">create</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"insert into produk (kode, nama, harga) "</span><span class="o">;</span>
<span class="n">sql</span> <span class="o">+=</span> <span class="s">"(?,?,?)"</span><span class="o">;</span>
<span class="nc">Connection</span> <span class="n">databaseConnection</span> <span class="o">=</span> <span class="n">dataSource</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">databaseConnection</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getKode</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getNama</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setBigDecimal</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getHarga</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">executeUpdate</span><span class="o">();</span>
<span class="n">databaseConnection</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">cariById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">){</span>
<span class="c1">// implementasi tidak ditulis</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">update</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">){</span>
<span class="c1">// implementasi tidak ditulis</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">){</span>
<span class="c1">// implementasi tidak ditulis</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Sebagai demonstrasi, kita akan menggunakan/memanggil <code class="language-plaintext highlighter-rouge">ProdukDao</code> ini dalam class
<code class="language-plaintext highlighter-rouge">ProdukDaoTest</code> sebagai berikut:</p>
<h2 id="produkdaotestjava">ProdukDaoTest.java</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDaoTest</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">xx</span><span class="o">){</span>
<span class="nc">Produk</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Produk</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="s">"P-001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Produk 001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setHarga</span><span class="o">(</span><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="mf">10000.00</span><span class="o">);</span>
<span class="nc">ProdukDao</span> <span class="n">pd</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProdukDao</span><span class="o">();</span>
<span class="n">pd</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dari ketiga class di atas, kita akan melihat:</p>
<ul>
<li>bagaimana implementasi tanpa Dependency Injection</li>
<li>bagaimana konsep Dependency Injection</li>
<li>bagaimana menggunakan Spring Framework untuk melakukan Dependency Injection</li>
</ul>
<p>Sebelumnya, apa itu dependency injection? Kalau diterjemahkan ke bahasa Indonesia,
kira-kira artinya adalah <em>menyediakan kebutuhan</em>.
Kebutuhan apa yang dimaksud?</p>
<p>Coba lihat class <code class="language-plaintext highlighter-rouge">ProdukDao</code>. Untuk bisa menjalankan tugasnya dengan baik,
dia membutuhkan object <code class="language-plaintext highlighter-rouge">DataSource</code>, yaitu koneksi ke database.
Bagaimana <code class="language-plaintext highlighter-rouge">ProdukDao</code> mendapatkan <code class="language-plaintext highlighter-rouge">DataSource</code> inilah yang menjadi pembahasan dalam <em>Dependency Injection</em> (DI).</p>
<h1 id="tanpa-di">Tanpa DI</h1>
<p>Kalau kita tidak menggunakan prinsip DI, maka <code class="language-plaintext highlighter-rouge">ProdukDao</code> harus mengadakan sendiri object <code class="language-plaintext highlighter-rouge">DataSource</code>.
Kira-kira begini implementasinya:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDao</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">ProdukDao</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">dataSource</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BasicDataSource</span><span class="o">();</span>
<span class="n">dataSource</span><span class="o">.</span><span class="na">setDriverClassName</span><span class="o">(</span><span class="s">"com.mysql.jdbc.Driver"</span><span class="o">);</span>
<span class="n">dataSource</span><span class="o">.</span><span class="na">setUsername</span><span class="o">(</span><span class="s">"root"</span><span class="o">);</span>
<span class="n">dataSource</span><span class="o">.</span><span class="na">setPassword</span><span class="o">(</span><span class="s">"admin"</span><span class="o">);</span>
<span class="n">dataSource</span><span class="o">.</span><span class="na">setUrl</span><span class="o">(</span><span class="s">"jdbc:mysql://localhost/belajar"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">create</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"insert into produk (kode, nama, harga) "</span><span class="o">;</span>
<span class="n">sql</span> <span class="o">+=</span> <span class="s">"(?,?,?)"</span><span class="o">;</span>
<span class="nc">Connection</span> <span class="n">databaseConnection</span> <span class="o">=</span> <span class="n">dataSource</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">databaseConnection</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getKode</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getNama</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setBigDecimal</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getHarga</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">executeUpdate</span><span class="o">();</span>
<span class="n">databaseConnection</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Produk</span> <span class="nf">cariById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">){</span>
<span class="c1">// implementasi tidak ditulis</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">update</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">){</span>
<span class="c1">// implementasi tidak ditulis</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">){</span>
<span class="c1">// implementasi tidak ditulis</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Perlu kita sadari bahwa pada aplikasi yang sebenarnya, kode akses database tidak hanya <code class="language-plaintext highlighter-rouge">ProdukDao</code> saja.
Nantinya juga ada <code class="language-plaintext highlighter-rouge">CustomerDao</code>, <code class="language-plaintext highlighter-rouge">PenjualanDao</code>, dan sebagainya.
Di aplikasi berskala menengah, bisa ada ratusan class seperti ini,
sehingga untuk memahami situasinya, kita tidak boleh berpikir hanya di satu class ini saja.</p>
<p>Ada beberapa kelemahan dari cara tanpa DI ini, diantaranya:</p>
<ul>
<li>Konfigurasi koneksi database tersebar di banyak tempat, yaitu di semua <code class="language-plaintext highlighter-rouge">XxxDao</code></li>
<li>Object <code class="language-plaintext highlighter-rouge">dataSource</code> juga tersebar, tidak bisa satu <code class="language-plaintext highlighter-rouge">dataSource</code> dipakai bersama oleh semua <code class="language-plaintext highlighter-rouge">XxxDao</code></li>
<li>Karena konfigurasi dan inisialisasinya tersebar, bila ada perubahan (misalnya menggunakan <em>connection pooling</em>),
harus dilakukan di banyak tempat.</li>
<li>Semua perubahan di atas mengharuskan full compile karena banyaknya class yang terlibat.</li>
</ul>
<p>Untuk mengatasi keterbatasan di atas, kita gunakan prinsip DI.</p>
<h1 id="di-manual">DI manual</h1>
<p>Bila kita gunakan prinsip DI, maka <code class="language-plaintext highlighter-rouge">ProdukDao</code> tidak lagi mengurus inisialisasi <code class="language-plaintext highlighter-rouge">dataSource</code>.
Dia cukup tahu beres dan tinggal pakai. Lalu siapa yang melakukan inisialisasi?
Boleh siapa saja, tapi untuk kesederhanaan ilustrasi, mari kita tulis saja di dalam <code class="language-plaintext highlighter-rouge">ProdukDaoTest</code>, sebagai berikut:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDaoTest</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">xx</span><span class="o">){</span>
<span class="nc">Produk</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Produk</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="s">"P-001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Produk 001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setHarga</span><span class="o">(</span><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="mf">10000.00</span><span class="o">);</span>
<span class="nc">DataSource</span> <span class="n">dataSource</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BasicDataSource</span><span class="o">();</span>
<span class="n">dataSource</span><span class="o">.</span><span class="na">setDriverClassName</span><span class="o">(</span><span class="s">"com.mysql.jdbc.Driver"</span><span class="o">);</span>
<span class="n">dataSource</span><span class="o">.</span><span class="na">setUsername</span><span class="o">(</span><span class="s">"root"</span><span class="o">);</span>
<span class="n">dataSource</span><span class="o">.</span><span class="na">setPassword</span><span class="o">(</span><span class="s">"admin"</span><span class="o">);</span>
<span class="n">dataSource</span><span class="o">.</span><span class="na">setUrl</span><span class="o">(</span><span class="s">"jdbc:mysql://localhost/belajar"</span><span class="o">);</span>
<span class="nc">ProdukDao</span> <span class="n">pd</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProdukDao</span><span class="o">();</span>
<span class="n">pd</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Lalu bagaimana cara memasukkan (inject) ke dalam <code class="language-plaintext highlighter-rouge">ProdukDao</code>?
Kita harus sediakan jalan masuknya. Ada dua pilihan:</p>
<ul>
<li>membuat method untuk mengisi data (setter-injection)</li>
<li>menambah argumen di constructor (constructor-injection)</li>
</ul>
<p>Agar jelas, kita akan buatkan dua-duanya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDao</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="c1">// ini kalau mau inject melalui constructor</span>
<span class="kd">public</span> <span class="nf">ProdukDao</span><span class="o">(</span><span class="nc">DataSource</span> <span class="n">ds</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">dataSource</span> <span class="o">=</span> <span class="n">ds</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// ini kalau mau method sendiri (setter-injection)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setDataSource</span><span class="o">(</span><span class="nc">DataSource</span> <span class="n">ds</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">dataSource</span> <span class="o">=</span> <span class="n">ds</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// method lain tidak ditampilkan supaya tidak bikin penuh</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Selanjutnya, dalam <code class="language-plaintext highlighter-rouge">ProdukDaoTest</code> kita bisa isikan object DataSource tersebut melalui constructor seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ProdukDao</span> <span class="n">pd</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProdukDao</span><span class="o">(</span><span class="n">dataSource</span><span class="o">);</span>
</code></pre></div></div>
<p>ataupun melalui method setter seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ProdukDao</span> <span class="n">pd</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProdukDao</span><span class="o">();</span>
<span class="n">pd</span><span class="o">.</span><span class="na">setDataSource</span><span class="o">(</span><span class="n">dataSource</span><span class="o">);</span>
</code></pre></div></div>
<p>Cara manual ini sudah lumayan merapikan kode program kita, karena bila ada perubahan terhadap inisialisasi dataSource, seperti misalnya:</p>
<ul>
<li>perubahan konfigurasi koneksi</li>
<li>perubahan implementasi connection pooling</li>
<li>ingin menggunakan managed DataSource melalui JNDI</li>
<li>dsb</li>
</ul>
<p>Kita cukup melakukan perubahan di satu tempat saja, yaitu dimana dia diinisialisasikan.</p>
<h1 id="di-spring-xml">DI Spring XML</h1>
<p>Walaupun bisa dilakukan secara manual, tetapi ada baiknya kita menggunakan Spring Framework untuk melakukan DI.
Beberapa alasannya antara lain:</p>
<ul>
<li>AOP (tidak dibahas pada artikel ini)</li>
<li>keseragaman antar project/aplikasi</li>
<li>standarisasi skillset. Bila cari programmer baru, cukup mensyaratkan pengetahuan Spring Framework.
Tidak perlu lagi ditraining tentang teknik DI yang kita karang sendiri.</li>
</ul>
<p>Spring Framework <strong>umumnya</strong> dikonfigurasi dengan file XML (walaupun bisa juga full Java).
Berikut adalah contoh file konfigurasinya, misalnya kita beri nama <code class="language-plaintext highlighter-rouge">konfig-spring.xml</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xmlns:context=</span><span class="s">"http://www.springframework.org/schema/context"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"</span><span class="nt">></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"dataSource"</span> <span class="na">class=</span><span class="s">"org.apache.commons.dbcp.BasicDataSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"driverClassName"</span> <span class="na">value=</span><span class="s">"com.mysql.jdbc.Driver"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"url"</span> <span class="na">value=</span><span class="s">"jdbc:mysql://localhost/belajar"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">value=</span><span class="s">"root"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">value=</span><span class="s">"admin"</span> <span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"produkDao"</span> <span class="na">class=</span><span class="s">"com.muhardin.endy.belajar.spring.ProdukDao"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dataSource"</span> <span class="na">ref=</span><span class="s">"dataSource"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>File konfigurasi tersebut kita baca dan gunakan dalam class <code class="language-plaintext highlighter-rouge">ProdukDaoTest</code>,
perhatikan perbedaan inisialisasi <code class="language-plaintext highlighter-rouge">DataSource</code> dan <code class="language-plaintext highlighter-rouge">ProdukDao</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDaoTest</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">xx</span><span class="o">){</span>
<span class="nc">Produk</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Produk</span><span class="o">();</span>
<span class="n">p</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="s">"P-001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Produk 001"</span><span class="o">);</span>
<span class="n">p</span><span class="o">.</span><span class="na">setHarga</span><span class="o">(</span><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="mf">10000.00</span><span class="o">);</span>
<span class="nc">ApplicationContext</span> <span class="n">ctx</span>
<span class="o">=</span> <span class="k">new</span> <span class="nc">ClassPathXmlApplicationContext</span><span class="o">(</span><span class="s">"classpath:konfig-spring.xml"</span><span class="o">);</span>
<span class="nc">ProdukDao</span> <span class="n">pd</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="nc">ProdukDao</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">pd</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada contoh di atas, kita bisa lihat beberapa perbedaan yaitu:</p>
<ul>
<li>inisialisasi <code class="language-plaintext highlighter-rouge">DataSource</code> pindah ke dalam <code class="language-plaintext highlighter-rouge">konfig-spring.xml</code></li>
<li>tidak perlu lagi <em>mengisikan</em> <code class="language-plaintext highlighter-rouge">DataSource</code> ke dalam <code class="language-plaintext highlighter-rouge">ProdukDao</code>, karena sudah dilakukan oleh Spring.</li>
</ul>
<p>Walaupun demikian, masih ada sedikit ganjalan, yaitu:</p>
<ul>
<li>bila <code class="language-plaintext highlighter-rouge">XxxDao</code> jumlahnya ratusan, maka file <code class="language-plaintext highlighter-rouge">konfig-spring.xml</code> akan membengkak.</li>
</ul>
<p>Apa solusinya?</p>
<h1 id="di-spring-autowired">DI Spring @Autowired</h1>
<p>Spring Framework menyediakan fitur <em>component-scan</em>, yaitu dia akan melihat isi <em>package</em> yang kita sebutkan,
kemudian akan mencari class-class yang diberi anotasi berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">@Repository</code></li>
<li><code class="language-plaintext highlighter-rouge">@Service</code></li>
<li><code class="language-plaintext highlighter-rouge">@Controller</code></li>
<li><code class="language-plaintext highlighter-rouge">@Component</code></li>
</ul>
<p>Setelah ditemukan, maka dia akan melakukan inisialisasi terhadap class tersebut, dan lalu mengisi (inject)
semua kebutuhannya (dependency). Untuk injection ini, kita juga tidak perlu lagi menyediakan setter method maupun
menambahkan argumen di constructor. Kita dapat menggunakan anotasi <code class="language-plaintext highlighter-rouge">@Autowired</code>.</p>
<p>Berikut adalah konfigurasi <code class="language-plaintext highlighter-rouge">konfig-spring.xml</code> yang baru:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xmlns:context=</span><span class="s">"http://www.springframework.org/schema/context"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"</span><span class="nt">></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"dataSource"</span> <span class="na">class=</span><span class="s">"org.apache.commons.dbcp.BasicDataSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"driverClassName"</span> <span class="na">value=</span><span class="s">"com.mysql.jdbc.Driver"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"url"</span> <span class="na">value=</span><span class="s">"jdbc:mysql://localhost/belajar"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">value=</span><span class="s">"root"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">value=</span><span class="s">"admin"</span> <span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><context:component-scan</span> <span class="na">base-package=</span><span class="s">"com.muhardin.endy.belajar.spring"</span> <span class="nt">/></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>Perhatikan bahwa deklarasi <code class="language-plaintext highlighter-rouge">produkDao</code> telah digantikan dengan perintah <code class="language-plaintext highlighter-rouge">context:component-scan</code>.</p>
<p>Berikut adalah perubahan di <code class="language-plaintext highlighter-rouge">ProdukDao</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProdukDao</span> <span class="o">{</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">create</span><span class="o">(</span><span class="nc">Produk</span> <span class="n">p</span><span class="o">){</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"insert into produk (kode, nama, harga) "</span><span class="o">;</span>
<span class="n">sql</span> <span class="o">+=</span> <span class="s">"(?,?,?)"</span><span class="o">;</span>
<span class="nc">Connection</span> <span class="n">databaseConnection</span> <span class="o">=</span> <span class="n">dataSource</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">databaseConnection</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getKode</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getNama</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setBigDecimal</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">p</span><span class="o">.</span><span class="na">getHarga</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">executeUpdate</span><span class="o">();</span>
<span class="n">databaseConnection</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// method lain tidak ditampilkan supaya tidak bikin penuh</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Perhatikan bahwa setter dan constructor injection sudah dihapus,
dan digantikan dengan anotasi <code class="language-plaintext highlighter-rouge">@Autowired</code>.
Semua field/property yang memiliki anotasi <code class="language-plaintext highlighter-rouge">@Autowired</code> akan diisikan oleh Spring dengan object bertipe-data sesuai.
Bila tidak ditemukan, maka aplikasi akan gagal start dengan pesan error seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SEVERE: Context initialization failed
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'produkDao':
Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field:
private javax.sql.DataSource dataSource;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No matching bean of type [javax.sql.DataSource] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
</code></pre></div></div>
<p>Bila menemukan pesan error tersebut, artinya Spring tidak memiliki satupun object bertipe <code class="language-plaintext highlighter-rouge">DataSource</code>
dalam daftar pengelolaannya. Untuk bisa dikelola oleh Spring, ada beberapa caranya, yaitu:</p>
<ul>
<li>dideklarasikan secara tertulis seperti object <code class="language-plaintext highlighter-rouge">dataSource</code> di atas</li>
<li>discan otomatis melalui <code class="language-plaintext highlighter-rouge">component-scan</code> dan anotasi <code class="language-plaintext highlighter-rouge">@Repository</code>, <code class="language-plaintext highlighter-rouge">@Service</code>, <code class="language-plaintext highlighter-rouge">@Controller</code>, ataupun<code class="language-plaintext highlighter-rouge">@Component</code>.
Contohnya object <code class="language-plaintext highlighter-rouge">produkDao</code> di atas.</li>
</ul>
<p>Ada beberapa kesalahan yang umum terjadi sehingga muncul pesan error di atas, diantaranya</p>
<ul>
<li>sudah ada anotasi, tapi package tempatnya berada tidak didaftarkan dalam <code class="language-plaintext highlighter-rouge">component-scan</code></li>
<li>package sudah didaftarkan dalam <code class="language-plaintext highlighter-rouge">component-scan</code>, tapi classnya tidak diberikan anotasi <code class="language-plaintext highlighter-rouge">@Repository</code>, <code class="language-plaintext highlighter-rouge">@Service</code>, <code class="language-plaintext highlighter-rouge">@Controller</code>, ataupun<code class="language-plaintext highlighter-rouge">@Component</code>.</li>
<li>sudah ada anotasi, packagenya sudah didaftarkan, tapi file xml yang memuat konfigurasi tersebut tidak diload oleh aplikasi.
Ini biasa terjadi kalau satu aplikasi terdiri dari banyak file konfigurasi Spring (yang mana ini adalah hal yang umum terjadi)</li>
</ul>
<p>Lalu kapan dan bagaimana Spring membaca file konfigurasi? Ada beberapa cara:</p>
<ul>
<li>Ditulis dalam kode program : <code class="language-plaintext highlighter-rouge">new ClassPathXmlApplicationContext("classpath:konfig-spring.xml")</code></li>
<li>Bila aplikasinya berbasis web, biasanya diinisialisasi melalui <code class="language-plaintext highlighter-rouge">web.xml</code> seperti ini:</li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><context-param></span>
<span class="nt"><param-name></span>contextConfigLocation<span class="nt"></param-name></span>
<span class="nt"><param-value></span>
classpath*:konfig-spring.xml
<span class="nt"></param-value></span>
<span class="nt"></context-param></span>
<span class="nt"><listener></span>
<span class="nt"><listener-class></span>org.springframework.web.context.ContextLoaderListener<span class="nt"></listener-class></span>
<span class="nt"></listener></span>
</code></pre></div></div>
<p>Demikianlah penjelasan tentang konsep Dependency Injection dan Spring Framework. Semoga bermanfaat.</p>
Membuat Changelog Liquibase2013-01-10T10:03:00+07:00https://software.endy.muhardin.com/java/membuat-changelog-liquibase<p>Di ArtiVisi, kami menggunakan tools bernama <a href="http://liquibase.org/">Liquibase</a> untuk mendefinisikan skema database.
Dengan Liquibase ini, skema database dapat disimpan dan dikelola versinya dalam Git.
Dia juga memiliki fitur untuk melakukan migrasi database pada saat aplikasi kita naik versi.
Bila terjadi error di versi baru, Liquibase juga bisa melakukan rollback
agar skema database kita kembali ke kondisi sebelum naik versi.</p>
<p>Skema database dalam Liquibase ditulis dalam format XML, disebut dengan istilah <code class="language-plaintext highlighter-rouge">changelog</code>.
Untuk project baru, ini bisa ditulis tangan secara manual,
tetapi untuk aplikasi yang sudah ada,
terlalu merepotkan kalau semua tabel yang sudah ada harus ditulis ulang skemanya.</p>
<!--more-->
<p>Untungnya Liquibase memiliki fitur untuk menulis changelog dari skema yang sudah ada.
Berikut langkah-langkahnya:</p>
<ol>
<li>Siapkan jar liquibase dan database</li>
<li>Siapkan file konfigurasi</li>
<li>Jalankan liquibase</li>
<li>Cek hasilnya</li>
</ol>
<h2 id="siapkan-jar-liquibase-dan-database">Siapkan jar liquibase dan database</h2>
<p>Database yang akan saya import memiliki konfigurasi sebagai berikut:</p>
<ul>
<li>Jenis Database : MySQL</li>
<li>Nama Database : merchant-simulator_development</li>
<li>Username Database : root</li>
<li>Password Database : admin</li>
</ul>
<p>Untuk itu, saya siapkan dua file jar, yaitu :</p>
<ul>
<li>mysql-connector-java-5.1.22.jar</li>
<li>liquibase-core-2.0.5.jar</li>
</ul>
<h2 id="file-konfigurasi">File Konfigurasi</h2>
<p>Sebetulnya semua informasi ini bisa disebutkan dalam command line argument.
Tapi saya lebih suka menulis di file supaya mudah diedit.
Berikut isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># liquibase.properties
driver: com.mysql.jdbc.Driver
classpath: mysql-connector-java-5.1.22.jar
url: jdbc:mysql://localhost/merchant-simulator_development
username: root
password: admin
changeLogFile: liquibase-output.xml
</code></pre></div></div>
<p>Simpan file ini dengan nama <code class="language-plaintext highlighter-rouge">liquibase.properties</code> dan letakkan di folder yang sama dengan kedua file jar tersebut.</p>
<h2 id="jalankan-liquibase">Jalankan Liquibase</h2>
<p>Mari kita jalankan proses import dengan command berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar liquibase-core-2.0.5.jar generateChangelog
</code></pre></div></div>
<h2 id="cek-hasilnya">Cek Hasilnya</h2>
<p>Perintah di atas akan menghasilkan file <code class="language-plaintext highlighter-rouge">liquibase-output.xml</code> yang isinya seperti ini:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8" standalone="no"?></span>
<span class="nt"><databaseChangeLog</span> <span class="na">xmlns=</span><span class="s">"http://www.liquibase.org/xml/ns/dbchangelog"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="na">xsi:schemaLocation=</span><span class="s">"http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"</span><span class="nt">></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-1"</span><span class="nt">></span>
<span class="nt"><createTable</span> <span class="na">tableName=</span><span class="s">"c_application_config"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span> <span class="na">primaryKey=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"name"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"label"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"value"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></createTable></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-2"</span><span class="nt">></span>
<span class="nt"><createTable</span> <span class="na">tableName=</span><span class="s">"c_security_menu"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span> <span class="na">primaryKey=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"label"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"menu_action"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">/></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"menu_level"</span> <span class="na">type=</span><span class="s">"INT"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"menu_order"</span> <span class="na">type=</span><span class="s">"INT"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"menu_options"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">/></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id_parent"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">/></span>
<span class="nt"></createTable></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-3"</span><span class="nt">></span>
<span class="nt"><createTable</span> <span class="na">tableName=</span><span class="s">"c_security_permission"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span> <span class="na">primaryKey=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"permission_label"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"permission_value"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></createTable></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-4"</span><span class="nt">></span>
<span class="nt"><createTable</span> <span class="na">tableName=</span><span class="s">"c_security_role"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span> <span class="na">primaryKey=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"name"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"description"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">/></span>
<span class="nt"></createTable></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-5"</span><span class="nt">></span>
<span class="nt"><createTable</span> <span class="na">tableName=</span><span class="s">"c_security_role_menu"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id_role"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id_menu"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></createTable></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-6"</span><span class="nt">></span>
<span class="nt"><createTable</span> <span class="na">tableName=</span><span class="s">"c_security_role_permission"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id_role"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id_permission"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></createTable></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-7"</span><span class="nt">></span>
<span class="nt"><createTable</span> <span class="na">tableName=</span><span class="s">"c_security_user"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span> <span class="na">primaryKey=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"fullname"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">/></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"active"</span> <span class="na">type=</span><span class="s">"BIT"</span><span class="nt">/></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id_role"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"></createTable></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-8"</span><span class="nt">></span>
<span class="nt"><createTable</span> <span class="na">tableName=</span><span class="s">"t_tagihan_listrik"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span> <span class="na">primaryKey=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"admin"</span> <span class="na">type=</span><span class="s">"DECIMAL(19,2)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"bulan_tagihan"</span> <span class="na">type=</span><span class="s">"DATE"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"current_meter_reading_1"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"current_meter_reading_2"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"current_meter_reading_3"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"denda"</span> <span class="na">type=</span><span class="s">"DECIMAL(19,2)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id_pelanggan"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"jatuh_tempo"</span> <span class="na">type=</span><span class="s">"DATE"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"lunas"</span> <span class="na">type=</span><span class="s">"BIT"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"meter_read_date"</span> <span class="na">type=</span><span class="s">"DATE"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"mitra_lunas"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">/></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"nama_pelanggan"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"nilai"</span> <span class="na">type=</span><span class="s">"DECIMAL(19,2)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"pajak"</span> <span class="na">type=</span><span class="s">"DECIMAL(19,2)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"power_consuming_category"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"previous_meter_reading_1"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"previous_meter_reading_2"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"previous_meter_reading_3"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"referensi_lunas"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">/></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"service_unit"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"service_unit_hone"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"subscriber_segmentation"</span> <span class="na">type=</span><span class="s">"VARCHAR(255)"</span><span class="nt">></span>
<span class="nt"><constraints</span> <span class="na">nullable=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></column></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"waktu_lunas"</span> <span class="na">type=</span><span class="s">"DATETIME"</span><span class="nt">/></span>
<span class="nt"></createTable></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-9"</span><span class="nt">></span>
<span class="nt"><addPrimaryKey</span> <span class="na">columnNames=</span><span class="s">"id_role, id_menu"</span> <span class="na">tableName=</span><span class="s">"c_security_role_menu"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-10"</span><span class="nt">></span>
<span class="nt"><addPrimaryKey</span> <span class="na">columnNames=</span><span class="s">"id_role, id_permission"</span> <span class="na">tableName=</span><span class="s">"c_security_role_permission"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-11"</span><span class="nt">></span>
<span class="nt"><addForeignKeyConstraint</span> <span class="na">baseColumnNames=</span><span class="s">"id_parent"</span> <span class="na">baseTableName=</span><span class="s">"c_security_menu"</span> <span class="na">baseTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">constraintName=</span><span class="s">"fk_menu_parent"</span> <span class="na">deferrable=</span><span class="s">"false"</span> <span class="na">initiallyDeferred=</span><span class="s">"false"</span> <span class="na">onDelete=</span><span class="s">"NO ACTION"</span> <span class="na">onUpdate=</span><span class="s">"NO ACTION"</span> <span class="na">referencedColumnNames=</span><span class="s">"id"</span> <span class="na">referencedTableName=</span><span class="s">"c_security_menu"</span> <span class="na">referencedTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">referencesUniqueColumn=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-12"</span><span class="nt">></span>
<span class="nt"><addForeignKeyConstraint</span> <span class="na">baseColumnNames=</span><span class="s">"id_menu"</span> <span class="na">baseTableName=</span><span class="s">"c_security_role_menu"</span> <span class="na">baseTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">constraintName=</span><span class="s">"fk_role_menu_menu"</span> <span class="na">deferrable=</span><span class="s">"false"</span> <span class="na">initiallyDeferred=</span><span class="s">"false"</span> <span class="na">onDelete=</span><span class="s">"NO ACTION"</span> <span class="na">onUpdate=</span><span class="s">"NO ACTION"</span> <span class="na">referencedColumnNames=</span><span class="s">"id"</span> <span class="na">referencedTableName=</span><span class="s">"c_security_menu"</span> <span class="na">referencedTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">referencesUniqueColumn=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-13"</span><span class="nt">></span>
<span class="nt"><addForeignKeyConstraint</span> <span class="na">baseColumnNames=</span><span class="s">"id_role"</span> <span class="na">baseTableName=</span><span class="s">"c_security_role_menu"</span> <span class="na">baseTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">constraintName=</span><span class="s">"fk_role_menu_role"</span> <span class="na">deferrable=</span><span class="s">"false"</span> <span class="na">initiallyDeferred=</span><span class="s">"false"</span> <span class="na">onDelete=</span><span class="s">"NO ACTION"</span> <span class="na">onUpdate=</span><span class="s">"NO ACTION"</span> <span class="na">referencedColumnNames=</span><span class="s">"id"</span> <span class="na">referencedTableName=</span><span class="s">"c_security_role"</span> <span class="na">referencedTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">referencesUniqueColumn=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-14"</span><span class="nt">></span>
<span class="nt"><addForeignKeyConstraint</span> <span class="na">baseColumnNames=</span><span class="s">"id_permission"</span> <span class="na">baseTableName=</span><span class="s">"c_security_role_permission"</span> <span class="na">baseTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">constraintName=</span><span class="s">"fk_role_permission_permission"</span> <span class="na">deferrable=</span><span class="s">"false"</span> <span class="na">initiallyDeferred=</span><span class="s">"false"</span> <span class="na">onDelete=</span><span class="s">"NO ACTION"</span> <span class="na">onUpdate=</span><span class="s">"NO ACTION"</span> <span class="na">referencedColumnNames=</span><span class="s">"id"</span> <span class="na">referencedTableName=</span><span class="s">"c_security_permission"</span> <span class="na">referencedTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">referencesUniqueColumn=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-15"</span><span class="nt">></span>
<span class="nt"><addForeignKeyConstraint</span> <span class="na">baseColumnNames=</span><span class="s">"id_role"</span> <span class="na">baseTableName=</span><span class="s">"c_security_role_permission"</span> <span class="na">baseTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">constraintName=</span><span class="s">"fk_role_permission_role"</span> <span class="na">deferrable=</span><span class="s">"false"</span> <span class="na">initiallyDeferred=</span><span class="s">"false"</span> <span class="na">onDelete=</span><span class="s">"NO ACTION"</span> <span class="na">onUpdate=</span><span class="s">"NO ACTION"</span> <span class="na">referencedColumnNames=</span><span class="s">"id"</span> <span class="na">referencedTableName=</span><span class="s">"c_security_role"</span> <span class="na">referencedTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">referencesUniqueColumn=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-16"</span><span class="nt">></span>
<span class="nt"><addForeignKeyConstraint</span> <span class="na">baseColumnNames=</span><span class="s">"id_role"</span> <span class="na">baseTableName=</span><span class="s">"c_security_user"</span> <span class="na">baseTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">constraintName=</span><span class="s">"fk_user_role"</span> <span class="na">deferrable=</span><span class="s">"false"</span> <span class="na">initiallyDeferred=</span><span class="s">"false"</span> <span class="na">onDelete=</span><span class="s">"NO ACTION"</span> <span class="na">onUpdate=</span><span class="s">"NO ACTION"</span> <span class="na">referencedColumnNames=</span><span class="s">"id"</span> <span class="na">referencedTableName=</span><span class="s">"c_security_role"</span> <span class="na">referencedTableSchemaName=</span><span class="s">"merchant-simulator_development"</span> <span class="na">referencesUniqueColumn=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-17"</span><span class="nt">></span>
<span class="nt"><createIndex</span> <span class="na">indexName=</span><span class="s">"name"</span> <span class="na">tableName=</span><span class="s">"c_application_config"</span> <span class="na">unique=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"name"</span><span class="nt">/></span>
<span class="nt"></createIndex></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-18"</span><span class="nt">></span>
<span class="nt"><createIndex</span> <span class="na">indexName=</span><span class="s">"unique_permission_value"</span> <span class="na">tableName=</span><span class="s">"c_security_permission"</span> <span class="na">unique=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"permission_value"</span><span class="nt">/></span>
<span class="nt"></createIndex></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-19"</span><span class="nt">></span>
<span class="nt"><createIndex</span> <span class="na">indexName=</span><span class="s">"unique_role_name"</span> <span class="na">tableName=</span><span class="s">"c_security_role"</span> <span class="na">unique=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"name"</span><span class="nt">/></span>
<span class="nt"></createIndex></span>
<span class="nt"></changeSet></span>
<span class="nt"><changeSet</span> <span class="na">author=</span><span class="s">"endy (generated)"</span> <span class="na">id=</span><span class="s">"1357786892853-20"</span><span class="nt">></span>
<span class="nt"><createIndex</span> <span class="na">indexName=</span><span class="s">"unique_user_username"</span> <span class="na">tableName=</span><span class="s">"c_security_user"</span> <span class="na">unique=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"username"</span><span class="nt">/></span>
<span class="nt"></createIndex></span>
<span class="nt"></changeSet></span>
<span class="nt"></databaseChangeLog></span>
</code></pre></div></div>
<p>File di atas siap diedit dan disimpan di repository version control.
Biasanya, edit yang saya lakukan:</p>
<ul>
<li>menggabungkan semua <code class="language-plaintext highlighter-rouge">createTable</code> menjadi satu changeset</li>
<li>menggabungkan semua <code class="language-plaintext highlighter-rouge">addForeignKeyConstraint</code> dan <code class="language-plaintext highlighter-rouge">createIndex</code> menjadi satu changeset</li>
<li>menambahkan loader untuk file csv sebagai data awal</li>
<li>mengganti nama file</li>
</ul>
Membuat Audit Log2012-10-24T11:44:00+07:00https://software.endy.muhardin.com/java/membuat-audit-log<p>Dalam membuat aplikasi bisnis, kita sering diminta membuat audit log.</p>
<blockquote>
<p>Apa itu audit log?</p>
</blockquote>
<p>Audit log adalah catatan mengenai perubahan data dalam aplikasi.
Yang dicatat biasanya :</p>
<ul>
<li>kolom mana yang berubah</li>
<li>siapa yang mengubah</li>
<li>diubah dari apa menjadi apa</li>
<li>kapan dia berubah</li>
</ul>
<!--more-->
<p>Dengan menggunakan <a href="http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html/ch15.html">Hibernate Envers</a>,
audit log ini bisa dibuat dengan mudah sekali.
Hibernate Envers adalah salah satu modul tambahan <a href="http://hibernate.org/">Hibernate</a> untuk keperluan ini.
Tentunya aplikasi kita harus menggunakan Hibernate supaya bisa memanfaatkan fitur ini.</p>
<h2 id="konfigurasi-awal">Konfigurasi Awal</h2>
<p>Konfigurasi dan setup awal sangat mudah, yaitu:</p>
<ol>
<li>Tambahkan JAR hibernate-envers ke dalam aplikasi</li>
<li>Tambahkan anotasi <code class="language-plaintext highlighter-rouge">@Audited</code> di class <code class="language-plaintext highlighter-rouge">@Entity</code> kita.</li>
</ol>
<p>Contoh perubahan ini bisa dilihat <a href="https://github.com/endymuhardin/belajar-auditlog/commit/05ca0c7c90b10cf64560d4cec933774aa91a8a81">di sini</a>.</p>
<h2 id="audit-log-sederhana">Audit Log Sederhana</h2>
<p>Entity class yang sudah ditambahi dengan annotation <code class="language-plaintext highlighter-rouge">@Audited</code> tampak seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span> <span class="nd">@Audited</span>
<span class="nd">@Table</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"m_kategori"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Kategori</span> <span class="o">{</span>
<span class="nd">@Id</span> <span class="nd">@GeneratedValue</span><span class="o">(</span><span class="n">strategy</span><span class="o">=</span> <span class="nc">GenerationType</span><span class="o">.</span><span class="na">AUTO</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span><span class="o">=</span><span class="kc">false</span><span class="o">,</span> <span class="n">unique</span><span class="o">=</span><span class="kc">true</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">kode</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">nama</span><span class="o">;</span>
<span class="c1">// getter setter tidak ditampilkan</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada awalnya, mapping di atas akan menghasilkan skema database seperti ini:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`m_kategori`</span> <span class="p">(</span>
<span class="nv">`id`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="nv">`kode`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`nama`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="nv">`id`</span><span class="p">),</span>
<span class="k">UNIQUE</span> <span class="k">KEY</span> <span class="nv">`kode`</span> <span class="p">(</span><span class="nv">`kode`</span><span class="p">)</span>
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span><span class="p">;</span>
</code></pre></div></div>
<p>Setelah ditambahi <code class="language-plaintext highlighter-rouge">@Audited</code>, Envers akan menambahkan satu tabel untuk keperluan audit log <code class="language-plaintext highlighter-rouge">m_kategori</code> dengan nama tabel ditambahi akhiran <code class="language-plaintext highlighter-rouge">_AUD</code> dan memiliki skema seperti ini:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`m_kategori_AUD`</span> <span class="p">(</span>
<span class="nv">`id`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`REV`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`REVTYPE`</span> <span class="nb">tinyint</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`kode`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`nama`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="nv">`id`</span><span class="p">,</span><span class="nv">`REV`</span><span class="p">),</span>
<span class="k">KEY</span> <span class="nv">`FK4263149C625F360`</span> <span class="p">(</span><span class="nv">`REV`</span><span class="p">),</span>
<span class="k">CONSTRAINT</span> <span class="nv">`FK4263149C625F360`</span> <span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="nv">`REV`</span><span class="p">)</span> <span class="k">REFERENCES</span> <span class="nv">`REVINFO`</span> <span class="p">(</span><span class="nv">`id`</span><span class="p">)</span>
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span><span class="p">;</span>
</code></pre></div></div>
<p>Kolom dalam tabelnya tidak jauh berbeda, dengan perbedaan sebagai berikut:</p>
<ul>
<li>
<p>tambahan kolom <code class="language-plaintext highlighter-rouge">REV</code> yang menunjukkan urutan perubahan.
Nilai <code class="language-plaintext highlighter-rouge">REV</code> ini tidak berurut karena penambahannya (increment) sharing dengan semua tabel lain.
Bisa saja <code class="language-plaintext highlighter-rouge">REV</code> <code class="language-plaintext highlighter-rouge">1</code> mencatat perubahan di <code class="language-plaintext highlighter-rouge">m_kategori</code> sedangkan <code class="language-plaintext highlighter-rouge">REV</code> <code class="language-plaintext highlighter-rouge">2</code> mencatat perubahan <code class="language-plaintext highlighter-rouge">m_produk</code>.</p>
</li>
<li>
<p>kolom <code class="language-plaintext highlighter-rouge">REVTYPE</code> yang menunjukkan jenis perubahan.
Nilainya <code class="language-plaintext highlighter-rouge">0</code> untuk <strong>insert</strong> record baru, <code class="language-plaintext highlighter-rouge">1</code> untuk <strong>modifikasi</strong>, dan <code class="language-plaintext highlighter-rouge">2</code> untuk <strong>hapus</strong></p>
</li>
<li>
<p>kolom <code class="language-plaintext highlighter-rouge">id</code> tidak lagi menjadi primary key sendirian, tapi bersama dengan kolom <code class="language-plaintext highlighter-rouge">REV</code></p>
</li>
</ul>
<p>Kita juga lihat ada relasi ke tabel <code class="language-plaintext highlighter-rouge">REVINFO</code>. Skemanya sebagai berikut:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`REVINFO`</span> <span class="p">(</span>
<span class="nv">`id`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="nv">`timestamp`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span>
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="nv">`id`</span><span class="p">)</span>
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span><span class="p">;</span>
</code></pre></div></div>
<p>Kolom <code class="language-plaintext highlighter-rouge">id</code> merupakan nilai yang bertambah terus (auto increment). Tabel ini akan diisi setiap kali ada revisi di tabel yang dipantau oleh Envers seperti tabel <code class="language-plaintext highlighter-rouge">m_kategori</code> kita di atas.
Tabel ini dishare oleh semua tabel audit log sehingga untuk satu tabel yang sama, nomer <code class="language-plaintext highlighter-rouge">REV</code> belum tentu berurutan. Bisa saja diselingi oleh nomer <code class="language-plaintext highlighter-rouge">REV</code> yang berkaitan dengan tabel lain. Kolom <code class="language-plaintext highlighter-rouge">timestamp</code> menunjukkan kapan revisi terjadi.</p>
<p>Untuk lebih memahami penggunaan tabel-tabel ini, mari kita lihat isi datanya.</p>
<p>Pertama, kita jalankan kode berikut dari aplikasi kita:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCrudKategori</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Kategori</span> <span class="n">k</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Kategori</span><span class="o">();</span>
<span class="n">k</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="s">"K-999"</span><span class="o">);</span>
<span class="n">k</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Kategori 999"</span><span class="o">);</span>
<span class="n">assertNull</span><span class="o">(</span><span class="n">k</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="n">belajarService</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">k</span><span class="o">);</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">k</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="n">k</span><span class="o">.</span><span class="na">setKode</span><span class="o">(</span><span class="s">"K-999-X"</span><span class="o">);</span>
<span class="n">k</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Kategori 999-X"</span><span class="o">);</span>
<span class="n">belajarService</span><span class="o">.</span><span class="na">simpan</span><span class="o">(</span><span class="n">k</span><span class="o">);</span>
<span class="nc">Kategori</span> <span class="n">kx</span> <span class="o">=</span> <span class="n">belajarService</span><span class="o">.</span><span class="na">cariKategoriById</span><span class="o">(</span><span class="n">k</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"K-999-X"</span><span class="o">,</span> <span class="n">kx</span><span class="o">.</span><span class="na">getKode</span><span class="o">());</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"Kategori 999-X"</span><span class="o">,</span> <span class="n">kx</span><span class="o">.</span><span class="na">getNama</span><span class="o">());</span>
<span class="n">belajarService</span><span class="o">.</span><span class="na">hapus</span><span class="o">(</span><span class="n">k</span><span class="o">);</span>
<span class="n">assertNull</span><span class="o">(</span><span class="n">belajarService</span><span class="o">.</span><span class="na">cariKategoriById</span><span class="o">(</span><span class="n">k</span><span class="o">.</span><span class="na">getId</span><span class="o">()));</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Operasi di atas akan mengisi tabel audit sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from m_kategori_AUD order by id,REV,REVTYPE;
+-----+-----+---------+---------+-----------------------+
| id | REV | REVTYPE | kode | nama |
+-----+-----+---------+---------+-----------------------+
| 101 | 2 | 0 | K-999 | Kategori 999 |
| 101 | 3 | 1 | K-999-X | Kategori 999-X |
| 101 | 4 | 2 | NULL | NULL |
+-----+-----+---------+---------+-----------------------+
</code></pre></div></div>
<p>Data dengan <code class="language-plaintext highlighter-rouge">REV</code> <code class="language-plaintext highlighter-rouge">1</code> dan <code class="language-plaintext highlighter-rouge">2</code> tidak ditampilkan karena mencatat record lain.
Dari isi tabel ini, kita bisa melihat bahwa terjadi tiga kali operasi pada record kategori dengan id 101, yaitu:</p>
<ol>
<li>Insert record baru. <code class="language-plaintext highlighter-rouge">REVTYPE</code> berisi <code class="language-plaintext highlighter-rouge">0</code>, artinya record baru. Kolom id, kode, dan nama berisi nilai yang pertama diisi.</li>
<li>Update kode dan nama. <code class="language-plaintext highlighter-rouge">REVTYPE</code> berisi <code class="language-plaintext highlighter-rouge">1</code>, artinya modifikasi. Kolom kode dan nama diisi dengan nilai setelah modifikasi</li>
<li>Delete record. <code class="language-plaintext highlighter-rouge">REVTYPE</code> berisi <code class="language-plaintext highlighter-rouge">2</code>, artinya hapus. Kolom kode dan nama diisi null karena datanya sudah tidak relevan (karena record dihapus).</li>
</ol>
<p>Berikut isi tabel <code class="language-plaintext highlighter-rouge">REVINFO</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from REVINFO;
+----+---------------+
| id | timestamp |
+----+---------------+
| 2 | 1351521839648 |
| 3 | 1351521839718 |
| 4 | 1351521839801 |
+----+---------------+
</code></pre></div></div>
<p>Karena hanya mencatat timestamp saja, maka tidak ada yang perlu dijelaskan mengenai isi tabel <code class="language-plaintext highlighter-rouge">REVINFO</code>.</p>
<h2 id="penambahan-informasi-yang-ingin-dicatat">Penambahan informasi yang ingin dicatat</h2>
<p>Secara default, Hibernate Envers hanya mencatat data yang berubah dan kapan dia berubah.
Untuk menambahkan catatan tentang username, perlu ada sedikit coding seperti bisa dilihat <a href="https://github.com/endymuhardin/belajar-auditlog/commit/e910b6bc5ef33ee61f0137c8297b0f2ec4f502fe">di sini</a>. Kita harus :</p>
<ol>
<li>Extends <code class="language-plaintext highlighter-rouge">DefaultRevisionEntity</code> dan menambahkan kolom username</li>
<li>Membuat <code class="language-plaintext highlighter-rouge">RevisionListener</code> untuk mengisi kolom username tersebut</li>
</ol>
<p>Data username biasanya diambilkan dari user yang sedang login di aplikasi (dan dengan sendirinya dialah yang melakukan perubahan data).</p>
<p>Setelah dilakukan dua modifikasi di atas, skema REVINFO menjadi sebagai berikut:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`REVINFO`</span> <span class="p">(</span>
<span class="nv">`id`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="nv">`timestamp`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`username`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="nv">`id`</span><span class="p">)</span>
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span><span class="p">;</span>
</code></pre></div></div>
<p>Berikut yang sudah terisi data:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from REVINFO;
+----+---------------+----------+
| id | timestamp | username |
+----+---------------+----------+
| 1 | 1351521839220 | endy |
| 2 | 1351521839648 | endy |
| 3 | 1351521839718 | endy |
| 4 | 1351521839801 | endy |
| 5 | 1351521840174 | endy |
| 6 | 1351521840571 | endy |
| 7 | 1351521840959 | endy |
| 8 | 1351521841351 | endy |
+----+---------------+----------+
</code></pre></div></div>
<h2 id="penyesuaian-lain">Penyesuaian Lain</h2>
<p>Selain penambahan kolom yang ingin dicatat, biasanya kita juga ingin melakukan penyesuaian lain seperti:</p>
<ul>
<li>akhiran di nama tabel audit (<code class="language-plaintext highlighter-rouge">_AUD</code>)</li>
<li>nama kolom nomer revisi (<code class="language-plaintext highlighter-rouge">REV</code>)</li>
<li>nama kolom jenis revisi (<code class="language-plaintext highlighter-rouge">REVTYPE</code>)</li>
<li>dan sebagainya</li>
</ul>
<p>Keterangan apa saja yang bisa diubah dan cara mengubahnya dapat dilihat di <a href="http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html/ch15.html#d5e3937">dokumentasi konfigurasi Envers</a>.</p>
<p>Contoh kode yang lengkap bisa diambil di <a href="https://github.com/endymuhardin/belajar-auditlog">repository saya di Github</a>.</p>
Buku Panduan Markdown dan Pandoc2012-09-26T12:26:00+07:00https://software.endy.muhardin.com/aplikasi/buku-panduan-markdown-dan-pandoc<p>Menyusul <a href="http://software.endy.muhardin.com/aplikasi/membuat-dokumen-dengan-markdown-dan-pandoc/">postingan sebelumnya</a>, saya telah menulis buku panduan cara menggunakan Markdown dan Pandoc.</p>
<p>Buku ini tentu saja ditulis menggunakan Markdown dan dikonversi ke PDF menggunakan Pandoc. Silahkan digunakan:</p>
<ul>
<li>Source code ada di <a href="https://github.com/endymuhardin/buku-pandoc">Github</a></li>
<li>Hasil PDF yang sudah jadi bisa diunduh <a href="https://raw.github.com/endymuhardin/buku-pandoc/master/buku/markdown-dan-pandoc.pdf">di sini</a>.</li>
</ul>
<p>Semoga bermanfaat.</p>
Membuat dokumen dengan Markdown dan Pandoc2012-09-05T11:53:00+07:00https://software.endy.muhardin.com/aplikasi/membuat-dokumen-dengan-markdown-dan-pandoc<p>Sudah lama saya mencari format yang tepat untuk membuat dokumentasi. Baik untuk menulis ebook, user manual, modul pelatihan, dan berbagai keperluan penulisan lainnya. Beberapa fitur yang saya inginkan antara lain:</p>
<ol>
<li>berbentuk text file biasa. Dengan menggunakan format text file biasa, ada beberapa keuntungan yang bisa diambil:
<ul>
<li>Bisa dikelola di version control seperti Git atau Subversion</li>
<li>Bisa diedit di komputer, handphone, ataupun tablet</li>
<li>Ukurannya kecil, sehingga memudahkan penyimpanan di cloud storage seperti Dropbox</li>
</ul>
</li>
<li>Bisa dikonversi ke format lain, terutama pdf</li>
<li>Mudah dipahami</li>
<li>Bisa diwarnai dengan syntax highlighting</li>
<li>Bisa autocomplete</li>
</ol>
<p>Dari empat fitur di atas, yang wajib cuma yang pertama dan kedua saja. Fitur lainnya bersifat <em>nice to have</em>, ada syukur, gak ada juga tidak terlalu masalah.</p>
<!--more-->
<p>Setelah google kesana-kemari, ada dua format yang kira-kira memenuhi syarat di atas, yaitu <a href="http://en.wikipedia.org/wiki/Markdown">Markdown</a> dan <a href="http://en.wikipedia.org/wiki/DocBook">Docbook</a>. Dua-duanya mudah dipahami. Walaupun demikian, format Markdown lebih mudah diedit, karena menggunakan markup yang enak dilihat mata. Sedangkan Docbook menggunakan format XML, selain kurang WYSYWIG, juga bikin capek kelingking karena harus mengetik kurung siku terus menerus. Kurung siku ini juga menyulitkan kalau kita mengetik di handphone atau tablet.</p>
<p>Jadi, disimpulkan kita akan menggunakan format Markdown saja. Ini juga sejalan dengan aplikasi yang saya gunakan untuk menulis blog, yaitu <a href="http://octopress.org/">Octopress</a>. Untuk melihat bagaimana tampilan file markdown, silahkan unduh <a href="https://raw.github.com/endymuhardin/belajar-pandoc/master/01-akses-database-spring-25.md">file contoh yang saya gunakan di bawah</a>, kemudian buka dengan text editor primitif seperti Notepad atau Gedit.</p>
<p>Setelah dokumen ditulis, kita bisa melakukan konversi dengan menggunakan aplikasi bernama <a href="http://johnmacfarlane.net/pandoc/index.html">Pandoc</a>. Pandoc ini adalah aplikasi yang dibuat oleh <a href="http://johnmacfarlane.net/">John MacFarlane</a>, seorang profesor filosofi di University of California, Berkeley. Pandoc dibuat dengan bahasa pemrograman <a href="http://en.wikipedia.org/wiki/Haskell_(programming_language)">Haskell</a>.</p>
<h1 id="instalasi-pandoc">Instalasi Pandoc</h1>
<p>Di Ubuntu, instalasinya tidak sulit, cukup jalankan perintah berikut:</p>
<p><code class="language-plaintext highlighter-rouge">sudo apt-get install pandoc texlive-latex-base texlive-xetex latex-beamer</code></p>
<p>Instalasi untuk Windows saya tidak tahu dan juga tidak berminat untuk cari tahu. Silahkan baca <a href="http://johnmacfarlane.net/pandoc/installing.html">petunjuk instalasi</a>.</p>
<h1 id="cara-pemakaian">Cara Pemakaian</h1>
<p>Cara pakainya tidak sulit, cukup dengan perintah sederhana seperti ini:</p>
<p><code class="language-plaintext highlighter-rouge">pandoc -o hasil.pdf input.md</code></p>
<p>File <code class="language-plaintext highlighter-rouge">input.md</code> adalah tulisan kita dalam format markdown, sedangkan opsi <code class="language-plaintext highlighter-rouge">-o hasil.pdf</code> menunjukkan file output yang ingin dihasilkan. Pandoc cukup cerdas untuk mengetahui format output yang diinginkan dengan melihat ekstensi di nama file. Jadi, kalau kita berikan opsi <code class="language-plaintext highlighter-rouge">-o hasil.html</code>, dia akan mengkonversi tulisan kita menjadi file HTML.</p>
<p>Lebih jauh tentang cara pemakaian Pandoc bisa dilihat di <a href="http://johnmacfarlane.net/pandoc/README.html">user manualnya</a>. Selain itu, kita juga bisa melihat <a href="http://johnmacfarlane.net/pandoc/demos.html">berbagai contoh pemakaian Pandoc</a> di websitenya. Beberapa contoh yang menarik diantaranya adalah cara menghasilkan slide presentasi dengan format HTML, yaitu di contoh nomer 16.</p>
<h1 id="customization">Customization</h1>
<p>Untuk kebutuhan modul pelatihan ArtiVisi, tentu kita membutuhkan sedikit penyesuaian, yaitu:</p>
<ul>
<li>font serif diganti menjadi Droid Serif. Font serif ini biasa digunakan untuk paragraf.</li>
<li>font sans diganti menjadi Droid Sans. Font sans biasa digunakan untuk judul.</li>
<li>font monospace diganti menjadi Inconsolata. Font monospace biasa digunakan untuk contoh kode program.</li>
<li>cover depan. Pandoc secara default tidak membuatkan cover depan. Kita perlu membuat template cover sendiri.</li>
</ul>
<p>Penyesuaian di atas dilakukan dengan cara membuat template dokumen. Template ini berbeda-beda tergantung jenis output yang akan dihasilkan.</p>
<h2 id="template-open-office">Template Open Office</h2>
<p>Untuk membuat template dokumen Open Office, berikut langkah-langkahnya:</p>
<ol>
<li>Generate dulu dokumen Open Office dengan perintah <code class="language-plaintext highlighter-rouge">pandoc -o hasil.odt input.md</code></li>
<li>Buka <code class="language-plaintext highlighter-rouge">hasil.odt</code> dengan Open Office.</li>
<li>Edit masing-masing style yang digunakan, misalnya TextBody, PreformattedText, dsb.</li>
<li>Simpan dokumen tersebut, dan rename menjadi <code class="language-plaintext highlighter-rouge">template.odt</code></li>
<li>Generate lagi dokumen Open Office menggunakan template tersebut dengan perintah <code class="language-plaintext highlighter-rouge">pandoc --reference-odt=template.odt -o hasil.odt input.md</code></li>
</ol>
<p>Cara ini saya dapatkan dari <a href="http://maketecheasier.com/use-pandoc-convert-text-to-ebook/2012/09/01">tutorial ini</a>.</p>
<h2 id="template-pdf">Template PDF</h2>
<p>Pandoc menghasilkan dokumen PDF secara dua tahap. Tahap pertama adalah konversi dari <code class="language-plaintext highlighter-rouge">Markdown</code> menjadi <code class="language-plaintext highlighter-rouge">LaTeX</code>. Kemudian dari format <code class="language-plaintext highlighter-rouge">LaTeX</code> akan dikonversi lagi menjadi <code class="language-plaintext highlighter-rouge">PDF</code>. Perlu diperhatikan, yang dimaksud <code class="language-plaintext highlighter-rouge">LaTeX</code> di sini adalah format penulisan dokumen, bukan bahan baku pembuatan alat kontrasepsi.</p>
<p>Dengan demikian, untuk menyesuaikan output PDF, kita perlu membuat template <code class="language-plaintext highlighter-rouge">LaTeX</code>. Prof John sudah memberi tahu cara pakai template di contoh nomer 14. Di situ kita bisa mengunduh <a href="http://johnmacfarlane.net/pandoc/demo/mytemplate.tex">contoh template</a> yang dia gunakan. Contoh template ini akan menjadi titik awal kita untuk melakukan penyesuaian.</p>
<p>Di contoh template tersebut, beliau sudah memasang font yang bisa dicustomize melalui opsi <code class="language-plaintext highlighter-rouge">--variable</code>. Yang kurang adalah halaman sampul (cover page). Saya menemukan <a href="http://www.latextemplates.com/">daftar template cover yang siap pakai</a>, dan juga <a href="http://en.wikibooks.org/wiki/LaTeX/Title_Creation">tutorial cara mendesain cover sendiri</a>. Untuk penyesuaian lebih lanjut, kita bisa berpedoman pada <a href="http://en.wikibooks.org/wiki/LaTeX/Command_Glossary">dokumentasi perintah Latex</a>.</p>
<p>Setelah template selesai dibuat, kita bisa langsung gunakan dengan perintah sebagai berikut:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pandoc <span class="nt">--template</span> artivisi-template.tex <span class="nt">--variable</span> <span class="nv">mainfont</span><span class="o">=</span><span class="s2">"Droid Serif"</span> <span class="nt">--variable</span> <span class="nv">sansfont</span><span class="o">=</span><span class="s2">"Droid Sans"</span> <span class="nt">--variable</span> <span class="nv">fontsize</span><span class="o">=</span>12pt <span class="nt">--variable</span> <span class="nv">version</span><span class="o">=</span>1.0 <span class="nt">--variable</span> <span class="nv">subtitle</span><span class="o">=</span><span class="s2">"Mengakses database menggunakan Spring-JDBC"</span> <span class="nt">--latex-engine</span><span class="o">=</span>xelatex <span class="nt">--toc</span> <span class="nt">-N</span> <span class="nt">-o</span> hasil.pdf <span class="k">*</span>md
</code></pre></div></div>
<p>Anda bisa melihat <a href="https://raw.github.com/endymuhardin/belajar-pandoc/master/output/coba.pdf">file PDF yang dihasilkan</a>, <a href="https://raw.github.com/endymuhardin/belajar-pandoc/master/00-cover.md">file markdown untuk cover</a>, <a href="https://raw.github.com/endymuhardin/belajar-pandoc/master/01-akses-database-spring-25.md">file markdown isi artikel</a>, dan <a href="https://raw.github.com/endymuhardin/belajar-pandoc/master/artivisi-template.tex">template LaTeX yang digunakan</a>. Atau bisa juga unduh <a href="https://nodeload.github.com/endymuhardin/belajar-pandoc/zipball/master">file zip</a> yang berisi semua file yang dibutuhkan untuk menjalankan perintah di atas.</p>
<h1 id="kesimpulan">Kesimpulan</h1>
<p>Dengan menggunakan format berbasis text, kita bisa memperoleh keuntungan sebagai berikut:</p>
<ul>
<li>bisa diedit menggunakan aplikasi apa saja, termasuk Notepad</li>
<li>bisa dikelola menggunakan aplikasi version control</li>
<li>bisa diedit kapan saja dan di mana saja (menggunakan Dropbox) dengan device apa saja (handphone, tablet, komputer)</li>
<li>penulis bisa lebih fokus pada isi artikel, tidak memusingkan urusan formatting seperti besar kecil huruf, jenis font, penomoran bab, dan hal-hal lain yang bersifat tampilan</li>
</ul>
<p>Aplikasi pandoc bisa digunakan untuk mengkonversi berbagai format file seperti Markdown, Docbook, LaTeX, dan sebagainya menjadi berbagai format seperti PDF, HTML, Open Office, Microsoft Word, dan sebagainya.</p>
Release Management2012-08-29T18:01:00+07:00https://software.endy.muhardin.com/manajemen/release-management<p>Release, atau di-Indonesia-kan menjadi rilis, adalah tahap yang paling penting dalam software development. Segala kegiatan hulu (upstream activity) lainnya seperti <a href="/manajemen/fase-requirement/" title="Fase Requirement">requirement</a>, desain aplikasi, coding, testing, dan lainnya, semua dilakukan demi untuk menghasilkan software yang bisa dirilis. Sebagus apapun kita melakukan kegiatan lainnya, jika rilisnya tidak bagus, maka semua yang kita kerjakan menjadi tidak bagus. Sebaliknya, berbagai kesalahan dan kekurangan di kegiatan lain akan mudah dimaafkan dan dilupakan kalau kita menghasilkan rilis yang baik. Walaupun demikian, perlu diingat bahwa bila kita melakukan kegiatan hulu dengan baik, biasanya kita bisa menghasilkan rilis yang berkualitas baik secara konsisten.</p>
<p>Pada artikel ini, kita akan membahas pernak-pernik yang berkaitan dengan rilis. Apa itu rilis, berbagai jenis rilis, syarat dan ketentuan rilis, dan juga prosedur yang kita gunakan di ArtiVisi.</p>
<!--more-->
<h1 id="apa-itu-rilis">Apa itu rilis</h1>
<p>Rilis pada intinya adalah menyerahkan software yang sudah kita kerjakan ke pihak lain. Berdasarkan siapa yang dimaksud dengan pihak lain, kita membedakan rilis menjadi :</p>
<ul>
<li>rilis internal : menyerahkan software ke pihak internal tim pengembang, misalnya dari programmer ke tester.</li>
<li>rilis eksternal : menyerahkan ke pihak luar seperti client atau customer.</li>
</ul>
<p>Kita tidak harus menunggu sampai software selesai dikerjakan 100% untuk melakukan rilis. Berdasarkan tingkat penyelesaian pekerjaan, kita bisa membedakan rilis menjadi :</p>
<ul>
<li>
<p>development release : Ini adalah rilis yang dilakukan sebelum software selesai dikerjakan. Rilis ini dibuat untuk menunjukkan kemajuan dalam proses development, misalnya menunjukkan tambahan fitur baru ataupun sekedar menunjukkan perbedaan dengan rilis sebelumnya. Development release biasa dilakukan secara rutin dan periodik. Ada yang melakukannya secara mingguan, harian, bahkan dua kali sehari. Rilis jenis ini juga biasa disebut dengan unstable release (karena softwarenya belum stabil - sering hang atau error), milestone release, daily/nightly build release, atau alpha release.</p>
</li>
<li>
<p>testing release : Ini adalah rilis yang dilakukan setelah aplikasi siap untuk dites. Pada titik ini biasanya sudah tidak ada penambahan fitur lagi. Software yang dibuat diserahkan ke tester untuk dicari bugnya. Hasil laporan bug itu kemudian akan ditindak lanjuti sehingga menghasilkan testing release berikutnya. Kalau kita pernah mendengar istilah beta release, biasanya itu maksudnya adalah testing release. Orang sering membagi lagi testing release menjadi beta release dan release candidate. Pada prinsipnya, beta dan release candidate sama saja, bedanya ada di cakupan tester. Beta release khusus untuk dites oleh tester, sedangkan release candidate bisa juga dites oleh end-user.</p>
</li>
<li>
<p>final release : Rilis jenis ini menyatakan bahwa software sudah selesai dikerjakan, dites, dan laporan bugnya sudah ditindak lanjuti. Biasa disebut juga stable release, karena software yang dihasilkan bisa digunakan dengan lancar dan nyaman (stabil).</p>
</li>
</ul>
<h1 id="manfaat-rilis">Manfaat Rilis</h1>
<p>Manfaat rilis tentunya adalah supaya software yang sudah susah payah kita buat, bisa bermanfaat bagi penggunanya dan sukur-sukur bisa menghasilkan uang bagi pembuatnya. Karena di atas kita sudah membahas berbagai jenis rilis, tentu juga ada macam-macam manfaat dari berbagai jenis rilis tersebut.</p>
<p>Development release sebaiknya dilakukan sesering mungkin. Agar tidak merepotkan, dilakukan menggunakan perangkat otomasi seperti <a href="http://jenkins-ci.org/" title="Jenkins Continuous Integration">Jenkins</a>. Manfaat dari development release adalah untuk memastikan integrasi antara fitur berjalan dengan baik. Sering kali pada saat membuat fitur X, secara tidak sengaja mempengaruhi fitur Y sehingga tidak berjalan dengan baik. Hal inilah yang coba dicegah dengan development release.</p>
<p>Testing release manfaatnya adalah supaya software yang kita buat bisa diperiksa oleh orang lain dengan perspektif yang berbeda dan lebih fresh. Programmer yang membuat aplikasi pastinya sudah hafal perilaku aplikasi yang dibuatnya, sehingga bisa menghindari error yang mungkin terjadi. Tapi tester atau user tidak memiliki pengetahuan tentang cara kerja internal aplikasi, sehingga lebih mungkin menemukan skenario yang belum diantisipasi oleh software.</p>
<p>Final release tentunya berguna supaya software kita bisa digunakan oleh masyarakat umum. Para pengguna (end user) biasanya menunggu sampai suatu software dinyatakan selesai, baru dia mau menggunakannya. Demikian juga bila aplikasi yang kita buat akan dibundel oleh orang lain (contohnya distro Linux, integrasi dengan aplikasi lain, dsb), tentu mereka akan menunggu keluarnya rilis final.</p>
<h1 id="aturan-rilis">Aturan Rilis</h1>
<p>Seperti kita bahas sebelumnya, tujuan rilis adalah supaya software kita bisa digunakan oleh orang lain. Oleh karena itu, kita harus memudahkan pengguna dalam memahami rilis yang kita keluarkan.</p>
<p>Hal paling penting dalam melakukan rilis adalah aturan penamaan. Dengan aturan penamaan yang baik, kita bisa:</p>
<ul>
<li>menjelaskan status rilis, apakah ini development release, testing, atau final.</li>
<li>menjelaskan kompatibilitas dengan versi sebelumnya dan aplikasi lain. Ini akan dijelaskan secara lebih detail sebentar lagi.</li>
<li>menjelaskan hubungan dengan rilis lainnya. Bila kita memiliki dua rilis, kita bisa membedakan mana rilis yang duluan dan mana yang belakangan.</li>
</ul>
<h1 id="studi-kasus">Studi Kasus</h1>
<p>Untuk memudahkan pemahaman tentang penamaan rilis, mari kita karang studi kasusnya, yaitu Facebook Contact Backup.</p>
<blockquote>
<p>Aplikasi Facebook Contact Backup (FCB) adalah aplikasi yang bisa mendownload daftar contact kita di facebook, kemudian menuliskannya ke dalam text file. Contoh textfile akan dilampirkan di bawah. Untuk versi pertama, informasi kontak yang ditampilkan adalah : nama, email, no HP.</p>
</blockquote>
<p>Berikut contoh file yang dihasilkan oleh aplikasi FCB</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><contacts></span>
<span class="nt"><contact></span>
<span class="nt"><name></span>Endy Muhardin<span class="nt"></contact></span>
<span class="nt"><email></span>endy@geemail.com<span class="nt"></email></span>
<span class="nt"><mobile></span>081298000468<span class="nt"></mobile></span>
<span class="nt"></contact></span>
<span class="nt"><contact></span>
<span class="nt"><name></span>Ifnu Bima<span class="nt"></contact></span>
<span class="nt"><email></span>ifnu@geemail.com<span class="nt"></email></span>
<span class="nt"><mobile></span>+6281234567890<span class="nt"></mobile></span>
<span class="nt"></contact></span>
<span class="nt"></contacts></span>
</code></pre></div></div>
<blockquote>
<p>Karena aplikasi FCB kemudian menjadi populer, ada orang lain yang membuat aplikasi untuk mengisi phonebook di handphone bernama Handphone Contact Importer (HCI). Aplikasi HCI ini menggunakan aplikasi FCB untuk connect ke facebook dan mengambil data contact. Setelah datanya ada dalam format textfile, aplikasi HCI akan membaca text file tersebut dan kemudian mengisinya ke phonebook handphone.</p>
</blockquote>
<p>Aplikasi FCB dikembangkan dengan cepat sehingga menghasilkan beberapa rilis sebagai berikut:</p>
<h2 id="rilis-pertama">Rilis Pertama</h2>
<p>Memperbaiki format nomer handphone, sehingga semua diseragamkan menjadi format internasional. Nomer <code class="language-plaintext highlighter-rouge">081298000468</code> pada contoh di atas akan dikonversi menjadi <code class="language-plaintext highlighter-rouge">+6281298000468</code></p>
<h2 id="rilis-kedua">Rilis Kedua</h2>
<p>Fix protokol komunikasi ke Facebook, karena ada perubahan di Facebook API.</p>
<h2 id="rilis-ketiga">Rilis Ketiga</h2>
<p>Menambahkan field ulang tahun sehingga format text file menjadi sebagai berikut :</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><contacts></span>
<span class="nt"><contact></span>
<span class="nt"><name></span>Endy Muhardin<span class="nt"></contact></span>
<span class="nt"><email></span>endy@geemail.com<span class="nt"></email></span>
<span class="nt"><mobile></span>+6281298000468<span class="nt"></mobile></span>
<span class="nt"><birthdate></span>1945-08-17<span class="nt"></birthdate></span>
<span class="nt"></contact></span>
<span class="nt"><contact></span>
<span class="nt"><name></span>Ifnu Bima<span class="nt"></contact></span>
<span class="nt"><email></span>ifnu@geemail.com<span class="nt"></email></span>
<span class="nt"><mobile></span>+6281234567890<span class="nt"></mobile></span>
<span class="nt"><birthdate></span>2011-11-11<span class="nt"></birthdate></span>
<span class="nt"></contact></span>
<span class="nt"></contacts></span>
</code></pre></div></div>
<h2 id="rilis-keempat">Rilis Keempat</h2>
<p>Ganti format xml menjadi json, mengikuti trend masa kini.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Endy Muhardin</span><span class="dl">"</span><span class="p">,</span>
<span class="na">email</span><span class="p">:</span> <span class="dl">"</span><span class="s2">endy@geemail.com</span><span class="dl">"</span><span class="p">,</span>
<span class="na">mobile</span><span class="p">:</span> <span class="dl">"</span><span class="s2">+6281298000468</span><span class="dl">"</span><span class="p">,</span>
<span class="na">birthdate</span><span class="p">:</span> <span class="dl">"</span><span class="s2">1945-08-17</span><span class="dl">"</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Ifnu Bima</span><span class="dl">"</span><span class="p">,</span>
<span class="na">email</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ifnu@geemail.com</span><span class="dl">"</span><span class="p">,</span>
<span class="na">mobile</span><span class="p">:</span> <span class="dl">"</span><span class="s2">+6281234567890</span><span class="dl">"</span><span class="p">,</span>
<span class="na">birthdate</span><span class="p">:</span> <span class="dl">"</span><span class="s2">2011-11-11</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Dengan studi kasus di atas, kita harus mempertimbangkan penomoran rilis untuk aplikasi FCB dengan benar, supaya aplikasi HCI bisa tahu apa yang harus dilakukan pada saat aplikasi FCB mengeluarkan rilis baru.</p>
<h1 id="kompatibilitas">Kompatibilitas</h1>
<p>Dalam mengeluarkan rilis untuk aplikasi FCB, kita harus mempertimbangkan aplikasi HCI agar tidak rusak. Pengaruh aplikasi FCB terhadap HCI dikenal dengan istilah kompatibilitas. Kompatibilitas dibedakan berdasarkan ketergantungan secara programmatic:</p>
<ul>
<li>binary compatibility : FCB terbaru disebut binary-compatible dengan HCI bila FCB yang sudah dicompile bisa langsung ditaruh di folder dan dipakai oleh HCI.</li>
<li>source compatibility : FCB terbaru disebut source-compatible dengan HCI bila FCB yang sudah dicompile tidak bisa langsung ditaruh begitu saja. HCI harus dicompile ulang dulu dengan FCB terbaru, baru HCI bisa dijalankan.</li>
</ul>
<p>Lebih lanjut tentang binary vs source compatibility bisa dibaca <a href="http://blogs.msdn.com/b/jmstall/archive/2008/03/10/binary-vs-source-compatibility.aspx">di artikel ini</a>.</p>
<p>Dan juga bisa dibedakan berdasarkan hubungannya dengan rilis terdahulu.</p>
<ul>
<li>backward compatibility : bila HCI versi terbaru bisa menggunakan format data FCB yang terdahulu. Misalnya, aplikasi HCI terbaru bisa membaca data versi baru (json), versi sebelumnya (xml), dan sebelumnya lagi (xml tanpa field birthdate)</li>
<li>forward compatibility : bila HCI versi jadul bisa membaca format data FCB yang lama (xml tanpa birthdate) dan yang lebih baru yang dirilis setelah HCI jadul tersebut (xml dengan birhtdate).</li>
</ul>
<p>Setelah kita memahami urusan kompatibilitas, kita bisa menentukan skema penamaan rilis, atau dikenal dengan istilah version numbering.</p>
<h1 id="version-numbering">Version Numbering</h1>
<p>Kita akan menggunakan <a href="http://apr.apache.org/versioning.html" title="Version Numbering APR">aturan dari Apache Portable Runtime</a> yang sudah diakui sebagai best-practices dalam version numbering. Aturan APR mengharuskan ada tiga komponen version number, yaitu :</p>
<ul>
<li>major number</li>
<li>minor number</li>
<li>patch number</li>
</ul>
<p>Contohnya, waktu pertama kita merilis FCB, kita beri nama <code class="language-plaintext highlighter-rouge">FCB-1.0.0</code>. Major numbernya 1, minor numbernya 0, patch numbernya 0.</p>
<p><strong>Update 5 Agustus 2016</strong> : sekarang kita gunakan <a href="http://semver.org/">Semantic Versioning</a>. Konsepnya kira-kira sama, tapi penjelasan di website Semantic Versioning lebih detail dan komprehensif daripada APR.</p>
<p>Untuk rilis selanjutnya, kita menaikkan major/minor/patch number sesuai dengan pengaruhnya terhadap kompatibilitas. Aturannya sebagai berikut:</p>
<ul>
<li>major number dinaikkan bila FCB baru tidak kompatibel dengan FCB rilis sebelumnya. Bila HCI dipasang dengan FCB terbaru ini, HCI akan error.</li>
<li>minor number dinaikkan bila FCB baru mengandung penambahan fitur, tapi tetap kompatibel dengan HCI yang dibuat berdasarkan FCB lama. HCI versi lama tetap bisa jalan, walaupun tidak bisa memanfaatkan fitur yang baru.</li>
<li>patch dinaikkan bila tidak ada perubahan secara fitur, tapi cuma ada perbaikan di internal FCB yang tidak terlihat dari luar (misalnya optimasi koneksi jaringan, perubahan protokol ke arah Facebook, dsb)</li>
</ul>
<p>Selanjutnya, mari kita beri nomer sesuai studi kasus kita di atas.</p>
<ol>
<li>Rilis Pertama : diberi nama <code class="language-plaintext highlighter-rouge">1.1.0</code>, karena cuma terjadi perubahan di content saja. HCI yang dibuat dengan FCB versi <code class="language-plaintext highlighter-rouge">1.0.0</code> akan tetap berjalan lancar</li>
<li>Rilis Kedua : diberi nama <code class="language-plaintext highlighter-rouge">1.1.1</code>, karena perubahan protokol komunikasi dengan Facebook tidak mempengaruhi HCI sama sekali. HCI versi lama tetap bisa jalan dengan lancar.</li>
<li>Rilis Ketiga : diberi nama <code class="language-plaintext highlighter-rouge">1.2.0</code>, sama dengan rilis pertama. Ada penambahan fitur, tapi tidak membuat HCI jadi error. Dengan rilis ini, programmer HCI bisa menambahkan fitur baru misalnya reminder ulang tahun. Tapi HCI versi lama (tanpa reminder ulang tahun) tetap bisa digunakan dengan <code class="language-plaintext highlighter-rouge">FCB-1.2.0</code></li>
<li>Rilis Keempat : diberi nama <code class="language-plaintext highlighter-rouge">2.0.0</code>. Ini adalah perubahan signifikan. Untuk dapat menggunakan <code class="language-plaintext highlighter-rouge">FCB-2.0.0</code> ini, HCI harus mengalami perubahan signifikan. HCI versi lama tidak bisa digunakan dengan <code class="language-plaintext highlighter-rouge">FCB-2.0.0</code>. Agar dapat digunakan, programmer HCI harus mengeluarkan rilis baru yang bisa mengakomodasi format data JSON.</li>
</ol>
<p>Berikutnya, mari kita lihat pengaruhnya untuk HCI.</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">HCI-1.0.0</code> : versi pertama, dibuat dengan <code class="language-plaintext highlighter-rouge">FCB-1.0.0</code></li>
<li>Rilis <code class="language-plaintext highlighter-rouge">FCB-1.1.0</code> : tidak ada tambahan fitur yang bisa dibuat di HCI, sehingga programmernya tidak coding.</li>
<li>Rilis <code class="language-plaintext highlighter-rouge">FCB-1.1.1</code> : tidak ada perubahan yang terlihat. Programmer HCI makan gaji buta sambil update status Facebook. Nganggurnya programmer HCI ini berarti <code class="language-plaintext highlighter-rouge">HCI-1.0.0</code> forward-compatible dengan <code class="language-plaintext highlighter-rouge">FCB-1.0.0</code>, <code class="language-plaintext highlighter-rouge">FCB-1.1.0</code>, hingga <code class="language-plaintext highlighter-rouge">FCB-1.1.1</code>.</li>
<li>Rilis <code class="language-plaintext highlighter-rouge">FCB-1.2.0</code> : ada field baru (birthdate) yang bisa dimanfaatkan, programmer HCI mulai coding.</li>
<li><code class="language-plaintext highlighter-rouge">HCI-1.1.0</code> : tambahan fitur reminder ulang tahun. <code class="language-plaintext highlighter-rouge">HCI-1.1.0</code> ini backward-compatible dengan <code class="language-plaintext highlighter-rouge">FCB-1.1.1</code>, <code class="language-plaintext highlighter-rouge">FCB-1.1.0</code>, maupun <code class="language-plaintext highlighter-rouge">FCB-1.0.0</code>.</li>
<li>Rilis <code class="language-plaintext highlighter-rouge">FCB-2.0.0</code> : HCI semua versi tidak dapat digunakan bila user meng-upgrade FCB-nya. Programmer HCI harus segera mengeluarkan rilis baru, tidak boleh coding sambil facebookan.</li>
<li><code class="language-plaintext highlighter-rouge">HCI-1.1.1</code> : bagi end-user, HCI terbaru ini tidak ada tambahan fiturnya. Tapi dia fixing bug, yang tadinya error pada waktu dijalankan (karena FCB-nya tidak kompatibel), menjadi tidak error.</li>
</ol>
<p>Selain major.minor.patch, ada kalanya orang juga menambahkan satu informasi lagi yang menyatakan kestabilan rilis. Berikut beberapa contohnya:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">FCB-1.0.1-SNAPSHOT</code> : ini biasanya digunakan untuk mencerminkan rilis daily build terbaru</li>
<li><code class="language-plaintext highlighter-rouge">FCB-1.0.0-20121212080808</code> : ini biasanya digunakan untuk menunjukkan hasil daily build tertentu</li>
<li><code class="language-plaintext highlighter-rouge">FCB-1.0.1-RC-01</code> : release candidate pertama</li>
<li><code class="language-plaintext highlighter-rouge">FCB-1.0.0-M2</code> : milestone kedua</li>
<li><code class="language-plaintext highlighter-rouge">FCB-1.0.0-Final</code> : final release. Ada orang yang menambahkan keyword Final, ada juga yang tidak.</li>
<li><code class="language-plaintext highlighter-rouge">FCB-1.0.0-GA</code> : generally available, sama dengan final release</li>
<li><code class="language-plaintext highlighter-rouge">FCB-1.0.0-RELEASE</code> : sama dengan GA dan Final</li>
</ul>
<p>Penomoran versi ini terlihat sepele saja. Tapi kalau kita tidak punya aturan penamaan yang jelas, maka orang lain akan bingung setiap kali ada rilis baru. Mereka tidak bisa menentukan apakah harus upgrade atau tidak, karena mereka tidak bisa tahu bagaimana kompatibilitas rilis ini dengan aplikasi lainnya. Salah satu contoh populer kekacauan yang disebabkan penomoran versi yang sembarangan <a href="http://news.ycombinator.com/item?id=1734936">bisa dilihat di komunitas Ruby</a>. Akibat aturan rilis Ruby tidak jelas, sehingga dibutuhkan aplikasi lain seperti RVM atau rbenv supaya antar versi Ruby tidak saling bentrok. Effort yang dikeluarkan untuk membuat dan memantain RVM dan rbenv tentu tidak sedikit. Ini semua disebabkan <em>hanya karena</em> penomoran versi belaka. Hasil akhir dari semua ini, sampai saat artikel ini ditulis, <a href="http://ryanbigg.com/2010/12/ubuntu-ruby-rvm-rails-and-you/">Ruby belum bisa dipaket dengan benar di distro Debian</a> dan turunannya (termasuk Ubuntu).</p>
<h1 id="release-notes">Release Notes</h1>
<p>Tentunya penomoran versi saja tidak bisa memuat informasi yang detail. Kita membutuhkan sarana lain untuk memberikan informasi yang detail tentang isi dari suatu rilis. Untuk keperluan ini, biasanya orang membuat dokumen yang disebut Release Notes. Beberapa hal yang biasanya dicantumkan dalam release notes antara lain:</p>
<ul>
<li>fitur baru</li>
<li>bug yang diperbaiki</li>
<li>enhancement/improvement, yaitu perbaikan pada fitur yang ada</li>
<li>known issues, yaitu bug atau error yang sudah teridentifikasi tapi belum difix. Informasi ini menunjukkan bahwa programmernya sudah tahu bahwa ada bug, tapi karena satu dan lain hal belum memperbaikinya</li>
<li>kontributor, yaitu siapa saja yang berkontribusi di rilis ini.</li>
<li>to do, yaitu fitur apa yang direncanakan akan dibuat pada rilis berikut. Informasi ini bisa juga ditulis di dokumen terpisah bernama roadmap</li>
</ul>
<p>Contoh release notes bisa dilihat <a href="/images/uploads/2012/08/RELEASE.txt" title="Contoh Release Notes">di sini</a>.</p>
<h1 id="tools">Tools</h1>
<p>Ada beberapa hal yang perlu dilakukan pada waktu kita akan melakukan rilis, yaitu:</p>
<ul>
<li>menaikkan nomor versi di source code. Biasanya kita ada mencantumkan nomor versi di aplikasi, misalnya di halaman About.</li>
<li>membuat rekap perubahan yang terjadi sejak rilis sebelumnya</li>
<li>membuat tag di version control database</li>
</ul>
<p>Semua kegiatan di atas dapat dioptimasi dengan tools misalnya <a href="http://maven.apache.org/plugins/maven-release-plugin/" title="Maven Release Plugin">maven-release-plugin</a>. Cara pakainya bisa dilihat <a href="http://java.dzone.com/articles/automating-releases-maven-0">di sini</a>. Tapi beberapa orang melihat bahwa maven-release-plugin ini tidak memenuhi kebutuhannya, sehingga dia pakai <a href="http://www.axelfontaine.com/2011/01/maven-releases-on-steroids-adios.html">cara yang lain</a>.</p>
<p>Di ArtiVisi, kita sudah mencoba maven-release-plugin, dan berpendapat bahwa dia <a href="http://www.sonatype.com/people/2011/01/using-the-maven-release-plugin-things-to-know/">terlalu kaku</a> sehingga sulit dikonfigurasi agar sesuai dengan kebutuhan kita. Akhirnya kita menggunakan cara manual dengan prosedur sebagai berikut:</p>
<h2 id="prosedur-rilis-artivisi">Prosedur Rilis ArtiVisi</h2>
<p>Contoh skenario :</p>
<ul>
<li>Rilis sebelumnya : <code class="language-plaintext highlighter-rouge">1.2.0</code></li>
<li>Rilis sekarang : <code class="language-plaintext highlighter-rouge">1.2.1</code></li>
</ul>
<p>Langkah-langkah melakukan rilis :</p>
<ol>
<li>
<p>Generate Changelog dengan perintah <code class="language-plaintext highlighter-rouge">git shortlog 1.2.0..HEAD</code></p>
</li>
<li>
<p>Copy paste output dari langkah 1 ke dalam release notes.</p>
</li>
<li>
<p>Naikkan version number di dalam source code menggunakan Eclipse. Search file <code class="language-plaintext highlighter-rouge">pom.xml</code>, find <code class="language-plaintext highlighter-rouge">1.2.0</code> dan replace menjadi <code class="language-plaintext highlighter-rouge">1.2.1</code>.</p>
</li>
<li>
<p>Save semua file, kemudian commit ke Git dengan perintah <code class="language-plaintext highlighter-rouge">git commit -m "release 1.2.1"</code></p>
</li>
<li>
<p>Buat tag di Git dengan perintah <code class="language-plaintext highlighter-rouge">git tag -a -F RELEASE.txt 1.2.1</code></p>
</li>
</ol>
<h1 id="penutup">Penutup</h1>
<p>Demikianlah penjelasan tentang serba-serbi release management dalam software development. Mudah-mudahan bisa membuat project dan produk yang kita hasilkan lebih mudah dikelola.</p>
Terima kasih Wordpress2012-08-09T13:54:00+07:00https://software.endy.muhardin.com/aplikasi/terima-kasih-wordpress<p>Sejak pertama saya membuat blog, aplikasi yang saya gunakan adalah <a href="http://www.wordpress.org">Wordpress</a>. Aplikasi Wordpress sangat mudah digunakan dan banyak fiturnya. Selain itu, themes gratisan di internet juga berlimpah ruah, sehingga kita bisa ganti tampilan setiap hari kalau mau.</p>
<p>Walaupun demikian, zaman berganti, dan cara kita bekerja juga berubah. Saya saat ini lebih banyak berkutat di pemrograman dengan workflow sebagai berikut :</p>
<ol>
<li>Buka text editor</li>
<li>Edit source code</li>
<li>Jalankan di local dan test</li>
<li>Kalau sudah ok, simpan di version control (commit)</li>
<li>Ulangi ke langkah 2 sampai selesai</li>
<li>Begitu sudah siap untuk sharing hasil pekerjaan, upload (push) perubahan ke version control pusat.</li>
</ol>
<p>Workflow ini sayangnya tidak dapat dilakukan untuk menulis blog. Workflow saya dalam menulis blog biasanya seperti ini:</p>
<ol>
<li>Buka text editor</li>
<li>Ketik dalam format text file biasa, dengan menggunakan HTML tag bila perlu</li>
<li>Simpan di folder Dropbox selama masih draft</li>
<li>Setelah siap dipublish, buka Wordpress di browser</li>
<li>Login ke Wordpress</li>
<li>Create New Post</li>
<li>Copy - Paste dari text editor</li>
<li>Preview dan rapikan lagi</li>
<li>Publish</li>
</ol>
<p>Seperti bisa kita lihat, dengan workflow di atas, praktis fitur-fitur Wordpress yang serba canggih tidak termanfaatkan. Oleh karena itu, sudah tiba saatnya untuk mencari aplikasi blogging yang <strong>lebih sedikit</strong> fiturnya. Biasanya orang mengganti aplikasi dengan yang lebih banyak fiturnya, tapi kali ini saya melakukan hal yang sebaliknya.</p>
<!--more-->
<p>Setelah mencari berbagai alternatif, pilihan jatuh ke <a href="https://github.com/mojombo/jekyll/">Jekyll</a>. Jekyll adalah static content generator. Kita menulis artikel dalam format <a href="http://daringfireball.net/projects/markdown/">Markdown</a>, yaitu text file biasa dengan sedikit markup yang enak dilihat mata. Kalau kita pernah mengedit wiki, format Markdown tidak asing lagi bagi kita.</p>
<p>Cara kerja Jekyll berbeda dengan aplikasi blog atau CMS pada umumnya. Kalau kita menggunakan Wordpress, Joomla, Drupal, dsb, artikel kita akan disimpan di database. Berdasarkan URL atau link yang diklik pengunjung, aplikasi CMS akan mencarikan artikel di database, memproses formattingnya, dan menampilkan ke browser. Aplikasi CMS biasanya kita instal di server kita di internet, sehingga server kita tersebut harus mendukung bahasa pemrograman yang digunakan CMS dan juga harus memiliki database server. Sebagai contoh, untuk menjalankan Wordpress, server kita harus bisa PHP dan memiliki MySQL.</p>
<p>Berbeda dengan Wordpress, Joomla, Drupal, dan CMS pada umumnya, blog yang dibuat dengan Jekyll tidak dynamic. Semua variabel dan logika looping diproses di komputer kita sendiri. Hasil pemrosesan ini menghasilkan file HTML yang sudah jadi. File HTML inilah yang kita upload ke server. Dengan demikian, di dalam server semua file bersifat static.</p>
<p>Ada beberapa konsekuensi dari static website seperti ini.
Sisi positifnya :</p>
<ul>
<li>kebutuhan server menjadi lebih sederhana, tidak perlu lagi PHP dan MySQL</li>
<li>halaman artikel bisa ditampilkan dengan lebih cepat karena tidak perlu query dan pemrosesan</li>
<li>karena tidak ada query dan proses, mau berapapun request per detik tidak terlalu membebani CPU/RAM/Disk</li>
<li>anti dihack, kecuali sistem operasinya yang ditembus. Kalau kita pakai Wordpress dkk, kita harus rajin upgrade untuk menambal bug security yang baru saja difix.</li>
</ul>
<p>Sisi negatifnya :</p>
<ul>
<li>tidak bisa menampung komentar. Ini saya atasi dengan Facebook Comment.</li>
</ul>
<p>Nah, karena positifnya lebih banyak dari negatifnya, maka saya putuskan untuk migrasi.</p>
<p>Saya tidak menggunakan Jekyll yang aslinya, melainkan framework yang dibuat di atas Jekyll. Ada beberapa pilihan, misalnya <a href="http://jekyllbootstrap.com/">Jekyll Bootstrap (JB)</a>, <a href="http://ruhoh.com/">Ruhoh</a>, dan <a href="http://octopress.org">Octopress</a>. Ruhoh rupanya dibuat oleh orang yang sama dengan pembuat JB karena dia sudah bosan dengan JB, dan nampaknya dia juga sedang sibuk sehingga Ruhoh juga tidak dimaintain. Jadilah pilihan jatuh ke Octopress.</p>
<p>Octopress memiliki berbagai fitur, diantaranya:</p>
<ul>
<li>Source code formatting. Ini adalah fitur yang sulit digunakan di Wordpress. Harus instal berbagai plugin dan sering berantakan tampilannya. Ini menyebabkan saya harus menggunakan <a href="http://gist.github.com">Gist</a>.</li>
<li>Deploy script ke Github, Heroku, dan rsync. Dengan fitur ini, saya bisa membuat <a href="http://endymuhardin.github.com">mirror blog saya di Github</a>.</li>
<li>Berbagai plugin yang sudah disertakan secara built in, misalnya tag khusus untuk image dan video.</li>
</ul>
<p>Proses migrasi berjalan sebagai berikut :</p>
<ol>
<li>Setup Octopress</li>
<li>Customize theme</li>
<li>Unduh artikel lama di Wordpress menggunakan <a href="https://github.com/thomasf/exitwp/">exitwp.py</a></li>
<li>Bersihkan beberapa tag yang tidak kompatibel, diantaranya tabel, gist, dan image</li>
<li>Pindahkan artikel ke Octopress</li>
<li>Generate dan deploy</li>
</ol>
<h1 id="setup-octopress">Setup Octopress</h1>
<p>Octopress disiapkan dengan beberapa langkah berikut :</p>
<ol>
<li>Install Ruby dengan RVM</li>
<li>Install Jekyll</li>
<li>Clone repository Octopress</li>
<li>Konfigurasi Octopress</li>
</ol>
<p>Ada beberapa konfigurasi yang saya lakukan, yaitu :</p>
<h2 id="informasi-umum">Informasi Umum</h2>
<p>``` ruby _config.yml
url: http://endy.artivisi.com/blog
title: Living life and make it better
subtitle: life, learn, contribute
author: Endy Muhardin</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
## Permalink ##
Supaya sama dengan URL blog lama, format URL di Octopress juga harus disesuaikan sbb :
``` ruby _config.yml
permalink: /:categories/:title/
</code></pre></div></div>
<h2 id="pagination">Pagination</h2>
<p>Di halaman pertama cukup tampilkan 3 entri terbaru. Di sidebar, tampilkan 10 link artikel terakhir.</p>
<p>``` ruby _config.yml
paginate: 3
recent_posts: 10</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
# Customize Theme #
Supaya tidak keliatan seragam, saya mencari theme untuk Octopress. Sejauh ini baru menemukan [Darkstripes](https://github.com/amelandri/darkstripes), jadi langsung saja dipasang.
# Mengolah artikel lama #
Sebetulnya ada beberapa cara untuk memigrasi artikel lama. Saya menggunakan exitwp yang nampaknya lebih mudah. Berikut langkah-langkahnya :
1. Export dulu artikel lama dari Wordpress ke format XML.
2. Jalankan exitwp.py untuk mengkonversi ke format markdown.
## Memproses tag image ##
Selanjutnya, saya harus membersihkan tag image dan caption bawaan Wordpress, karena tidak bisa tampil dengan baik. Saya menggunakan perintah sed di linux agar bisa memproses banyak file sekaligus.
Berikut perintah untuk memperbaiki tag image yang tadinya seperti ini <code>!\[Synergy Screenshot](/images/uploads/2006/05/synergy.gif)</code> menjadi seperti ini <code>![Synergy Screenshot ](/uploads/2006/05/synergy.gif) </code>, saya gunakan perintah berikut :
``` sh
find . -name "*.markdown" -print | xargs sed -i "s|\[!\[\(.*\)\](\(.*\))\](\(.*\))|{% img \2 \1 %}|g"
</code></pre></div></div>
<p>Tag caption juga harus dihilangkan, berikut perintahnya:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find <span class="nb">.</span> <span class="nt">-name</span> <span class="s2">"*.markdown"</span> <span class="nt">-print</span> | xargs <span class="nb">sed</span> <span class="nt">-i</span> <span class="s2">"s|</span><span class="se">\[</span><span class="s2">caption.*</span><span class="se">\]\(</span><span class="s2">.*</span><span class="se">\)\[</span><span class="s2">/caption</span><span class="se">\]</span><span class="s2">|</span><span class="se">\1</span><span class="s2">|g"</span>
</code></pre></div></div>
<p>Secara default, exitwp akan menghasilkan tag image dengan URL lengkap, misalnya <code>http://endy.artivisi.com/blog/wp-content|/images/uploads/2006/05/synergy.gif</code> dan masih mengarah ke Wordpress. Saya ingin mengubah ini menjadi URL relatif, yaitu <code>/images/uploads/2006/05/synergy.gif</code></p>
<p>Berikut perintahnya :</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find <span class="nb">.</span> <span class="nt">-name</span> <span class="s2">"*.markdown"</span> <span class="nt">-print</span> | xargs <span class="nb">sed</span> <span class="nt">-i</span> <span class="s2">"s|http://endy.artivisi.com/blog/wp-content|/images|g"</span>
</code></pre></div></div>
<h2 id="memproses-tag-gist">Memproses tag gist</h2>
<p>Seperti saya tuliskan di sini, saya menggunakan plugin Wordpress untuk <a href="http://endy.artivisi.com/blog/Aplikasi/menggunakan-gist/">menampilkan Gist</a>. Octopress sudah memiliki dukungan sendiri terhadap Gist, sehingga harus ada proses konversi. Berikut perintahnya :</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find <span class="nb">.</span> <span class="nt">-name</span> <span class="s2">"*.markdown"</span> <span class="nt">-print</span> | xargs <span class="nb">sed</span> <span class="nt">-i</span> <span class="s2">"s|</span><span class="se">\[</span><span class="s2">gist id=</span><span class="se">\(</span><span class="s2">.*</span><span class="se">\)</span><span class="s2"> file=</span><span class="se">\(</span><span class="s2">.*</span><span class="se">\)\]</span><span class="s2">|{% gist </span><span class="se">\1</span><span class="s2"> </span><span class="se">\2</span><span class="s2"> %}|g"</span>
find <span class="nb">.</span> <span class="nt">-name</span> <span class="s2">"*.markdown"</span> <span class="nt">-print</span> | xargs <span class="nb">sed</span> <span class="nt">-i</span> <span class="s2">"s| bump=.||g"</span>
</code></pre></div></div>
<h1 id="facebook-comment">Facebook Comment</h1>
<p>Untuk menggantikan fitur komentar, ada beberapa alternatif yang bisa digunakan, diantaranya :</p>
<ul>
<li>Disqus</li>
<li>Facebook</li>
<li>Tanpa comment</li>
</ul>
<p>Melihat audiens blog yang rata-rata memiliki akun Facebook, maka baiklah kita pilih Facebook saja. Cara mengaktifkannya bisa dibaca <a href="http://blog.grambo.me.uk/blog/2012/02/20/adding-facebook-comments-to-octopress/">di tutorial ini</a>. Ada sedikit bug disana, yaitu pada file <code>post.html</code> dan <code>page.html</code>. Harusnya seperti ini :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="sx">% if </span><span class="n">site</span><span class="p">.</span><span class="nf">facebook_appid</span> <span class="n">and</span> <span class="n">page</span><span class="p">.</span><span class="nf">comments</span> <span class="o">==</span> <span class="kp">true</span> <span class="sx">%}
<section>
<h1>Comments</h1>
<div id="facebook_comments" aria-live="polite">
{% include post/facebook_comments.html %}</span>
<span class="o"><</span><span class="sr">/div>
</se</span><span class="n">ction</span><span class="o">></span>
<span class="p">{</span><span class="sx">% endif </span><span class="o">%</span><span class="p">}</span>
</code></pre></div></div>
<p>Daftar komentar orang bisa kita lihat dan moderasi di <a href="https://developers.facebook.com/tools/comments">https://developers.facebook.com/tools/comments</a>.</p>
<h1 id="penutup">Penutup</h1>
<p>Sebetulnya sedih juga berpisah dengan Wordpress yang telah setia menemani hampir 7 tahun lamanya, tapi life must go on. Wordpress, terima kasih atas kebersamaan selama 7 tahun ini. Mudah-mudahan engkau akan berevolusi lebih baik lagi sehingga di lain waktu kita bisa bersama lagi.</p>
Konsep Dasar Log4j2012-07-30T20:59:00+07:00https://software.endy.muhardin.com/java/konsep-dasar-log4<p>Walaupun sudah dibuatkan <a href="http://endy.artivisi.com/blog/java/menggunakan-log4j">minibook</a>, tapi ternyata ada juga beberapa orang yang tidak paham bagaimana cara enable/disable log message di aplikasi Java. Oleh karena itu, baiklah saya jelaskan lagi secara lebih singkat.</p>
<!--more-->
<p>Ada beberapa komponen penting dalam aplikasi logging :</p>
<ul>
<li>
<p>logger : ini adalah yang kita gunakan di aplikasi untuk mengeluarkan pesan.
tadinya System.out.println(“Coba”);
diganti menjadi logger.info(“Coba”);</p>
</li>
<li>
<p>appender : komponen yang bertugas menampilkan log message
misalnya : console appender : menampilkan ke System.out
File appender : menulis log ke file
Rolling file appender : menulis ke file, lalu dirolling berdasarkan kriteria tertentu (size atau time)
misalnya, setelah mencapai 1 MB, tulis ke file berbeda, atau tiap 1 jam ganti file</p>
</li>
<li>
<p>category : ini adalah sumber log message, yaitu package atau realm.
<strong>biasanya</strong> category == package
Ini digunakan untuk memfilter log mana yang akan ditampilkan ke appender mana</p>
</li>
</ul>
<p>Selain 3 komponen itu, ada terminologi yang namanya level.
Contoh level : error, warn, info, debug, trace
Level ini berlaku bertingkat, jadi kalau kita bilang info, artinya info, warn, error.
Kalau kita bilang debug, maka hasilnya debug, info, warn, error.</p>
<p>Log message ditampilkan atau tidak, tergantung category dan level.</p>
<p>Contoh kasus :
Saya membuat aplikasi, berisi package com.artivisi.belajar.logging.
Isinya ada 2 class, Coba dan Halo.</p>
<p>Aplikasi saya ini menggunakan framework Spring, yang mana berisi package org.springframework, yang berisi banyak sub package, seperti org.springframework.core, org.springframework.jdbc, dsb.</p>
<p>Untuk source code yang saya tulis sendiri (Coba dan Halo), saya ingin menampilkan level debug, karena masih fase development. Nantinya kalau sudah production, cukup level warn saja yang ditampilkan.
Sedangkan untuk library Spring Framework, cukup level error saja yang ditampilkan.</p>
<p>Semua log message ditampilkan ke terminal, supaya mudah diamati.</p>
<p>Contoh kasus di atas, bila kita menggunakan log4j akan dikonfigurasi sebagai berikut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># by default, levelnya adalah INFO, tampilkan ke System.out
log4j.rootLogger=INFO,Konsole
# untuk package com.artivisi, tampilkan level DEBUG ke System.out
log4j.logger.com.artivisi=DEBUG,Konsole
# untuk spring, error saja yang ditampilkan
log4j.logger.org.springframework=ERROR,Konsole
# Appender Konsole adalah System.out
log4j.appender.Konsole=org.apache.log4j.ConsoleAppender
log4j.appender.Konsole.layout=org.apache.log4j.PatternLayout
# Format tanggal menurut ISO8601 : %d
log4j.appender.Konsole.layout.ConversionPattern=%d [%t] %5p %c %m%n
</code></pre></div></div>
<p>Konfigurasi di atas harus dibuat dengan nama log4j.properties, dan diletakkan di dalam classpath. Kalau namanya tidak sama atau lokasinya salah, maka tidak akan dibaca oleh Log4J.</p>
Monitoring Aplikasi Java2012-07-28T20:45:07+07:00https://software.endy.muhardin.com/java/monitoring-aplikasi-java<p>Setelah aplikasi kita selesai dibuat, langkah selanjutnya tentu saja adalah menjalankannya di server production. Dalam software development, ini disebut dengan transisi dari development menjadi operation.</p>
<p>Salah satu aspek penting dalam fase operation adalah monitoring performance. Monitoring dilakukan untuk berbagai tujuan, diantaranya :</p>
<ul>
<li>
<p>Mengetahui karakteristik aplikasi dalam menggunakan resource. Informasi ini bisa kita gunakan untuk melakukan tuning performance</p>
</li>
<li>
<p>Mengetahui karakteristik user dalam menggunakan aplikasi. Perilaku user ini berguna untuk menentukan waktu-waktu sibuk dan waktu-waktu idle, sehingga kita bisa melakukan capacity planning dengan akurat.</p>
</li>
<li>
<p>Mengetahui berapa persen resource yang sudah terpakai, sehingga kita punya gambaran kapan harus melakukan upgrade, dan apa yang harus diupgrade</p>
</li>
<li>
<p>Mendapatkan notifikasi secepat mungkin pada saat sistem mengalami gangguan</p>
</li>
</ul>
<!--more-->
<p>Pada umumnya, monitoring dilakukan dengan memantau bagian-bagian dalam sistem, diantaranya :</p>
<ul>
<li>
<p>Dalam sistem operasi</p>
<ul>
<li>
<p>Jumlah proses</p>
</li>
<li>
<p>CPU</p>
</li>
<li>
<p>Memori</p>
</li>
<li>
<p>Disk I/O</p>
</li>
<li>
<p>Network I/O</p>
</li>
</ul>
</li>
<li>
<p>Dalam aplikasi</p>
<ul>
<li>
<p>Jumlah thread</p>
</li>
<li>
<p>Alokasi memori</p>
</li>
<li>
<p>Class/method yang mengkonsumsi resource terbesar</p>
</li>
<li>
<p>Class/method yang memakan waktu lama</p>
</li>
<li>
<p>Untuk bahasa pemrograman yang berjalan di virtual machine (seperti Java, .NET, Ruby, dsb), kita juga perlu memantau perilaku Garbage Collector</p>
</li>
</ul>
</li>
<li>
<p>Database Server</p>
<ul>
<li>
<p>Jumlah koneksi yang dibuka baik yang idle maupun yang aktif</p>
</li>
<li>
<p>Query yang memakan waktu lama</p>
</li>
<li>
<p>Tabel yang sering diakses</p>
</li>
</ul>
</li>
</ul>
<p>Dalam melakukan monitoring, ada banyak tools yang digunakan. Beberapa aplikasi opensource yang populer diantaranya :</p>
<ul>
<li>
<p>Nagios</p>
</li>
<li>
<p>Icinga : ini adalah fork dari Nagios</p>
</li>
<li>
<p>Zenoss</p>
</li>
<li>
<p>Zabbix</p>
</li>
<li>
<p>OpenNMS</p>
</li>
<li>
<p>Hyperic</p>
</li>
</ul>
<p>Pada umumnya, semua aplikasi di atas memiliki fitur yang mirip-mirip, yaitu :</p>
<ul>
<li>
<p>SNMP Monitoring. SNMP adalah protokol yang populer, tersedia di sistem operasi, network hardware (switch, router, dsb), bahkan aplikasi (Java VM, database server, dsb)</p>
</li>
<li>
<p>JMX Monitoring. JMX adalah protokol untuk memonitor aplikasi Java. Dengan menggunakan JMX, informasi yang diperoleh akan lebih rinci.</p>
</li>
<li>
<p>Agent dan Agentless Monitoring. Agent adalah aplikasi kecil yang diinstal di server yang ingin dimonitor. Dengan agent, informasi yang dikumpulkan bisa lebih detail. Di lain sisi, tidak semua tempat bisa dipasangi agent (misalnya router). Umumnya aplikasi monitoring mendukung monitoring dengan agent maupun tanpa agent.</p>
</li>
</ul>
<p>Cara instalasi dari aplikasi di atas bisa dibaca di websitenya masing-masing. Yang akan kita bahas di sini adalah cara konfigurasi aplikasi Java, khususnya yang menggunakan framework Spring dan Hibernate, agar bisa dimonitor oleh aplikasi-aplikasi di atas.</p>
<p>Pada prinsipnya, berikut adalah hal-hal yang perlu dilakukan :</p>
<ol>
<li>
<p>Menentukan metric atau jenis data yang akan dimonitor</p>
</li>
<li>
<p>Mengaktifkan MBean agar metric tersebut dipublish melalui JMX</p>
</li>
<li>
<p>Mengaktifkan JMX server agar bisa dihubungi dari aplikasi monitoring</p>
</li>
</ol>
<p>Aplikasi yang akan kita jadikan contoh kasus adalah template standar aplikasi web ArtiVisi, yang <a href="https://github.com/endymuhardin/belajar-restful">source codenya tersedia di Github</a>.</p>
<h2 id="menentukan-metric">Menentukan metric</h2>
<p>Beda jenis datanya, tentu beda juga cara pengumpulan data dan tools yang digunakan untuk mengolahnya.</p>
<h3 id="tabel-metrik-dan-tools">Tabel Metrik dan Tools</h3>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Metode Pengumpulan</th>
<th>Tools</th>
</tr>
</thead>
<tbody>
<tr>
<td>CPU</td>
<td>SNMP atau Agent</td>
<td>Nagios,Zenoss,dkk</td>
</tr>
<tr>
<td>Memori</td>
<td>SNMP atau Agent</td>
<td>Nagios,Zenoss,dkk</td>
</tr>
<tr>
<td>Disk IO</td>
<td>SNMP atau Agent</td>
<td>Nagios,Zenoss,dkk</td>
</tr>
<tr>
<td>Network</td>
<td>SNMP atau Agent</td>
<td>Nagios,Zenoss,dkk</td>
</tr>
<tr>
<td>Tomcat</td>
<td>JMX</td>
<td>Hyperic</td>
</tr>
<tr>
<td>MySQL</td>
<td>Agent</td>
<td>Hyperic</td>
</tr>
<tr>
<td>Spring</td>
<td>AOP</td>
<td>Javamelody</td>
</tr>
<tr>
<td>Hibernate</td>
<td>JMX</td>
<td>Hyperic</td>
</tr>
</tbody>
</table>
<h2 id="monitoring-dengan-javamelody">Monitoring dengan JavaMelody</h2>
<p>Untuk mengaktifkan monitoring menggunakan JavaMelody, ada beberapa langkah yang perlu kita lakukan, yaitu :</p>
<ol>
<li>Menambahkan jar JavaMelody</li>
<li>Memasang AOP interceptor supaya bisa memonitor beans dalam Spring</li>
<li>Mengaktifkan monitoring JavaMelody</li>
</ol>
<p>Karena projectnya menggunakan Maven, maka menambahkan jar sangat mudah, cukup dengan menambahkan dependency sebagai berikut :</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>net.bull.javamelody<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>javamelody-core<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.39.0<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Selanjutnya, kita memasang interceptor supaya object yang kita buat dimonitor oleh JavaMelody. Biasanya kita memonitor implementasi proses bisnis. Berikut konfigurasi applicationContext.xml</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"facadeMonitoringAdvisor"</span>
<span class="na">class=</span><span class="s">"net.bull.javamelody.MonitoringSpringAdvisor"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"pointcut"</span><span class="nt">></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"net.bull.javamelody.MonitoredWithInterfacePointcut"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"interfaceName"</span>
<span class="na">value=</span><span class="s">"com.artivisi.belajar.restful.service.MonitoredService"</span>
<span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Terakhir, kita aktifkan JavaMelody. Karena aplikasinya adalah aplikasi web, maka inisialisasi dilakukan di dalam file web.xml sebagai berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><context-param></span>
<span class="nt"><param-name></span>contextConfigLocation<span class="nt"></param-name></span>
<span class="nt"><param-value></span>
classpath:net/bull/javamelody/monitoring-spring.xml
classpath*:com/artivisi/**/applicationContext.xml
<span class="nt"></param-value></span>
<span class="nt"></context-param></span>
<span class="nt"><listener></span>
<span class="nt"><listener-class></span>net.bull.javamelody.SessionListener<span class="nt"></listener-class></span>
<span class="nt"></listener></span>
<span class="c"><!-- Monitor aplikasi menggunakan javamelody --></span>
<span class="nt"><filter></span>
<span class="nt"><filter-name></span>monitoring<span class="nt"></filter-name></span>
<span class="nt"><filter-class></span>net.bull.javamelody.MonitoringFilter<span class="nt"></filter-class></span>
<span class="nt"></filter></span>
<span class="nt"><filter-mapping></span>
<span class="nt"><filter-name></span>monitoring<span class="nt"></filter-name></span>
<span class="nt"><url-pattern></span>/*<span class="nt"></url-pattern></span>
<span class="nt"></filter-mapping></span>
</code></pre></div></div>
<p>Setelah selesai, kita bisa jalankan aplikasi seperti biasa. Untuk mengakses hasil monitoring, kita dapat mengakses url http://host:port/context-aplikasi/monitoring.</p>
<p>Berikut adalah screenshotnya.</p>
<p><a href="/images/uploads/2012/07/JavaMelody-Charts.png"><img src="/images/uploads/2012/07/JavaMelody-Charts.png" alt="JavaMelody-Charts" /></a></p>
<h2 id="mengaktifkan-mbean">Mengaktifkan MBean</h2>
<p>Selain menggunakan JavaMelody, kita juga bisa melakukan monitoring menggunakan JMX. Beruntung kita yang menggunakan Spring, JMX akan sangat mudah dikonfigurasi. Pada contoh berikut, kita akan mengaktifkan monitoring terhadap statistik Hibernate. Langkah-langkahnya adalah sebagai berikut :</p>
<ol>
<li>Mengaktifkan fitur statistik dalam Hibernate</li>
<li>Mendeklarasikan MBean untuk memonitor statistik Hibernate</li>
<li>Menginstankan JMX server (MBean Server)</li>
</ol>
<p>Aktifasi fitur statistik dalam Hibernate dilakukan dengan mengisi nilai true pada variabel konfigurasi hibernate.generate_statistics, sebagai berikut :</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"sessionFactory"</span>
<span class="na">class=</span><span class="s">"org.springframework.orm.hibernate4.LocalSessionFactoryBean"</span>
<span class="na">p:dataSource-ref=</span><span class="s">"dataSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"hibernateProperties"</span><span class="nt">></span>
<span class="nt"><props></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"hibernate.generate_statistics"</span><span class="nt">></span>true<span class="nt"></prop></span>
<span class="nt"></props></span>
<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"packagesToScan"</span> <span class="na">value=</span><span class="s">"com.artivisi.belajar.restful.domain"</span> <span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Selanjutnya, statistik yang telah dihitung ini dipublish menggunakan MBean.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"hibernateStatisticsMBean"</span> <span class="na">class=</span><span class="s">"org.hibernate.jmx.StatisticsService"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"sessionFactory"</span> <span class="na">ref=</span><span class="s">"sessionFactory"</span> <span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"mbeanExporter"</span> <span class="na">class=</span><span class="s">"org.springframework.jmx.export.MBeanExporter"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"beans"</span><span class="nt">></span>
<span class="nt"><map></span>
<span class="nt"><entry</span>
<span class="na">key=</span><span class="s">"SpringBeans:name=hibernateStatisticsMBean"</span>
<span class="na">value-ref=</span><span class="s">"hibernateStatisticsMBean"</span> <span class="nt">/></span>
<span class="nt"></map></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Terakhir, kita sediakan MBean Server untuk menjalankan MBean yang sudah kita deklarasikan di atas. Spring sudah memudahkan konfigurasinya dengan namespace yang baru</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><context:mbean-server/></span>
</code></pre></div></div>
<p>Selanjutnya, jalankan aplikasi kita seperti biasa di Tomcat, Jetty, dsb.
Setelah aplikasi berjalan, kita dapat melihatnya menggunakan JConsole.</p>
<p>Jalankan JConsole, dan pilih proses yang akan dimonitor.</p>
<p><a href="/images/uploads/2012/07/jconsole-select-pid.png"><img src="/images/uploads/2012/07/jconsole-select-pid.png" alt="jconsole-select-pid" /></a></p>
<p>Tampilkan MBean Hibernate Statistics</p>
<p><a href="/images/uploads/2012/07/jconsole-hibernate-statistics.png"><img src="/images/uploads/2012/07/jconsole-hibernate-statistics.png" alt="jconsole-hibernate-statistics" /></a></p>
<p>Kita bisa lihat bahwa statistik Hibernate sudah dapat diakses. Informasi ini juga bisa ditampilkan di aplikasi monitoring seperti OpenNMS, Hyperic, dan aplikasi lain yang support JMX.</p>
<p>Selain itu, kalau kita sudah mengaktifkan JavaMelody seperti dijelaskan di atas, MBean ini juga bisa ditampilkan di url yang sama seperti screenshot di bawah</p>
<p><a href="/images/uploads/2012/07/javamelody-mbeans.png"><img src="/images/uploads/2012/07/javamelody-mbeans.png" alt="javamelody-mbeans" /></a></p>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Demikianlah hasil oprek-oprek sambil menunggu motor servis rutin. Dengan beberapa langkah sederhana, kita dapat memperoleh informasi lengkap tentang apa yang terjadi di aplikasi kita. Informasi ini selanjutnya dapat dijadikan pedoman untuk melakukan tuning performance. Selain itu, dengan menggunakan aplikasi pengolah data seperti <a href="http://code.google.com/p/rocksteady/">Rocksteady</a>, kita dapat membuat early warning system, yaitu suatu sistem yang dapat memprediksi terjadinya bencana dan memberikan notifikasi, sehingga kita bisa melakukan antisipasi.</p>
<h2 id="referensi">Referensi</h2>
<ul>
<li>
<p><a href="http://www.oracle.com/technetwork/java/javase/tech/javamanagement-140525.html">JMX Homepage</a></p>
</li>
<li>
<p><a href="http://visualvm.java.net/">VisualVM Homepage</a></p>
</li>
<li>
<p><a href="http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html">Cara menggunakan JConsole</a></p>
</li>
<li>
<p><a href="http://dzone.com/snippets/spring-jmx-sample">Cara konfigurasi Spring dan Hibernate</a></p>
</li>
<li>
<p><a href="http://nurkiewicz.blogspot.com/2011/03/jolokia-highcharts-jmx-for-human-beings.html">Menampilkan data dari JMX dengan Jolokia</a></p>
</li>
</ul>
Backup Home Folder2011-08-26T23:00:29+07:00https://software.endy.muhardin.com/linux/backup-home-folder<p>Seberapa penting file di komputer kita? Tentu tidak ternilai harganya. Tapi apakah kita melakukan backup secara terhadap file-file di komputer kita? Beberapa menit yang lalu, saya menjawab tidak untuk pertanyaan tersebut.</p>
<p>Kenapa backup tidak dilakukan? Penyebab utamanya biasanya adalah karena merepotkan. Kita harus pilih file yang mau dibackup, membuka aplikasi backup, lalu menjalankannya. Walaupun cuma butuh waktu beberapa menit, tapi biasanya kita sering menunda dan akhirnya lupa.</p>
<p>Cara paling efektif untuk melakukan backup rutin adalah dengan mengotomasinya. Effort untuk melakukan setup cukup sekali saja, selanjutnya backup akan berjalan otomatis tanpa kita sadari. Pada artikel ini, saya akan posting teknik backup yang saya gunakan.</p>
<p>Sebelum kita mulai, terlebih dulu kita tentukan requirementnya, supaya jelas apa yang kita ingin capai. Saya ingin membackup folder tertentu di komputer saya (misalnya <em>/home/endy</em> dan <em>/opt/multimedia/Photos</em>). Backup ini dilakukan secara rutin (misalnya satu jam sekali, satu hari sekali, atau satu minggu sekali). Selain rutin, juga harus incremental. Artinya kalau saya punya backup hari ini jam 11, maka backup selanjutnya di jam 12 hanya menyimpan file yang berubah saja. Dengan demikian, saya bisa jalankan backupnya satu jam sekali dan tidak akan menyebabkan harddisk menjadi penuh dalam beberapa jam saja.</p>
<!--more-->
<p>Kalau kita cari di Google dengan keyword <em>ubuntu backup application</em>, ada banyak sekali aplikasi backup yang tersedia.
Ada Unison, Bacula, SBackup, rdiff-backup, Deja Dup, dan <a href="http://davestechshop.net/ListOfFreeOpenSourceLinuxUbuntuBackupSoftware">entah apa lagi</a>. Walaupun demikian, seperti biasanya, saya akan gunakan aplikasi yang paling populer, universal (ada di mana-mana), dan bisa dijalankan dari command line (supaya bisa diotomasi dengan cron). Pilihannya tentu adalah <em>rsync</em>.</p>
<p><em>rsync</em> adalah aplikasi untuk melakukan file transfer. Dia memiliki beberapa kelebihan, diantaranya :</p>
<ul>
<li>
<p>tersedia di semua *nix (misalnya Linux dan Mac)</p>
</li>
<li>
<p>berbasis command line, sehingga bisa saya aplikasikan juga di server</p>
</li>
<li>
<p>sudah teruji kehandalannya</p>
</li>
<li>
<p>bisa resume (bila transfer putus di tengah jalan, tidak perlu ulang dari awal)</p>
</li>
<li>
<p>data yang ditransfer bisa dikompres, supaya lebih cepat</p>
</li>
<li>
<p>bisa mengirim data melalui ssh, sehingga keamanan data terjamin</p>
</li>
<li>
<p>memiliki kemampuan hard linking sehingga bisa menghemat space (akan dijelaskan lebih lanjut)</p>
</li>
</ul>
<p>With great power, comes great complexity. Demikian kata pamannya Spiderman seandainya dia harus menggunakan <em>rsync</em>. Saking banyaknya opsi, sehingga kita bingung apa yang harus dipakai. Berikut adalah opsi rsync yang saya gunakan :</p>
<ul>
<li>
<p>a : archive. Opsi ini sama dengan kalau kita mengaktifkan opsi rlptgoD</p>
</li>
<li>
<p>r : rekursif sampai ke subfolder terdalam</p>
</li>
<li>
<p>l : symlink tetap dicopy sebagai symlink</p>
</li>
<li>
<p>p : file permission disamakan dengan aslinya</p>
</li>
<li>
<p>t : modification time (waktu terakhir update) disamakan dengan aslinya</p>
</li>
<li>
<p>g : kepemilikan group disamakan dengan aslinya</p>
</li>
<li>
<p>o : kepemilikan owner disamakan dengan aslinya</p>
</li>
<li>
<p>D : file device dan special disamakan dengan aslinya</p>
</li>
<li>
<p>force : folder kosong di tujuan dihapus walaupun ada isinya</p>
</li>
<li>
<p>ignore-errors : lanjut terus walaupun ada error</p>
</li>
<li>
<p>exclude-from : file text berisi daftar file/folder yang tidak perlu ditransfer</p>
</li>
<li>
<p>link-dest : lihat ke folder yang disebutkan, kalau ada file yang sama, buat hard link</p>
</li>
</ul>
<p>Pada penjelasan di atas, beberapa kali disebutkan istilah hard-link. Di Linux, suatu file terdiri dari dua bagian : isi (content), dan nama. Satu content yang sama bisa saja memiliki dua nama yang berbeda di folder berbeda sehingga terlihat seolah-olah ada dua file.
Misalnya, kita memiliki file bernama <em>coba.txt</em>. Ini artinya, ada satu content dan satu nama file coba.txt. Kita bisa membuat nama file baru yang isinya sama dengan perintah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ln coba.txt halo.txt_
</code></pre></div></div>
<p>Perintah di atas akan membentuk file <em>halo.txt</em> yang isinya sama dengan <em>coba.txt</em>. Kalau kita edit <em>coba.txt</em>, maka isi file <em>halo.txt</em> juga akan berubah, karena mereka sebetulnya menunjuk ke benda yang sama.</p>
<p>Dengan menggunakan hard-link ini, kita bisa menduplikasi file tanpa menduplikasi space di harddisk. Ini akan sangat berguna untuk membuat incremental backup, karena kita bisa membuat backup sesering mungkin tanpa memenuhi isi harddisk.</p>
<p>Fitur ini kita gunakan pada <em>rsync</em> dengan opsi <em>link-dest</em>. Sebagai contoh, kita jalankan backup pertama kali dan menghasilkan folder <em>20110826-1100</em>. Pada waktu kita jalankan backup kedua, kita berikan opsi <em>link-dest=20110826-1100</em>. Saat akan mengisi folder yang baru (misalnya <em>20110826-1200</em>), <em>rsync</em> akan melihat ke folder <em>20110826-1100</em> dan memeriksa apakah file yang sama sudah ada. Bila sudah ada, maka <em>rsync</em> tidak akan menulis file baru, melainkan hanya akan membuat hard-link saja. Jadi, bila backup pertama berisi 100 file dengan total 10 GB, dan backup kedua berisi 99 file yang sama, dan 1 file saja yang berubah dengan ukuran 1 GB, maka total space yang terpakai adalah 10 GB (backup pertama) dan 1 GB (backup kedua), bukannya 20 GB.</p>
<p>Setelah kita memahami opsi <em>rsync</em>, berikut adalah perintah yang kita gunakan</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="c"># run hourly with cron</span>
<span class="c"># 0 * * * * /path/ke/rsync-backup.sh /home/endy /opt/downloads/backups /path/ke/rsync-exclude.txt</span>
<span class="nv">SRC</span><span class="o">=</span><span class="nv">$1</span>
<span class="nv">DEST</span><span class="o">=</span><span class="nv">$2</span>
<span class="nv">EXCLUDES</span><span class="o">=</span><span class="nv">$3</span>
<span class="nv">args</span><span class="o">=(</span><span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span><span class="o">)</span><span class="p">;</span>
<span class="nv">MORE_OPTS</span><span class="o">=</span><span class="k">${</span><span class="nv">args</span><span class="p">[@]</span>:3<span class="k">}</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"$#"</span> <span class="nt">-lt</span> 3 <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"Usage : </span><span class="nv">$0</span><span class="s2"> <src> <dest> <exclude list> [rsync options]"</span>
<span class="k">return </span>1
<span class="k">fi
</span><span class="nv">LAST</span><span class="o">=</span><span class="si">$(</span><span class="nb">ls</span> <span class="nt">-tr</span> <span class="nv">$DEST</span> | <span class="nb">tail</span> <span class="nt">-1</span><span class="si">)</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$LAST</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">""</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">LINK</span><span class="o">=</span><span class="s2">"--link-dest=</span><span class="nv">$DEST</span><span class="s2">/</span><span class="nv">$LAST</span><span class="s2">"</span>
<span class="k">fi
</span><span class="nv">OPTS</span><span class="o">=</span><span class="s2">" -a --force --ignore-errors --exclude-from=</span><span class="nv">$EXCLUDES</span><span class="s2"> </span><span class="nv">$LINK</span><span class="s2">"</span>
<span class="c"># echo OPTS $OPTS MORE_OPTS $MORE_OPTS</span>
rsync <span class="nv">$OPTS</span> <span class="nv">$MORE_OPTS</span> <span class="nv">$SRC</span> <span class="nv">$DEST</span>/<span class="si">$(</span><span class="nb">date</span> +%Y%m%d-%H%M<span class="si">)</span>
</code></pre></div></div>
<p>File <em>rsync-exclude.txt</em> berisi folder yang tidak dibackup, punya saya isinya seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#rsync script exclude file
**/.thumbnails/
**/Desktop/Trash/
**/.cache/
**/.m2/
**/.metadata/
**/.netbeans/
**/.shotwell/
**/.config/
**/.gconf/
**/virtual-machines/
</code></pre></div></div>
<p><em>folder-backup-sebelumnya</em> perlu dihitung dulu. Caranya menggunakan perintah <em>ls -tr</em> yang akan menampilkan isi folder yang diurutkan berdasarkan modification time secara descending. Berikut contoh outputnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -ltr /opt/downloads/backups/
total 12
drwxr-xr-x 3 endy endy 4096 2011-08-26 13:21 20110826-1321
drwxr-xr-x 3 endy endy 4096 2011-08-26 14:07 20110826-1407
drwxr-xr-x 3 endy endy 4096 2011-08-26 14:27 20110826-1427
</code></pre></div></div>
<p>Dari sini, kita cukup ambil yang paling atas menggunakan perintah <em>tail -1</em></p>
<p>ls -tr /opt/downloads/backups/ | tail -1
20110826-1427</p>
<p>Dengan bermodalkan pengetahuan tersebut, kita bisa membuat script seperti ini.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="c"># run hourly with cron</span>
<span class="c"># 0 * * * * /path/ke/rsync-backup.sh /home/endy /opt/downloads/backups /path/ke/rsync-exclude.txt</span>
<span class="nv">SRC</span><span class="o">=</span><span class="nv">$1</span>
<span class="nv">DEST</span><span class="o">=</span><span class="nv">$2</span>
<span class="nv">EXCLUDES</span><span class="o">=</span><span class="nv">$3</span>
<span class="nv">args</span><span class="o">=(</span><span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span><span class="o">)</span><span class="p">;</span>
<span class="nv">MORE_OPTS</span><span class="o">=</span><span class="k">${</span><span class="nv">args</span><span class="p">[@]</span>:3<span class="k">}</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"$#"</span> <span class="nt">-lt</span> 3 <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"Usage : </span><span class="nv">$0</span><span class="s2"> <src> <dest> <exclude list> [rsync options]"</span>
<span class="k">return </span>1
<span class="k">fi
</span><span class="nv">LAST</span><span class="o">=</span><span class="si">$(</span><span class="nb">ls</span> <span class="nt">-tr</span> <span class="nv">$DEST</span> | <span class="nb">tail</span> <span class="nt">-1</span><span class="si">)</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$LAST</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">""</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">LINK</span><span class="o">=</span><span class="s2">"--link-dest=</span><span class="nv">$DEST</span><span class="s2">/</span><span class="nv">$LAST</span><span class="s2">"</span>
<span class="k">fi
</span><span class="nv">OPTS</span><span class="o">=</span><span class="s2">" -a --force --ignore-errors --exclude-from=</span><span class="nv">$EXCLUDES</span><span class="s2"> </span><span class="nv">$LINK</span><span class="s2">"</span>
<span class="c"># echo OPTS $OPTS MORE_OPTS $MORE_OPTS</span>
rsync <span class="nv">$OPTS</span> <span class="nv">$MORE_OPTS</span> <span class="nv">$SRC</span> <span class="nv">$DEST</span>/<span class="si">$(</span><span class="nb">date</span> +%Y%m%d-%H%M<span class="si">)</span>
</code></pre></div></div>
<p>Untuk membackup folder <em>/home/endy</em> ke folder <em>/opt/downloads/backups</em>, kita jalankan seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./rsync-backup-home.sh /home/endy /opt/downloads/backups rsync-exclude.txt
</code></pre></div></div>
<p>Selanjutnya, kita bisa pasang di crontab dengan setting seperti ini, supaya dijalankan tiap tiga jam.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 */3 * * * /path/ke/rsync-backup-home.sh /home/endy /opt/downloads/backups/path/ke/rsync-exclude.txt
</code></pre></div></div>
<p>Voila … folder home kita sudah terbackup secara otomatis tanpa kita sadari. Sepanjang menulis artikel ini, laptop saya sudah membackup dirinya sendiri sebanyak 3 kali :D</p>
<p>Setelah membuat backup di harddisk laptop, tentunya kita ingin memindahkannya ke external harddisk supaya kita bisa mengosongkan lokasi backup di laptop.</p>
<p>Berikut adalah perintah rsync yang digunakan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -avzPH /opt/downloads/backups /media/DATA2/
</code></pre></div></div>
<p>Dan ini adalah penjelasan terhadap opsi yang digunakan:</p>
<ul>
<li>
<p>P : sama dengan partial dan progress</p>
</li>
<li>
<p>partial : file yang baru dicopy sebagian tetap disimpan agar bisa diresume</p>
</li>
<li>
<p>progress : menampilkan progress report</p>
</li>
<li>
<p>H : hard link dicopy sebagai hard link juga. Tanpa opsi ini, tiap hard link akan dibuatkan file baru sehingga boros space harddisk</p>
</li>
</ul>
<p>Perintah untuk transfer ke external harddisk ini tidak saya jalankan secara otomatis via cron, karena harddisknya belum tentu terpasang.</p>
<p>Demikianlah artikel tentang backup rutin. Mudah-mudahan kita semua bisa terhindar dari musibah harddisk.</p>
<h3 id="referensi">Referensi</h3>
<ul>
<li>
<p><a href="http://www.mikerubel.org/computers/rsync_snapshots">Easy Automated Snapshot-Style Backups with Linux and Rsync</a></p>
</li>
<li>
<p><a href="http://www.hermann-uwe.de/blog/simple-backups-using-rsync">Simple backup using rsync</a></p>
</li>
<li>
<p><a href="http://rsync.samba.org/examples.html">rsync examples</a></p>
</li>
</ul>
Staged Deployment2011-08-16T15:57:35+07:00https://software.endy.muhardin.com/java/staged-deployment<p>Staged Deployment</p>
<p>Pada waktu kita coding, tentunya kita melakukan test terhadap kode program yang kita tulis. Kita jalankan langkah-langkah sesuai yang telah didefinisikan dalam test scenario. Setelah test di komputer kita sendiri (local) selesai dilakukan, tentunya kode program tersebut tidak langsung kita deploy ke production. Best practicesnya adalah, kita deploy aplikasinya ke server testing untuk kemudian ditest oleh Software Tester. Barulah setelah dinyatakan OK oleh tester, aplikasi versi terbaru tersebut kita deploy ke production.</p>
<p>Dengan demikian, kita memiliki tiga deployment environment, yaitu :</p>
<ul>
<li>
<p>development (komputer si programmer)</p>
</li>
<li>
<p>testing (test server)</p>
</li>
<li>
<p>production (live system)</p>
</li>
</ul>
<p>Environment ini bisa lebih banyak lagi kalau aplikasi kita harus dites kompatibilitasnya dengan berbagai hardware atau sistem operasi.</p>
<p>Cara kerja seperti ini disebut dengan istilah staged deployment atau deployment bertahap. Dengan menggunakan staged deployment, kita mencegah terjadinya bug fatal di production/live system.</p>
<p>Tantangan yang kita hadapi adalah, bagaimana cara mengelola konfigurasi aplikasi kita sehingga bisa dideploy di berbagai environment secara baik. Teknik bagaimana cara melakukan ini berbeda-beda, tergantung bahasa pemrograman, framework, dan library yang kita gunakan.</p>
<p>Pada artikel ini, kita akan membahas cara mengelola konfigurasi deployment menggunakan <a href="http://endy.artivisi.com/blog/java/development-stack-2011/">teknologi yang biasa digunakan di ArtiVisi</a>, yaitu<a href="http://www.springframework.org">Spring Framework</a> dan <a href="logback.qos.ch">Logback</a>.</p>
<h2 id="alternatif-solusi">Alternatif Solusi</h2>
<p>Manajemen konfigurasi ini bisa kita lakukan dengan dua pendekatan, yaitu dikelola dengan <a href="http://maven.apache.org/guides/introduction/introduction-to-profiles.html">Maven Profile</a>, atau dengan konfigurasi Spring Framework.</p>
<p>Jika kita menggunakan Maven Profile, kita menambahkan opsi pada saat melakukan build, kira-kira seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn -P production clean install
</code></pre></div></div>
<p>atau</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn -Denv=production clean install
</code></pre></div></div>
<p>Dalam konfigurasi profile, kita bisa memilih file mana yang akan diinclude di dalam hasil build. Hasilnya, kita bisa menghasilkan artifact yang berbeda tergantung dari opsi yang kita berikan pada saat build.</p>
<p>Walaupun demikian, berdasarkan hasil Googling, ternyata <a href="http://java.dzone.com/articles/maven-profile-best-practices">metode ini tidak direkomendasikan</a>. Justru konfigurasi melalui Spring lebih disarankan.</p>
<p>Dengan menggunakan konfigurasi Spring, artifact yang dihasilkan oleh build hanya satu jenis saja. Artifact ini berisi semua pilihan konfigurasi. Konfigurasi mana yang akan aktif pada saat dijalankan (runtime) akan ditentukan oleh setting environment variable, bukan oleh artifactnya.</p>
<p>Selanjutnya, kita akan membahas metode manajemen konfigurasi menggunakan Spring.</p>
<h2 id="konfigurasi-database">Konfigurasi Database</h2>
<p>Konfigurasi yang biasanya berbeda adalah informasi koneksi database. Untuk membedakan masing-masing environment, kita akan membuat tiga file, yaitu:</p>
<ul>
<li>
<p>jdbc.properties : digunakan di laptop programmer</p>
</li>
<li>
<p>jdbc.testing.properties : digunakan di server test</p>
</li>
<li>
<p>jdbc.production.properties : digunakan di live</p>
</li>
</ul>
<p>Berikut contoh isi jdbc.properties, yaitu konfigurasi koneksi database di laptop saya :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost/kasbon?zeroDateTimeBehavior=convertToNull
jdbc.username = kasbon
jdbc.password = kasbon
</code></pre></div></div>
<p>Kemudian, ini file jdbc.testing.properties :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost/kasbon_testing?zeroDateTimeBehavior=convertToNull
jdbc.username = root
jdbc.password = admin
</code></pre></div></div>
<p>Perhatikan bahwa informasi nama database, username, dan password databasenya berbeda dengan yang ada di konfigurasi laptop.</p>
<p>Terakhir, jdbc.production.properties</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost/kasbon_live?zeroDateTimeBehavior=convertToNull
jdbc.username = root
jdbc.password = admin
</code></pre></div></div>
<p>Ketiga file konfigurasi ini akan dibaca oleh konfigurasi Spring, yaitu di file applicationContext.xml. Isi lengkap dari file ini adalah sebagai berikut.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="na">xmlns:context=</span><span class="s">"http://www.springframework.org/schema/context"</span>
<span class="na">xmlns:p=</span><span class="s">"http://www.springframework.org/schema/p"</span> <span class="na">xmlns:tx=</span><span class="s">"http://www.springframework.org/schema/tx"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"</span><span class="nt">></span>
<span class="nt"><context:property-placeholder</span> <span class="na">location=</span><span class="s">"
classpath*:jdbc.properties,
classpath*:jdbc.${stage}.properties
"</span> <span class="nt">/></span>
<span class="nt"><tx:annotation-driven</span> <span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"dataSource"</span> <span class="na">class=</span><span class="s">"org.apache.commons.dbcp.BasicDataSource"</span>
<span class="na">destroy-method=</span><span class="s">"close"</span> <span class="na">p:driverClassName=</span><span class="s">"${jdbc.driver}"</span> <span class="na">p:url=</span><span class="s">"${jdbc.url}"</span>
<span class="na">p:username=</span><span class="s">"${jdbc.username}"</span> <span class="na">p:password=</span><span class="s">"${jdbc.password}"</span> <span class="na">p:maxWait=</span><span class="s">"40000"</span>
<span class="na">p:maxActive=</span><span class="s">"80"</span> <span class="na">p:maxIdle=</span><span class="s">"20"</span> <span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"transactionManager"</span>
<span class="na">class=</span><span class="s">"org.springframework.orm.hibernate3.HibernateTransactionManager"</span>
<span class="na">p:sessionFactory-ref=</span><span class="s">"sessionFactory"</span> <span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"sessionFactory"</span>
<span class="na">class=</span><span class="s">"org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"</span>
<span class="na">p:dataSource-ref=</span><span class="s">"dataSource"</span> <span class="na">p:configLocations=</span><span class="s">"classpath*:com/artivisi/**/hibernate.cfg.xml"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"hibernateProperties"</span><span class="nt">></span>
<span class="nt"><props></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"hibernate.dialect"</span><span class="nt">></span>${hibernate.dialect}<span class="nt"></prop></span>
<span class="nt"></props></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"messageSource"</span>
<span class="na">class=</span><span class="s">"org.springframework.context.support.ResourceBundleMessageSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"basenames"</span><span class="nt">></span>
<span class="nt"><list></span>
<span class="nt"><value></span>messages<span class="nt"></value></span>
<span class="nt"></list></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>Untuk lebih spesifik, konfigurasinya ada di baris berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><context:property-placeholder</span> <span class="na">location=</span><span class="s">"
classpath*:jdbc.properties,
classpath*:jdbc.${stage}.properties
"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>Di sana kita melihat ada variabel ${stage}.
Variabel ${stage} ini akan dicari dari <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-xml-import">beberapa tempat, diantaranya environment variabel yang bisa diset di JVM ataupun di sistem operasi</a>. Cara mengeset variabel ${stage} akan kita bahas sebentar lagi.</p>
<p>Di situ kita menyuruh Spring untuk membaca file jdbc.properties dan jdbc.${stage}.properties. Jika ada nilai variabel yang sama (misalnya jdbc.username), maka nilai variabel di file yang disebutkan belakangan akan menimpa nilai yang didefinisikan file di atasnya.</p>
<p>Contohnya, misalnya variabel ${stage} nilainya adalah testing. Maka Spring akan membaca file jdbc.properties dan jdbc.testing.properties. Karena kedua file memiliki variabel jdbc.url, maka isi jdbc.url di file jdbc.testing.properties akan menimpa nilai jdbc.url di jdbc.properties.</p>
<p>Bila variabel ${stage} tidak ada isinya, Spring akan mencari file yang namanya jdbc.${stage}.properties, dan tidak akan ketemu. Dengan demikian, nilai yang digunakan adalah yang ada di jdbc.properties.</p>
<p>Dengan demikian, behavior aplikasi adalah sebagai berikut</p>
<blockquote>
<p>Bila variabel stage diset production atau testing, maka yang digunakan adalah nilai konfigurasi di jdbc.production.properties atau jdbc.testing.properties. Bila tidak diset atau diset selain itu, maka yang digunakan adalah konfigurasi di jdbc.properties</p>
</blockquote>
<p>Behavior seperti inilah yang kita inginkan. Selanjutnya, tinggal kita isi nilai variabel stage.</p>
<h2 id="setting-environment-variabel">Setting Environment Variabel</h2>
<p>Variabel stage bisa diset dengan berbagai cara. Bila kita menggunakan <a href="http://tomcat.apache.org">Apache Tomcat</a>, maka kita mengedit file startup.sh atau startup.bat. Modifikasi baris yang berisi CATALINA_OPTS menjadi seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export CATALINA_OPTS="-Dstage=production"
</code></pre></div></div>
<p>Atau, kita bisa jalankan dengan Jetty melalui Maven</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn jetty:run -Dstage=testing
</code></pre></div></div>
<p>Bisa juga melalui environment variabel sistem operasi, di Linux kita set seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>EXPORT stage=production
</code></pre></div></div>
<h2 id="konfigurasi-logger">Konfigurasi Logger</h2>
<p>Dengan menggunakan Spring seperti di atas, kita bisa membaca konfigurasi apa saja, misalnya</p>
<ul>
<li>
<p>Konfigurasi email : bila aplikasi kita mengirim/menerima email</p>
</li>
<li>
<p>Konfigurasi server lain : bila aplikasi kita berinteraksi dengan aplikasi orang lain, misalnya webservice atau koneksi socket</p>
</li>
<li>
<p>dsb</p>
</li>
</ul>
<p>Walaupun demikian, konfigurasi logger biasanya tidak diload oleh Spring, melainkan langsung dibaca oleh library loggernya.</p>
<p>Kita di ArtiVisi menggunakan SLF4J dan Logback. Cara konfigurasinya mirip dengan Spring. Kita punya satu master file yang akan membaca file lain sesuai isi variabel stage. Untuk itu kita siapkan beberapa file berikut:</p>
<ul>
<li>
<p>logback.xml : file konfigurasi utama</p>
</li>
<li>
<p>logback.production.xml : konfigurasi logger production, akan diinclude oleh logback.xml</p>
</li>
<li>
<p>logback.testing.xml : konfigurasi logger testing, akan diinclude oleh logback.xml</p>
</li>
<li>
<p>logback.development.xml : konfigurasi logger development, akan diinclude oleh logback.xml</p>
</li>
</ul>
<p>Berikut isi file logback.xml.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><configuration></span>
<span class="nt"><appender</span> <span class="na">name=</span><span class="s">"STDOUT"</span> <span class="na">class=</span><span class="s">"ch.qos.logback.core.ConsoleAppender"</span><span class="nt">></span>
<span class="nt"><encoder></span>
<span class="nt"><pattern></span>%d %-5level %logger{35} - %msg %n<span class="nt"></pattern></span>
<span class="nt"></encoder></span>
<span class="nt"></appender></span>
<span class="nt"><appender</span> <span class="na">name=</span><span class="s">"FILE"</span> <span class="na">class=</span><span class="s">"ch.qos.logback.core.FileAppender"</span><span class="nt">></span>
<span class="nt"><file></span>${catalina.home:-.}/logs/kasbon-${stage:-development}.log<span class="nt"></file></span>
<span class="nt"><encoder></span>
<span class="nt"><pattern></span>%d %-5level %logger{35} - %msg %n<span class="nt"></pattern></span>
<span class="nt"></encoder></span>
<span class="nt"></appender></span>
<span class="nt"><include</span> <span class="na">resource=</span><span class="s">"logback-${stage:-development}.xml"</span><span class="nt">/></span>
<span class="nt"></configuration></span>
</code></pre></div></div>
<p>Seperti kita lihat, file ini berisi konfigurasi yang berlaku umum, seperti appender yang digunakan. Di file ini kita menulis variabel seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>${stage:-development}
</code></pre></div></div>
<p>Yang artinya adalah, <a href="http://logback.qos.ch/manual/configuration.html">isi dengan variabel stage, kalau variabel tersebut tidak diset, defaultnya adalah development</a>. Ini sesuai dengan keinginan kita seperti pada waktu mengkonfigurasi Spring di atas.</p>
<p>Isi file logback-development.xml dan teman-temannya dapat dilihat <a href="https://github.com/artivisi/aplikasi-kasbon/tree/master/aplikasi-kasbon-config/src/main/resources">di Github</a>.</p>
<p>Demikianlah tutorial cara mengelola konfigurasi untuk keperluan staged deployment. Semoga bermanfaat.</p>
Meluruskan Mitos CMMI2011-08-10T15:01:25+07:00https://software.endy.muhardin.com/manajemen/meluruskan-mitos-cmmi<p>Di milis manajemen proyek IT sedang rame diskusi tentang CMMI dan Scrum.
Seperti layaknya diskusi yang rame, perdebatan dibumbui dengan segala macam mitos dan ‘FUDification’.</p>
<p>Berikut adalah tanggapan saya tentang mitos yang berkembang mengenai CMMI, dicopy-paste dari posting milis dengan sedikit penyesuaian.</p>
<p>Beberapa mitos yang akan diluruskan :</p>
<ul>
<li>CMMI adalah metodologi manajemen proyek yang cenderung waterfall</li>
<li>CMMI mewajibkan kita bikin banyak dokumen</li>
</ul>
<p>Pada artikel ini, kita akan meluruskan mitos-mitos tersebut.</p>
<h2 id="cmmi--metodologi-cenderung-waterfall">CMMI = metodologi, cenderung waterfall</h2>
<p>CMMI bukanlah metodologi manajemen proyek seperti
<a href="http://en.wikipedia.org/wiki/Scrum_(development)">Scrum</a>, <a href="http://en.wikipedia.org/wiki/IBM_Rational_Unified_Process">IBM Rational Unified Process</a>, <a href="http://en.wikipedia.org/wiki/Extreme_Programming">XP</a>, apalagi <a href="http://en.wikipedia.org/wiki/Waterfall_model">Waterfall</a>.</p>
<p>CMMI sebetulnya sudah pernah saya jelaskan <a href="http://endy.artivisi.com/blog/manajemen/apa-itu-cmmi/">di posting saya sebelumnya</a>. Tapi untuk lebih menyederhanakan lagi, kita bisa analogikan CMMI seperti akreditasi perguruan tinggi. Kalau kita mau daftar kuliah, biasanya kita cari tahu akreditasi kampus yang kita tuju. Semakin tinggi akreditasinya, semakin tinggi ekspektasi kita terhadap kualitas perguruan tinggi tersebut. <a href="http://ban-pt.kemdiknas.go.id/">Akreditasi perguruan tinggi</a> <a href="http://ban-pt.kemdiknas.go.id/index.php?option=com_content&view=article&id=57&Itemid=63&lang=in">ditentukan oleh banyak hal</a>, diantaranya :</p>
<ul>
<li>berapa jumlah dosen yang S3</li>
<li>berapa karya ilmiah dan penelitian yang dihasilkan dalam satu periode</li>
<li>dsb</li>
</ul>
<p>Untuk menentukan suatu kampus mendapat level A, B, atau lainnya, maka ada tim assessor yang akan memeriksa apakah kampus tersebut sudah memenuhi apa yang dipersyaratkan.</p>
<p>Demikian juga dengan CMMI, berisi seperangkat checklist yang bentuknya kira-kira seperti ini:</p>
<table>
<thead>
<tr>
<th>Level</th>
<th>Process Area</th>
<th>OK</th>
<th>Not OK</th>
</tr>
</thead>
<tbody>
<tr>
<td>2</td>
<td>REQM</td>
<td>v</td>
<td> </td>
</tr>
<tr>
<td>2</td>
<td>PP</td>
<td>v</td>
<td> </td>
</tr>
<tr>
<td>2</td>
<td>PMC</td>
<td> </td>
<td>v</td>
</tr>
<tr>
<td>2</td>
<td>MA</td>
<td> </td>
<td>v</td>
</tr>
</tbody>
</table>
<p>Nah, checklist itu nanti akan dicentang sesuai dengan kapabilitas perusahaan yang diperiksa.</p>
<p>Adapun urusan Scrum, Waterfall, XP, whatever metodologi yang kita gunakan,
hanyalah mencakup sebagian saja dari CMMI.</p>
<p>CMMI itu model untuk menggambarkan organisasi pembuat software yang mature. Apa itu mature? Salah satu karakteristiknya adalah konsistensi. Perusahaan yang gak mature, hasil kerjanya gak konsisten. Project A ontime, Project B molor 3 tahun. Project X bugnya dikit, Project Y isinya bug doang gak ada fiturnya.</p>
<p>Kalau kita bisa mengeksekusi project dengan sukses, kita hanya bisa lulus CMMI level 2. Untuk bisa mendapatkan level 3, kita harus bisa mengeksekusi project dengan sukses <strong>secara konsisten</strong>.
Untuk bisa konsisten, maka kita harus bisa menduplikasi project sukses ke seluruh perusahaan. Jadi, kalau kita sudah sukses pakai Scrum di project kita sekarang, tetap saja baru level 2. Hanya setelah kesuksesan Scrum bisa direplikasi di keseluruhan perusahaan, barulah bisa level 3.</p>
<p>Seperti juga halnya replikasi resep McDonalds ke seluruh cabang, untuk bisa mereplikasi project sukses ke seluruh perusahaan,
dibutuhkan kegiatan tambahan di level organisasi, misalnya :</p>
<ul>
<li>Menulis SOP (OPD)</li>
<li>Membuat program pelatihan internal (OT)</li>
<li>Selalu menganalisas prosedur yang sekarang berlaku, supaya bisa diimprove (OPF)</li>
</ul>
<p>Yang di dalam kurung adalah process area yang bersesuaian di CMMI.</p>
<p>Berurusan dengan perusahaan yang mature akan mengurangi resiko di client. Apa itu resiko?</p>
<p>Buat orang awam seperti kita, resiko adalah simply sekian persen kemungkinan adanya masalah di kemudian hari. Nah, ada perspektif finansial yang kita orang teknis biasanya gak kepikiran. Buat orang finance, persentase tersebut bisa diuangkan. Misalnya kita mau bikin aplikasi costnya 100 M, uangnya minjem ke bank. Karena pada dasarnya bank gak mau rugi, 100 M itu akan diasuransikan sama dia. Jadi kalo projectnya bubaran, kita gak sanggup bayar, hutangnya akan ditalangin sama asuransi.</p>
<p>Asuransi akan lihat, kita pakai vendor siapa. Kalo vendornya gak mature (baca: resiko tinggi) maka premi asuransinya akan tinggi. Akibatnya, biaya pinjaman kita (cost of money) juga tinggi.</p>
<p>Bisa aja kita bayar 100 M (pokok) + 20 M (bunga) + 20 M (asuransi). Padahal kalo vendornya mature, premi asuransinya cuma 5 M. Nah, jadi urusan resiko dan maturity ini bukan semata jargon2 aja, tapi ada duit beneran yang tersangkut di dalamnya.</p>
<p>Demikianlah mitos pertama, CMMI bukan metodologi manajemen proyek, melainkan manajemen keseluruhan perusahaan.</p>
<h2 id="cmmi-mewajibkan-kita-bikin-banyak-dokumen">CMMI mewajibkan kita bikin banyak dokumen</h2>
<p>CMMI sama sekali tidak mengharuskan kita bikin dokumen apa-apa.</p>
<p>Yang ada, kita harus :</p>
<ul>
<li><strong>melakukan</strong> project planning (level 2)</li>
<li><strong>melakukan</strong> project monitoring & control (level 2)</li>
<li><strong>mendefinisikan</strong> project life cycle : bisa waterfall, scrum, spiral, cowboy programming juga boleh</li>
</ul>
<p>Berikut beberapa definisi singkat</p>
<ul>
<li>Planning : merencanakan apa yang akan dilakukan</li>
<li>Monitoring : melihat kondisi aktual, apakah sesuai dengan plan</li>
<li>Control : melakukan tindakan kalau kondisi aktual tidak sesuai dengan plan</li>
</ul>
<p>Nah, kita harus membuktikan bahwa kita benar2 melakukan apa yang disuruh. Gimana cara membuktikannya?</p>
<p>Kita bisa :</p>
<ol>
<li>Tunjukkan dokumen hardcopy, atau</li>
<li>Tunjukkan bahwa kita melakukan planning, monitoring, dan control di aplikasi yang kita pakai (Redmine, planningpoker.com, pivotaltracker.com, basecamphq.com, fogbugz, whatever)</li>
</ol>
<p>Nah, dari 2 cara di atas, kalo kita <strong>benar-benar</strong> melakukan, akan lebih mudah menunjukkan yang #2. Tapi kalo akal2an, sebenarnya gak planning tapi mau ngakunya planning, akan lebih mudah memalsukan yang #1. Soalnya #2 gak bisa di-back-dated, sedangkan #1 bisa.</p>
<p>Jadi, fokusnya lebih ke <strong>melakukan proses</strong>, bukan <strong>membuat dokumen</strong></p>
<p>Kemudian, ada kesalah-kaprahan juga yang umum terjadi tentang planning. Planning itu tidak sekali saja lalu dipakai sepanjang project. Project plan harus mencerminkan kondisi yang terbaru dari project. Misalnya, kita bikin plan awal (versi 1) selesai 3 bulan. Ternyata waktu monitoring di akhir bulan 1, kita udah tau bahwa gak bakalan selesai dalam 2 bulan sisanya. Kita harus melakukan controlling terhadap projectnya. Tindakan control bisa macam2, bisa kita tambah orang biar tetap selesai dalam 3 bulan, bisa juga revisi plannya sehingga mencerminkan kondisi setelah 1 bulan berjalan.</p>
<p>UUD 45 aja bisa diamandemen, masa project plan gak bisa :D</p>
<p>Contoh lain, mengelola requirement (Requirement Management), Level 2.</p>
<blockquote>
<p>S.P 1.1 : Understand Requirement : kita harus memastikan bahwa requirement dipahami.</p>
</blockquote>
<p>Gimana cara membuktikannya?</p>
<p>Kalo prosesnya benar-benar dijalankan, kita bisa tunjukkan email dari BA ke Client yang isinya mengkonfirmasi pemahaman BA tentang requirement yang diminta Client.</p>
<p>Atau kalo seperti Scrum, Clientnya hadir di ruangan yang sama, gak nyatet apa2, rekaman audio juga boleh. Intinya, ada sesuatu yang bisa ditunjukkan ke auditor bahwa kita sudah Understanding Requirement.</p>
<p>Kalo prosesnya palsu, artinya sebenarnya gak dilakukan, tapi mau lulus Level 2, maka dibuatlah dokumen palsu. Bentuknya biasanya review report, isinya item2 requirement, lalu nanti ada tandatangan client palsu.</p>
<p>So, overhead dokumen (mis: review report) itu ada kalo kita memalsukan proses.</p>
<p>Selama kita benar-benar menjalankan apa yang disuruh, pasti ada evidence bahwa kita menjalankan, entah itu bentuknya chat YM, email, Skype call, apalah terserah, tidak ada CMMI mewajibkan formatnya harus mp3 atau apa.</p>
<blockquote>
<p>SP 1.2 : Obtain Commitment to Requirement : semua pihak harus commit terhadap requirement yang sudah dibuat.</p>
</blockquote>
<p>Gimana cara membuktikan bahwa kita comply dengan SP ini?</p>
<p>Paling gampang, BA kirim email ke Client, “Pak, di iterasi ini, kita kerjakan req #12, #14, sama #15 ya. #13 pending dulu aja”</p>
<p>Client reply, “Ok”</p>
<p>That’s it, tunjukkan emailnya ke auditor, beres.</p>
<p>Kalau proses ini tidak dijalankan, akan menimbulkan masalah di kemudian hari. Usernya client bilang A, bosnya user bilang A+, programmer bilang C, PM bilang lain lagi. Sekali lagi, selama prosesnya dilakukan, emailnya pasti ada.</p>
<p>Kalo prosesnya palsu, atau clientnya gaptek gak kenal email, ya dibuatlah dokumen requirement sign off. Orang2 tandatangan. Dokumennya dijadikan evidence.</p>
<blockquote>
<p>SP 1.3 : Manage Requirement Changes : kalo requirement berubah, harus di-manage.</p>
</blockquote>
<p>Apa itu dimanage?</p>
<p>Dimanage artinya harus jelas :</p>
<ul>
<li>apa yang berubah</li>
<li>siapa yang minta berubah</li>
<li>siapa yang approve</li>
<li>apa impactnya ke schedule/cost/effort/cuaca hari ini</li>
</ul>
<p>Apa buktinya? Email boleh, chat log boleh, rekaman cctv boleh.</p>
<p>Ok, lalu kenapa semua harus ada evidence ??</p>
<p>Berikut joke dari auditor kita dulu,</p>
<blockquote>
<p>In God We Trust, everybody else brings data.</p>
</blockquote>
<p>Jadi, CMMI = banyak dokumen hanyalah mitos belaka. Untuk bisa melakukan verifikasi, auditor tentu butuh melihat evidence. Di jaman modern seperti sekarang, evidence bentuknya tidak harus dokumen tertulis yang dibuat dengan aplikasi office.</p>
Instalasi Redmine di Tomcat2011-08-07T04:06:01+07:00https://software.endy.muhardin.com/aplikasi/redmine-jruby-tomca<p>Ada berbagai cara instalasi Redmine, diantaranya:</p>
<ul>
<li>
<p>Dijalankan langsung dari command prompt dengan Webrick</p>
</li>
<li>
<p>Dijalankan menggunakan Mongrel dan FastCGI</p>
</li>
<li>
<p>Dijalankan menggunakan Ruby Enterprise Edition dan Passenger</p>
</li>
<li>
<p>Dibuat menjadi war dan dideploy ke application server Java seperti Tomcat, Glassfish, dsb</p>
</li>
</ul>
<p>Pada artikel ini, kita akan mencoba cara terakhir, yaitu menggunakan Tomcat untuk menghosting Redmine.
Ini saya lakukan supaya semua tools manajemen proyek ArtiVisi bisa disatukan di satu Tomcat, sehingga memudahkan kegiatan maintenance.
Sebelum Redmine, Tomcat ArtiVisi juga menghosting :</p>
<ul>
<li>
<p><a href="http://nexus.sonatype.org/">Nexus</a></p>
</li>
<li>
<p><a href="http://jenkins-ci.org/">Jenkins</a></p>
</li>
</ul>
<p>Dan nantinya, kalau sudah ada waktu dan kesempatan, juga akan menghosting <a href="http://code.google.com/p/gerrit/">Gerrit</a></p>
<p>Mari kita mulai.</p>
<h2 id="instalasi-jruby">Instalasi JRuby</h2>
<p>Pertama, kita <a href="http://jruby.org/">Download JRuby</a>. Setelah itu, extract di folder yang diinginkan (contohnya /opt)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /opt
tar xzf ~/Downloads/jruby-bin-1.6.3.tar.gz
chown -R endy.endy /opt/jruby-1.6.3
ln -s jruby-1.6.3 jruby
</code></pre></div></div>
<p>Daftarkan jruby ke variabel PATH, supaya bisa diakses langsung dari command line.
Tulis baris berikut ini di dalam file ~/.bashrc</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export JRUBY_HOME=/opt/jruby
export PATH=$PATH:$JRUBY_HOME/bin
</code></pre></div></div>
<p>Terakhir, test instalasi JRuby</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jruby -v
jruby 1.6.3 (ruby-1.8.7-p330) (2011-07-07 965162f) (Java HotSpot(TM) Client VM 1.6.0_26) [linux-i386-java]
</code></pre></div></div>
<h2 id="instalasi-paket-gem">Instalasi Paket Gem</h2>
<p>Redmine membutuhkan beberapa library Ruby yang dipaket dalam format gem, yaitu :</p>
<ul>
<li>
<p>rack versi 1.1.1 : ini adalah library untuk web server</p>
</li>
<li>
<p>rails versi 2.3.11 (dibutuhkan karena kita akan menginstal Redmine dari Subversion, bukan dari distribusi)</p>
</li>
<li>
<p>jruby-openssl : supaya bisa melayani https</p>
</li>
<li>
<p>activerecord-jdbcmysql-adapter : library untuk koneksi database</p>
</li>
<li>
<p>warbler : packager supaya Redmine bisa dibuat jadi war dan dideploy ke Tomcat</p>
</li>
</ul>
<p>Mari kita install</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install rack -v=1.1.1
gem install rails -v=2.3.11
gem install jruby-openssl activerecord-jdbcmysql-adapter warbler
</code></pre></div></div>
<p>Semua paket sudah lengkap, mari kita lanjutkan ke langkah berikut.</p>
<h2 id="mengambil-redmine-dari-subversion-repository">Mengambil Redmine dari Subversion Repository</h2>
<p>Sebetulnya ada dua pilihan untuk mendapatkan Redmine, download versi rilis atau checkout langsung dari Subversion.
Saya lebih suka checkout langsung supaya nanti lebih gampang upgrade manakala rilis baru sudah terbit.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/Downloads
svn co http://redmine.rubyforge.org/svn/branches/1.2-stable redmine-1.2
</code></pre></div></div>
<p>Tunggu sejenak sampai proses checkout selesai. Setelah selesai, kita bisa langsung ke langkah selanjutnya.</p>
<h2 id="konfigurasi-database">Konfigurasi Database</h2>
<p>Masuk ke folder Redmine, lalu copy file config/database.yml.example ke database.yml, kemudian edit.
Saya menggunakan konfigurasi development sebagai berikut :</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">development</span><span class="pi">:</span>
<span class="na">adapter</span><span class="pi">:</span> <span class="s">jdbcmysql</span>
<span class="na">database</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">host</span><span class="pi">:</span> <span class="s">localhost</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">password</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">encoding</span><span class="pi">:</span> <span class="s">utf8</span>
</code></pre></div></div>
<p>Tentunya kita harus sediakan database dengan konfigurasi tersebut di MySQL. Login ke MySQL, kemudian buatlah database dan usernya.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql <span class="nt">-u</span> root <span class="nt">-p</span>
create database redmine character <span class="nb">set </span>utf8<span class="p">;</span>
create user <span class="s1">'redmine'</span>@<span class="s1">'localhost'</span> identified by <span class="s1">'redmine'</span><span class="p">;</span>
grant all privileges on redmine.<span class="k">*</span> to <span class="s1">'redmine'</span>@<span class="s1">'localhost'</span><span class="p">;</span>
</code></pre></div></div>
<p>Setelah databasenya selesai dibuat, selanjutnya kita akan melakukan inisialisasi.</p>
<h2 id="inisialisasi-redmine">Inisialisasi Redmine</h2>
<p>Pertama, kita inisialisasi dulu session store. Ini digunakan untuk menyimpan cookie dan session variabel.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/Downloads/redmine-1.2
rake generate_session_store
</code></pre></div></div>
<p>Setelah itu, inisialisasi skema database.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">RAILS_ENV</span><span class="o">=</span>development rake db:migrate
</code></pre></div></div>
<p>Isi data awal.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">RAILS_ENV</span><span class="o">=</span>development rake redmine:load_default_data
</code></pre></div></div>
<p>Setelah terisi, selanjutnya kita bisa test jalankan Redmine.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jruby script/server webrick -e development
</code></pre></div></div>
<p>Hasilnya bisa kita browse di http://localhost:3000
Kemudian kita bisa login dengan username admin dan password admin.</p>
<h2 id="konfigurasi-email">Konfigurasi Email</h2>
<p>Issue tracker yang baik harus bisa mengirim email, supaya dia bisa memberikan notifikasi pada saat ada issue baru ataupun perubahan terhadap issue yang ada.
Redmine versi 1.2 membutuhkan file konfigurasi yang bernama configuration.yml, berada di folder config. Berikut isi file configuration.yml untuk mengirim email ke Gmail.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># = Outgoing email settings</span>
<span class="na">development</span><span class="pi">:</span>
<span class="na">email_delivery</span><span class="pi">:</span>
<span class="na">delivery_method</span><span class="pi">:</span> <span class="s">:smtp</span>
<span class="na">smtp_settings</span><span class="pi">:</span>
<span class="na">tls</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">address</span><span class="pi">:</span> <span class="s2">"</span><span class="s">smtp.gmail.com"</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">587</span>
<span class="na">authentication</span><span class="pi">:</span> <span class="s">:plain</span>
<span class="na">user_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">nama.kita@gmail.com"</span>
<span class="na">password</span><span class="pi">:</span> <span class="s2">"</span><span class="s">passwordgmailkita"</span>
</code></pre></div></div>
<p>Selain itu, kita juga harus menginstal plugin action_mailer_optional_tls, seperti dijelaskan <a href="http://redmineblog.com/articles/setup-redmine-to-send-email-using-gmail/">di sini</a>.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jruby script/plugin <span class="nb">install
</span>git://github.com/collectiveidea/action_mailer_optional_tls.git
</code></pre></div></div>
<p>Coba restart Redmine, sesuaikan alamat email kita dengan cara klik link My Account di pojok kanan atas.
Di dalamnya ada informasi tentang email. Ganti dengan alamat email kita.
Kemudian pergi ke menu Administration > Settings > Email Notifications,
kemudian klik link Send a test email di pojok kanan bawah.
Tidak lama kemudian, seharusnya test email dari Redmine sudah masuk di mailbox kita.</p>
<p>Dengan demikian, Redmine sudah berhasil kita instal dan konfigurasi dengan baik.
Selanjutnya, kita akan paketkan supaya bisa dideploy di Tomcat.</p>
<h2 id="generate-war">Generate WAR</h2>
<p>Pertama, kita harus inisialisasi dulu konfigurasi warble.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>warble config
</code></pre></div></div>
<p>Dia akan menghasilkan file config/warble.rb. Mari kita edit sehingga menjadi seperti ini.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Warbler</span><span class="o">::</span><span class="no">Config</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="p">.</span><span class="nf">dirs</span> <span class="o">=</span> <span class="sx">%w(app config lib log vendor tmp extra files lang)</span>
<span class="n">config</span><span class="p">.</span><span class="nf">gems</span> <span class="o">+=</span> <span class="p">[</span><span class="s2">"activerecord-jdbcmysql-adapter"</span><span class="p">,</span> <span class="s2">"jruby-openssl"</span><span class="p">,</span> <span class="s2">"i18n"</span><span class="p">,</span> <span class="s2">"rack"</span><span class="p">]</span>
<span class="n">config</span><span class="p">.</span><span class="nf">webxml</span><span class="p">.</span><span class="nf">rails</span><span class="p">.</span><span class="nf">env</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'RAILS_ENV'</span><span class="p">]</span> <span class="o">||</span> <span class="s1">'development'</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Selanjutnya, kita tinggal menjalankan perintah warble untuk menghasilkan file war.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>warble
warning: application directory <span class="sb">`</span>lang<span class="s1">' does not exist or is not a directory; skipping
rm -f redmine-1.2.war
Creating redmine-1.2.war
</span></code></pre></div></div>
<p>File war yang dihasilkan tinggal kita deploy ke Tomcat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp redmine-1.2.war /opt/apache-tomcat-7.0.12/webapps/redmine.war
</code></pre></div></div>
<p>Jalankan Tomcat, dan Redmine bisa diakses di http://localhost:8080/redmine</p>
Instalasi Spket IDE2011-07-07T02:16:13+07:00https://software.endy.muhardin.com/aplikasi/instalasi-spket-ide<p>Beberapa minggu terakhir ini, saya mencari-cari cara terbaik untuk melakukan development dengan ExtJS.
Tentunya fitur utama yang kita inginkan adalah autocomplete,
sehingga tidak perlu bolak-balik membaca dokumentasi di websitenya.</p>
<p>Setelah berhari-hari mencari, akhirnya saya menemukan <a href="http://www.spket.com">Spket IDE</a>.
Di websitenya dinyatakan bahwa Spket sudah mendukung ExtJS versi 4, membuat saya tertarik untuk mencobanya.
Sayangnya, petunjuk instalasi sulit didapat, sehingga harus trial-and-error.</p>
<p>Di artikel ini, kita akan membahas petunjuk instalasi Spket IDE di Eclipse Indigo.</p>
<h2 id="klik-menu-install-new-software">Klik menu Install New Software</h2>
<p><a href="/images/uploads/2011/07/01.-Install-New-Software.png"><img src="/images/uploads/2011/07/01.-Install-New-Software.png" alt=" " /></a></p>
<h2 id="masukkan-update-site-spket-ide">Masukkan Update Site Spket IDE</h2>
<p>Tambahkan Update Site yang baru
<a href="/images/uploads/2011/07/02.-Add-Update-Site.png"><img src="/images/uploads/2011/07/02.-Add-Update-Site.png" alt=" " /></a></p>
<p>Update sitenya adalah http://www.spket.com/update/</p>
<p><a href="/images/uploads/2011/07/03.-Lokasi-Update-Site-Spket.png"><img src="/images/uploads/2011/07/03.-Lokasi-Update-Site-Spket.png" alt=" " /></a></p>
<h2 id="opsi-instalasi-spket">Opsi Instalasi Spket</h2>
<p><a href="/images/uploads/2011/07/04.-Centang-Semua-Opsi.png"><img src="/images/uploads/2011/07/04.-Centang-Semua-Opsi.png" alt=" " /></a></p>
<h2 id="klik-next">Klik Next</h2>
<p><a href="/images/uploads/2011/07/05.-Next-Terus.png"><img src="/images/uploads/2011/07/05.-Next-Terus.png" alt=" " /></a></p>
<p><a href="/images/uploads/2011/07/06.-Sit-Back-and-Relax.png"><img src="/images/uploads/2011/07/06.-Sit-Back-and-Relax.png" alt=" " /></a></p>
<p><a href="/images/uploads/2011/07/07.-Ada-Warning-Lanjut-Saja.png"><img src="/images/uploads/2011/07/07.-Ada-Warning-Lanjut-Saja.png" alt=" " /></a></p>
<p>Ada warning, klik saja Yes.</p>
<h2 id="download-support-extjs-4">Download Support ExtJS 4</h2>
<p>Agar bisa mengenali ExtJS 4, kita harus <a href="http://forums.spket.com/viewtopic.php?f=6&t=1866">mengunduh update terbaru dari forumnya</a>.
Entah apa alasannya, tiap ada update baru, versi jarnya tidak dinaikkan dan update sitenya tidak diperbarui.
Ini menyebabkan kita harus mengunduh file dari forum.</p>
<p>Ada dua file yang harus diunduh, yaitu jar
<a href="/images/uploads/2011/07/08.-Download-jar-terbaru.png"><img src="/images/uploads/2011/07/08.-Download-jar-terbaru.png" alt=" " /></a></p>
<p>dan jsb
<a href="/images/uploads/2011/07/09.-Dukungan-Ext-4.png"><img src="/images/uploads/2011/07/09.-Dukungan-Ext-4.png" alt=" " /></a>
Hasilnya, kita akan memiliki dua file.
<a href="/images/uploads/2011/07/13.-Hasil-donlod-dari-forum-spket.png"><img src="/images/uploads/2011/07/13.-Hasil-donlod-dari-forum-spket.png" alt=" " /></a></p>
<h2 id="patch-eclipse">Patch Eclipse</h2>
<p>File jar akan kita pasang di folder plugins di tempat Eclipse terinstal.<br />
<a href="/images/uploads/2011/07/11.-Folder-Plugin-Eclipse.png"><img src="/images/uploads/2011/07/11.-Folder-Plugin-Eclipse.png" alt=" " /></a></p>
<p>Ini akan menimpa file dengan nama sama.
<a href="/images/uploads/2011/07/14.-Replace-dengan-yang-baru.png"><img src="/images/uploads/2011/07/14.-Replace-dengan-yang-baru.png" alt=" " /></a></p>
<h2 id="patch-extjs">Patch ExtJS</h2>
<p>Sedangkan file jsb akan kita pasang di folder ExtJS 4.
<a href="/images/uploads/2011/07/15.-Masukkan-jsb-ke-folder-extjs.png"><img src="/images/uploads/2011/07/15.-Masukkan-jsb-ke-folder-extjs.png" alt=" " /></a></p>
<h2 id="edit-jsb">Edit jsb</h2>
<p>Sayangnya, file jsb ini juga masih ada bugnya. Dia salah menyebutkan nama file dalam folder pkgs.
Kita harus edit, ganti <code class="language-plaintext highlighter-rouge">all.js</code> menjadi <code class="language-plaintext highlighter-rouge">classes.js</code>.
<a href="/images/uploads/2011/07/16.-Edit-file-jsb-sesuai-nama-file.png"><img src="/images/uploads/2011/07/16.-Edit-file-jsb-sesuai-nama-file.png" alt=" " /></a></p>
<h2 id="konfigurasi-spket">Konfigurasi Spket</h2>
<p>Selanjutnya, kita masuk ke menu preferences untuk melakukan konfigurasi.
<a href="/images/uploads/2011/07/17.-Konfigurasi-Spket.png"><img src="/images/uploads/2011/07/17.-Konfigurasi-Spket.png" alt=" " /></a>
Masuk ke menu Spket - Javascript Profile
<a href="/images/uploads/2011/07/18.-Javascript-Profile.png"><img src="/images/uploads/2011/07/18.-Javascript-Profile.png" alt=" " /></a>
Tambah Profile baru, beri nama ExtJS
<a href="/images/uploads/2011/07/19.-Tambah-profile-baru.png"><img src="/images/uploads/2011/07/19.-Tambah-profile-baru.png" alt=" " /></a>
Di profile yang baru saja ditambahkan, Add Library dan pilih ExtJS
<a href="/images/uploads/2011/07/20.-Tambah-library-ExtJS.png"><img src="/images/uploads/2011/07/20.-Tambah-library-ExtJS.png" alt=" " /></a>
Setelah itu, Add File jsb yang sudah kita edit tadi.
<a href="/images/uploads/2011/07/21.-Add-File-JSB.png"><img src="/images/uploads/2011/07/21.-Add-File-JSB.png" alt=" " /></a>
Lalu, set profile ExtJS menjadi default
<a href="/images/uploads/2011/07/22.-Set-Profile-ExtJS-sebagai-default.png"><img src="/images/uploads/2011/07/22.-Set-Profile-ExtJS-sebagai-default.png" alt=" " /></a>
Kemudian, pergi ke menu General - Editors - File Associations. Pilih file js, dan jadikan Spket sebagai editornya.
<a href="/images/uploads/2011/07/23.-Set-file-associations.png"><img src="/images/uploads/2011/07/23.-Set-file-associations.png" alt=" " /></a>
Klik Ok, restart Eclipse.
<a href="/images/uploads/2011/07/10.-Restart-Eclipse.png"><img src="/images/uploads/2011/07/10.-Restart-Eclipse.png" alt=" " /></a></p>
<h2 id="code-completion">Code Completion</h2>
<p>Sekarang kita bisa melakukan code completion pada saat memberi titik di depan object.
<a href="/images/uploads/2011/07/24.-Autocomplete-method.png"><img src="/images/uploads/2011/07/24.-Autocomplete-method.png" alt=" " /></a>
Atau juga pada saat mengetik di dalam tanda kurung.
<a href="/images/uploads/2011/07/25.-Autocomplete-property.png"><img src="/images/uploads/2011/07/25.-Autocomplete-property.png" alt=" " /></a></p>
<p>Demikianlah cara instalasi Spket IDE di Eclipse.</p>
Membuat Screencast2011-06-28T01:03:23+07:00https://software.endy.muhardin.com/aplikasi/linux/membuat-screencast<p>Jaman sekarang sudah semakin maju. Fakir bandwidth semakin sedikit. Oleh karena itu, media komunikasi juga berubah, yang tadinya berbasis teks (hemat bandwidth) menjadi multimedia (rakus bandwidth).</p>
<p>Demi mengikuti perkembangan jaman, saya mengeksplorasi pembuatan tutorial dalam bentuk screencast. Ternyata hasilnya memuaskan. Dengan beberapa menit merekam screencast, informasi yang disampaikan sama dengan beberapa jam mengetik blog entry.</p>
<p>Artikel ini saya tulis untuk mendokumentasikan langkah-langkah membuat screencast, mulai dari merekam screencast, sampai mempublikasikannya di blog.</p>
<h2 id="merekam-video">Merekam video</h2>
<p>Di Ubuntu ada dua aplikasi yang saya coba, yaitu Xvidcap dan Record My Desktop. Dua-duanya sama fungsinya dan tidak ada perbedaan yang signifikan. Setelah mencoba keduanya, pendapat saya adalah Record My Desktop lebih mudah digunakan. Jadi, inilah aplikasi yang saya pilih.</p>
<p>Perlu diperhatikan kemampuan prosesor komputer Anda. Ini kaitannya dengan setting frame per second (fps). Bila fps melebihi kemampuan prosesor, video yang kita rekam akan terlihat lebih cepat dari sebenarnya. Di laptop saya, setting 15 fps akan menghasilkan video yang kira-kira 2x lebih cepat. Setelah trial and error, saya temukan bahwa 9 fps adalah setting yang tepat.</p>
<p>Perbedaan yang utama di antara kedua aplikasi ini adalah format outputnya. Record My Desktop mengeluarkan format ogv sedangkan Xvidcap mengeluarkan format mpeg. Perbedaan format ini nantinya akan mempengaruhi langkah pemrosesan selanjutnya.</p>
<h2 id="merekam-suara">Merekam suara</h2>
<p>Biasanya, saya merekam suara dalam proses yang terpisah, supaya tidak banyak ehm dan eee. Rekaman dibuat sambil menonton screencast yang sudah kita rekam. Setelah rekaman suara dibuat, bisa diedit dengan menggunakan aplikasi Audacity untuk menghilangkan noise, memotong bagian yang tidak penting, dan sebagainya.</p>
<h2 id="menggabungkan-video-dan-audio">Menggabungkan video dan audio</h2>
<p>Selanjutnya, kita menggunakan aplikasi Avidemux untuk menggabungkan file audio dan file video menjadi satu file. Dengan aplikasi ini kita juga bisa mengedit video untuk menghilangkan bagian-bagian yang tidak perlu ataupun menyambung beberapa video menjadi satu.</p>
<h2 id="mempersiapkan-format-video-untuk-web">Mempersiapkan format video untuk web</h2>
<p>Ada berbagai format video yang tersedia. Masing-masing format memiliki dukungan browser yang berbeda-beda. Daftar lengkapnya bisa dilihat <a href="http://diveintohtml5.info/video.html#what-works">di sini</a></p>
<p>Pada intinya, supaya bisa dilihat di berbagai browser, kita harus menyediakan file dengan format ogv, mp4, dan webm. Kita juga harus menyertakan poster dalam format jpg atau png supaya bisa ditampilkan dengan benar di browser.</p>
<p>Ada beberapa script yang bisa digunakan, misalnya <a href="https://github.com/kwiliarty/vfe-sh">ini</a> atau <a href="http://brettterpstra.com/automating-html5-video-encodes/">ini</a></p>
<p>Atau, kita juga bisa menjalankan commandnya satu persatu di command line. Berikut adalah command yang saya jalankan :</p>
<p>Konversi dari ogv menjadi mp4</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg <span class="nt">-vcodec</span> libx264 <span class="nt">-vpre</span> lossless_medium <span class="nt">-i</span> file-input.ogv file-output.mp4
</code></pre></div></div>
<p>Konversi dari ogv menjadi webm</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg <span class="nt">-pass</span> 1 <span class="nt">-passlogfile</span> file-input.ogv <span class="nt">-threads</span> 16 <span class="nt">-keyint_min</span> 0 <span class="nt">-g</span> 250 <span class="nt">-skip_threshold</span> 0 <span class="nt">-qmin</span> 1 <span class="nt">-qmax</span> 51 <span class="nt">-i</span> file-input.ogv <span class="nt">-vcodec</span> libvpx <span class="nt">-b</span> 614400 <span class="nt">-s</span> 640x480 <span class="nt">-aspect</span> 4:3 <span class="nt">-an</span> <span class="nt">-y</span> tmp.webm
<span class="nb">rm </span>tmp.webm
ffmpeg <span class="nt">-pass</span> 2 <span class="nt">-passlogfile</span> file-input.ogv <span class="nt">-threads</span> 16 <span class="nt">-keyint_min</span> 0 <span class="nt">-g</span> 250 <span class="nt">-skip_threshold</span> 0 <span class="nt">-qmin</span> 1 <span class="nt">-qmax</span> 51 <span class="nt">-i</span> file-input.ogv <span class="nt">-vcodec</span> libvpx <span class="nt">-b</span> 614400 <span class="nt">-s</span> 640x480 <span class="nt">-aspect</span> 4:3 <span class="nt">-an</span> <span class="nt">-y</span> file-output.webm
</code></pre></div></div>
<p>Command di atas mungkin berbeda bila file asli kita formatnya adalah mpeg seperti yang dihasilkan oleh XVidcap.</p>
<p>Membuat poster</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg <span class="nt">-r</span> 1 <span class="nt">-t</span> 1 <span class="nt">-vframes</span> 1 <span class="nt">-i</span> input-file.mp4 output-file.png
</code></pre></div></div>
<h2 id="upload">Upload</h2>
<p>Setelah semua file(ogv,mp4,png) terkumpul di satu folder, kita upload menggunakan rsync</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -avz /path/to/video/folder user@example.com:/home/user/public_html/videos
</code></pre></div></div>
<h2 id="tampilkan-di-blog">Tampilkan di blog</h2>
<p>Terakhir, kita ingin menayangkan video tersebut di blog kita. Karena saya menggunakan wordpress, saya pasang <a href="http://open.pages.kevinwiliarty.com/external-video-for-everybody/">plugin External Video for Everybody</a>. Plugin ini menampilkan video player di browser kita, supaya orang lain bisa langsung klik tombol play. Di belakang layar, plugin ini mendeteksi apakah browser kita mendukung HTML 5 atau tidak. Kalau iya, maka video akan ditampilkan dengan tag khusus <video>. Bila tidak, maka flash player akan digunakan. Flash player ini tidak disediakan oleh plugin ini. Kita perlu memilih dan mendownload sendiri dari sekian banyak flash player yang tersedia, contohnya <a href="http://www.longtailvideo.com/players/jw-flv-player/">JW Player</a></p>
<p>Sebagai penutup, bisa melihat <a href="http://linuxandfriends.com/2009/07/13/how-to-create-a-screencast-in-ubuntu-linux/">tutorial ini</a> yang saya gunakan sebagai titik awal eksplorasi saya.</p>
Tutorial ExtJS2011-06-21T20:15:31+07:00https://software.endy.muhardin.com/javascript/tutorial-extjs<p>Hari Senin kemarin, ArtiVisi mengadakan internal training mengenai ExtJS yang diikuti oleh programmer ArtiVisi dan sister company. Berikut adalah materi trainingnya.</p>
<h2 id="cara-setup-project-extjs">Cara setup project ExtJS</h2>
<p>TODO : fix video</p>
<h2 id="memahami-layout">Memahami Layout</h2>
<p>TODO : fix video</p>
<h2 id="membuat-menubar">Membuat Menubar</h2>
<p>TODO : fix video</p>
<h2 id="membuat-user-management-screen">Membuat User Management Screen</h2>
<p>TODO : fix video</p>
<h2 id="membuat-toolbar-crud">Membuat Toolbar CRUD</h2>
<p>TODO : fix video</p>
<p>Masih ada beberapa materi lanjutan yang belum sempat disampaikan, yaitu:</p>
<ul>
<li>
<p>Enable/Disable Form</p>
</li>
<li>
<p>Save isi form ke server</p>
</li>
<li>
<p>Query data dari server, tampilkan ke grid</p>
</li>
</ul>
<p>Kode program yang digunakan pada training ini dapat diikuti di <a href="https://github.com/endymuhardin/belajar-extjs">Github page saya</a></p>
Estimasi Proyek Software2011-05-11T06:37:12+07:00https://software.endy.muhardin.com/manajemen/estimasi-proyek-software<blockquote>
<p>Saya ingin membuat aplikasi akunting, berapa lama dan berapa biayanya?</p>
</blockquote>
<p>Demikian pertanyaan yang amat sering kita temui di profesi software developer.</p>
<p>Topik estimasi proyek merupakan topik yang sulit. Steve McConnell menyebutnya Black Art,
sehingga dia mengarang buku yang sangat bagus tentang topik ini,
judulnya Software Estimation, Demystifying the Black Art.</p>
<p>Menurut Steve, dalam membuat estimasi, ada 3 metode yang dilakukan, yaitu</p>
<ul>
<li>
<p>count</p>
</li>
<li>
<p>compute</p>
</li>
<li>
<p>judge</p>
</li>
</ul>
<p>Kita harus selalu berusaha count, karena ini yang paling akurat.
Kalo ditanya berapa tinggi pohon, paling baik adalah ambil meteran dan ukur pohonnya.
Dimana kita tidak bisa count, maka kita compute.
Contohnya, di sebelah pohon ada pagar dan di atas pagar ada tiang lampu.
Kita compute jumlah tinggi pagar dan tinggi tiang lampu, sehingga dapat tinggi pohon.
Hanya kalau tidak ada cara lain, baru kita judge.
Dikira2 tingginya berapa.</p>
<p>Dalam kaitannya dalam estimasi, idealnya yang kita estimasi hanyalah project size.
Metric lainnya akan kita compute dari project size ini.</p>
<h2 id="apa-itu-project-size">Apa itu project size?</h2>
<p>Project size merupakan ukuran suatu project.
Dengan project size, kita bisa membandingkan dua aplikasi yang berbeda,
misalnya aplikasi akunting dan aplikasi toko online.</p>
<p>Ada banyak metric untuk mengukur ukuran project. Metric yang paling lazim digunakan
adalah jumlah baris kode program, dalam bahasa Inggris disebut Source Lines of Code (SLOC)
atau Non Commenting Source Code (NCSS).</p>
<p>Walaupun SLOC sangat akurat dalam menentukan ukuran project, tapi jumlah baris kode sulit diperkirakan di awal project.
Oleh karena itu, beberapa orang kreatif lalu mengarang metode baru yang disebut Function Point Calculation.
Metode ini pada intinya adalah menghitung berapa screen input, screen output, jumlah tabel database, dan interaksinya dengan aplikasi lain.</p>
<p>Setelah kita mendapatkan estimasi ukuran project, kita akan menggunakannya untuk mengestimasi effort, durasi, dan cost.</p>
<p>Sebelum lebih jauh, mari kita jelaskan istilah-istilahnya.</p>
<p>Effort, adalah kerja real yang kita lakukan dalam menyelesaikan project. Satuannya adalah mandays atau manhour.
Misalnya suatu aplikasi diestimasi membutuhkan effort 10 mandays. Artinya aplikasi ini akan selesai bila dikerjakan 1 orang selama 10 hari terus menerus.
Atau 5 hari bila ada 2 pekerja. Effort tidak mempertimbangkan libur ataupun cuti.</p>
<p>Durasi (bahasa Inggrisnya schedule), adalah jangka waktu penyelesaian project. Ini biasanya dinyatakan dalam satuan hari kerja atau hari kalender.
Bila durasi project dinyatakan 10 hari kalender, maka bila dimulai tanggal 1, akan selesai tanggal 10.</p>
<p>Jadi, untuk mendapatkan durasi, kita harus punya asumsi berikut :</p>
<ul>
<li>
<p>berapa orang yang dipekerjakan</p>
</li>
<li>
<p>berapa hari libur</p>
</li>
<li>
<p>berapa alokasi waktu non-pekerjaan seperti meeting, presentasi, dsb</p>
</li>
</ul>
<p>Asumsi tersebut, digabungkan dengan estimasi effort, akan menghasilkan estimasi durasi.</p>
<p>Setelah durasi didapatkan, menghitung estimasi cost mudah saja. Kita membutuhkan matriks gaji per role.
Berapa gaji project manager, gaji programmer, dan role lain dalam setahun, termasuk THR, tunjangan kesehatan, benefit lain dan bonus.
Bagilah dengan hari kerja setahun sehingga didapatkan nilai gaji sehari.
Kemudian petakan penggunaan masing-masing role dalam schedule yang sudah kita estimasi, dan kita akan mendapat biaya personel.
Tambahkan dengan biaya lain-lain seperti transport, komunikasi, dsb untuk mendapatkan biaya total.
Masukkan juga faktor resiko project, misalnya kalau clientnya terkenal sulit ditagih, tentu perlu ada koefisien pengali.
Karena tagihan macet sama dengan kita memberi hutang ke client.</p>
<p>Nah, akhirnya biaya total sudah didapatkan, silahkan tambahkan profit yang diinginkan, buffer negosiasi, dan voila, dapatlah harga penawaran.</p>
<p>Selesai? Belum dong :D</p>
<p>Pembaca yang teliti tentu akan menemukan satu celah di penjelasan di atas. Bagaimana mengkonversi estimasi project size menjadi estimasi effort?
Nah disinilah bedanya perusahaan besar kaya pengalaman dengan startup mahasiswa baru lulus. Perusahaan dengan jam terbang tinggi biasanya punya data historis.
Dia punya data misalnya berapa mandays yang dibutuhkan programmer untuk membuat aplikasi dengan 3 tabel database.
Tentunya data ini harus dikumpulkan, diolah, dan diupdate agar bisa dijadikan pedoman. Ini sebabnya tidak semua perusahaan besar punya data ini.
Dengan bermodalkan data ini, perusahaan tinggal mengkonversi project size menjadi effort.
Setidaknya ada dua jenis data yang kita butuhkan, yaitu berapa effort yang dibutuhkan untuk menyelesaikan satu baris kode, dan bagaimana distribusi effort selama fase project. Maksudnya, bila project kita diestimasi 100 mandays, berapa mandays habis di analisa, coding, testing, implementasi, dan maintenance.
Selain itu, juga perlu ada matriks distribusi effort per role. Dengan adanya matriks ini, kita akan lebih mudah menkonversi effort menjadi durasi dan cost.</p>
<p>Startup mahasiswa, karena tidak punya data, maka cuma bisa tebak-tebak buah manggis. Atau kalo mau sedikit ilmiah, bisa mengikuti cara kami di ArtiVisi waktu baru mulai dulu. Kita membuat aplikasi kecil, kemudian datanya dikumpulkan dan dijadikan pedoman.</p>
<p>Jadi, kesimpulannya, begini metode estimasinya.</p>
<p><strong>Kebutuhan Data</strong></p>
<ol>
<li>
<p>Tabel konversi size ke mandays</p>
</li>
<li>
<p>Tabel gaji pegawai per role per hari</p>
</li>
<li>
<p>Tabel distribusi effort per fase</p>
</li>
<li>
<p>Tabel distribusi effort per role</p>
</li>
</ol>
<p>Flow Estimasi</p>
<ol>
<li>
<p>Estimasi Size</p>
</li>
<li>
<p>Dari size, gunakan tabel #1 untuk mendapatkan effort</p>
</li>
<li>
<p>Dari effort, gunakan tabel #3 untuk mendapatkan durasi</p>
</li>
<li>
<p>Dari effort, gunakan tabel #4 untuk mendapatkan effort per personel</p>
</li>
<li>
<p>Dari effort per personel, gunakan tabel #2 untuk menghitung biaya personel</p>
</li>
<li>
<p>Gunakan durasi untuk menghitung biaya lain-lain</p>
</li>
</ol>
<p>Demikian metodologi untuk melakukan estimasi project software. Membuat estimasi saja tidak cukup, kita juga harus bisa mempresentasikan dan mempertahankannya dari negosiasi pihak lain. Ini akan dibahas di lain kesempatan.</p>
Menghapus file secara permanen di Git2011-04-03T05:06:42+07:00https://software.endy.muhardin.com/aplikasi/menghapus-file-secara-permanen-di-git<p>Salah satu keuntungan menggunakan version control adalah dia akan menyimpan semua history perubahan. Dengan demikian, walaupun kita sudah menghapus satu file tertentu, sebetulnya file tersebut masih ada di repository dan bisa dimunculkan kembali kapan saja.</p>
<p>Hal ini menimbulkan beberapa konsekuensi, diantaranya</p>
<ul>
<li>
<p>Ukuran repository menjadi besar. Ini terutama sangat terasa di Git, karena pada waktu clone, kita akan mengambil keseluruhan perubahan dari pertama hingga terakhir. Berbeda dengan Subversion, dimana kita hanya mendapat perubahan terakhir saja.</p>
</li>
<li>
<p>File-file yang mengandung informasi rahasia –seperti misalnya password– tidak terhapus secara sempurna, sehingga bisa disalahgunakan orang lain.</p>
</li>
</ul>
<p>Untuk itu, kita perlu cara untuk menghilangkan file ini secara permanen.</p>
<p>Di Git, caranya adalah menggunakan perintah <code class="language-plaintext highlighter-rouge">git filter-branch</code> seperti <a href="http://help.github.com/removing-sensitive-data/">dijelaskan di sini</a>. Walaupun demikian, tutorial tersebut tidak menjelaskan bagaimana cara menghapus folder.</p>
<p>Di ArtiVisi, <a href="https://github.com/dadang">Dadang</a> dan <a href="https://github.com/donraakan">Doni</a> mengalami kejadian tersebut, dimana folder konfigurasi Eclipse (.project, .classpath, .settings) dan file hasil kompilasi Maven (target) ikut serta dicommit. Ini menyulitkan fakir bandwidth yang ingin melakukan clone, karena ukuran reponya menjadi besar sekali. Karena itu, file dan folder tersebut harus dihapus secara permanen.</p>
<p>Cara menghapusnya adalah sebagai berikut.</p>
<h2 id="pastikan-versi-repository-di-local-dan-di-remote-sudah-sama">Pastikan versi repository di local dan di remote sudah sama</h2>
<p>Ini bisa dilakukan dengan menggunakan perintah git pull dan git push. Selanjutnya, kita masuk ke folder kerja, dan memanggil perintah berikut.</p>
<p>Perintah di atas akan memodifikasi seluruh commit untuk menghilangkan file dan folder tersebut. Konsekuensinya, seluruh downline Anda akan terpaksa melakukan clone ulang, karena ini sama saja mengganti repository tersebut dengan repository baru. Akibatnya, commit, push, pull, dan merge tidak akan berjalan dengan baik.</p>
<p>Periksa kembali repository Git Anda setelah melakukan perintah di atas, pastikan semuanya baik-baik saja.
Begitu yakin, kita push ke remote.</p>
<p>`
git push origin master –force
`</p>
<p>Setelah melakukan perintah di atas, file yang terhapus itu masih ada di object database Git kita di local, sehingga ukuran reponya masih belum berkurang secara signifikan.
Karena sudah kita push ke remote, hapus saja repo local yang sekarang, dan lakukan clone ulang dari remote.</p>
<p>Demikian cara membersihkan repository dari file yang tidak sengaja dicommit. Silahkan mencoba.</p>
Database Transaction2011-02-14T04:14:22+07:00https://software.endy.muhardin.com/java/database-transaction<p>Artikel ini saya tulis berdasarkan diskusi tentang transaction di milis id-mysql. Awalnya sederhana, ada yang tanya begini,</p>
<blockquote>
<p>halo rekan2 dba & developer</p>
</blockquote>
<blockquote>
<p>mysql-innodb kan punya fasilitas transaction yang seperti oracle/postgres tuh.
mau nanya, dalam implementasi real di aplikasi,
contoh bussiness process/use case apa aja yang menggunakan transaction?
kemudian contoh kasus rollbacknya gimana?</p>
</blockquote>
<p>Tadinya saya kurang semangat menjawab, karena asumsi saya, ini pertanyaan mendasar, dan pastilah banyak yang bisa menjawab secara benar dan tidak menyesatkan. Tapi apa lacur, saya membaca pertanyaan lanjutan seperti ini.</p>
<blockquote>
<p>Ada yang pernah punya pengalaman pake software accounting tanpa feature
transaction?</p>
</blockquote>
<p>Dan jawabannya ternyata sangat mengerikan.</p>
<blockquote>
<p>yup, pernah.. 3 aplikasi sudah berjalan berbeda2 kasus accounting nya..
dan tidak menggunakan feature transaction…
skrng sedang garap accounting lainnya untuk perusahan dagang, dan
sudah direncakan tanpa feature transaction.</p>
</blockquote>
<blockquote>
<p>yg aplikasi 1 dr taun 2002, aplikasi 2 dr taun 2004, aplikasi 3 dr jan 2010.
oya, ada jg aplikasi lain di sekitar taun 2005-2009, beberapa masih
dipakai, beberapa tdk dipakai karena masalah internal mereka.
dan selama ini aplikasi yg telah dipakai masih ok2 saja pak.</p>
</blockquote>
<blockquote>
<p>menurut singkat saya, jika peng-handle php nya sudah cukup
menanggulangi masalah transaksi data, tidak harus menggunakan feature
transaction pada database nya.
karena pd umumnya yg sudah berjalan, kebutuhan inti ada pada
pencarian, input, edit, delete dengan kecepatan yg tinggi dan diakses
oleh beberapa user, dan juga optimize database, dengan begitu menurut
hemat saya, saya lebih condong menggunakan MyIsam yg tdk menggunakan
feature transaction yg sedikit memberatkan proses data.</p>
</blockquote>
<blockquote>
<p>oya, untuk case mengharuskan memakai feature transaction itu misalnya
pada kasus:
jika pada aplikasi tidak meng-handle apabila ada data transaksi yg
dihapus/update/input yg mengharuskan ada link data yg juga ikut
terupdate/terhapus/terinput</p>
</blockquote>
<blockquote>
<p>untuk yg sudah menggunakan feature transaction, silahkan saya juga
menunggu tanggapan dan pengalamannya.</p>
</blockquote>
<p>What the @#$!
Ini kalo meminjam istilah MUI, harus dibimbing untuk kembali ke jalan yang benar, tapi tidak boleh anarkis :D</p>
<p>Salah satu poin penting dalam database transaction adalah atomic, yaitu beberapa perintah dianggap sebagai satu kesatuan.
Kalau satu gagal, yang lain harus dibatalkan.</p>
<p>Ini adalah fundamental dari pemrograman dengan menggunakan database relasional.</p>
<p>Pada kasus apa perlu transaction?
Ya pada semua kasus yang perlu atomic.
Contohnya : header detail. Sekali insert, 1 header dan beberapa detail.
Kalo pada waktu insert detail gagal, ya headernya harus diundo, kalo ngga ada header yang gantung tanpa detail sehingga datanya juga jadi salah.</p>
<p>Sekarang balik saya tanya, aplikasi apa yang gak pake skema header detail?
Kecuali aplikasi prakarya tugas sekolah, aplikasi bisnis <strong>pasti</strong> pake header detail.</p>
<p>Itu masalah atomicity. Kemudian ada masalah isolation.
Isolation ini artinya, transaction yang belum dicommit, tidak akan bisa dibaca oleh session lain.
Contohnya gini, kita terima order 1000 item.
Tentunya butuh waktu untuk menginsert 1000 record, misalnya butuh waktu 2 detik.
Di dunia prosesor, 2 detik itu lama sekali, dan banyak hal bisa terjadi dalam rentang waktu tersebut.
Nah, akan terjadi musibah, kalo kita ternyata ada fitur untuk menghitung jumlah order, katakan saja querynya seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select sum(nilai) from t_order where tanggal = '2011-02-02'
</code></pre></div></div>
<p>yang berjalan di tengah-tengah proses insert tadi, misalnya pada waktu baru terinsert 53 order saja. Query hitung ini dijalankan oleh user lain. Suatu hal yang sangat umum terjadi, aplikasi diakses beberapa user berbarengan.</p>
<p>Query ini akan menghasilkan nilai yang salah, karena 1000 order itu belum tentu sukses diinsert.
Misalnya pada record ke 143 terjadi mati lampu, hardisk penuh, komputer hang, browser ketutup, laptop kesiram kopi, usernya menekan tombol cancel, validasi stok produk tidak cukup, atau whatever kejadian remeh-temeh yang umum terjadi dalam kehidupan sehari-hari, tentu akan terjadi kekacauan.
Karena tidak atomic, maka kita tidak tau sudah berapa record yang terinsert, sehingga menyulitkan proses recovery. Order mana yang harus diinsert ulang, dan order mana yang sudah masuk?
Karena tidak ada isolation, maka user yang menjalankan perhitungan order akan mendapat hasil yang tidak sahih kebenarannya.</p>
<p>Seandainya saja kita menggunakan transaction dengan benar, maka pada waktu terjadi sesuatu pada waktu proses insert tadi, maka posisi database akan dikembalikan ke posisi sebelum insert dilakukan. Karena posisi sebelum insert kita tahu dengan pasti, maka recovery gampang.
Insert ulang saja 1000 order tadi tanpa kecuali. Sederhana dan mudah.</p>
<p>Jadi kalo ada di sini yang bilang bikin aplikasi bisnis tanpa transaction, maka itu adalah nonsense.
Tidak peduli kalo sampe saat ini jalan lancar, maka itu hanyalah kebetulan belaka, dan kita tidak mau selamanya mengandalkan keberuntungan kan?
Kalau sampai saat ini berjalan lancar, ya mungkin aplikasinya cuma dipakai 1 concurrent user saja dan itupun jarang-jarang pake.</p>
<p>Nah, jadi transaction itu adalah fitur fundamental yang harus digunakan, sama seperti kalo kita keluar rumah ya harus pake celana.
Di daerah lain sana orang kemana2 cuma pake koteka, dan saya tidak mau berdebat dengan mereka urusan celana.
Jadi kalo masih ada yang bersikukuh bikin aplikasi bisnis gak pake transaction, ya silahkan, saya tidak mau berdebat urusan ini.
Percuma berdebat sama orang yang gak pake celana ;p</p>
<p>Selanjutnya, sebetulnya apa benar transaction itu memberatkan aplikasi?
Hmm … ini sebetulnya hanyalah mitos belaka.
Yang mau mendebat silahkan sajikan benchmark antara non-transactional dan transactional.
Kalo selisih performance cuma 100%, artinya kalo non-transactional cuma 2 kali lebih lemot, saya mendingan upgrade hardware daripada mengorbankan data integrity untuk gain performance yang tidak seberapa ini.</p>
<p>Jadi, apa kita tidak boleh pakai MyISAM ?
Tentu ada waktu dan tempatnya.
Data2 read only seperti misalnya tabel kategori, master produk, bolehlah pake MyISAM.
Tapi kalo sudah data header detail, ya harus InnoDB dan harus menggunakan transaction supaya atomic.</p>
<p>Setelah kita menggunakan InnoDB, sebetulnya kita tidak bisa non-transactional.
Kalo kita tidak begin dan commit secara explisit, sebenarnya untuk tiap SQL statement, itu dianggap satu transaction.
Sehingga SQL seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>update table harga set nilai = nilai + 1000;
</code></pre></div></div>
<p>Sebetulnya akan dijalankan seperti ini ;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>begin;
update table harga set nilai = nilai + 1000;
commit;
</code></pre></div></div>
<p>Ini namanya fitur autocommit. Di MySQL defaultnya dienable.</p>
<p>Dengan adanya autocommit ini, justru kita akan lebih lemot kalo tidak menggunakan transaction secara benar.
Contoh, insert 100 data produk.
Kalo tanpa begin dan commit explisit, berarti ada 100 begin dan ada 100 commit, artinya 100 kali menjalankan transaction.
Akan lebih efisien kalo kita lakukan explisit, seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>begin;
insert into table produk (kode) values ('P-001');
... ulangi 99 kali lagi ..
commit;
</code></pre></div></div>
<p>Cara di atas hanya akan membutuhkan satu transaction saja.
Jauh lebih efisien.</p>
<p>Baiklah, ada beberapa pesan moral di artikel ini</p>
<ol>
<li>
<p>Header detail harus dioperasikan secara atomic</p>
</li>
<li>
<p>Operasi yang belum selesai, tidak boleh dilihat session lain, sehingga untuk aplikasi multiuser, pasti butuh isolation</p>
</li>
<li>
<p>Karena aplikasi bisnis umumnya multiuser, dan pasti punya skema header-detail, maka <strong>pasti harus</strong>menggunakan transaction</p>
</li>
<li>
<p>Masalah performance di transaction umumnya mitos belaka, dan walaupun ada, tidak sebanding dengan mengabaikan integritas data</p>
</li>
<li>
<p>Jangan lupa pakai celana kalau keluar rumah</p>
</li>
</ol>
<p>Pembaca setia blog saya tentu paham bahwa biasanya saya memberikan anjuran dengan kata-kata sebaiknya, tergantung situasi, dan istilah-istilah yang relatif. Tapi di artikel ini, banyak kata-kata pasti, harus, dan sejenisnya. Ini karena masalah transaction ini berkaitan dengan integritas data. Aplikasi yang kita buat haruslah bisa dipercaya untuk menghasilkan perhitungan yang benar. Tanpa menjaga integritas data dengan transaction, mustahil perhitungan yang benar bisa didapatkan.</p>
<p>Lebih lanjut tentang masalah-masalah yang bisa terjadi, bisa lihat <a href="http://en.wikipedia.org/wiki/Database_transaction">di Wikipedia</a>.</p>
Pemakaian Git sehari-hari2011-02-08T16:02:29+07:00https://software.endy.muhardin.com/aplikasi/pemakaian-git-sehari-hari<p>Pada artikel ini, kita akan mengulas secara singkat perintah-perintah yang sering kita gunakan dalam Git. Tapi sebelum mulai, perlu kita pahami beberapa istilah sebagai berikut:</p>
<ul>
<li>
<p>diff : perbedaan antara satu file dengan file lain
biasanya diff dilakukan terhadap satu file yang sudah berubah isinya</p>
</li>
<li>
<p>changeset : kumpulan diff</p>
</li>
<li>
<p>working folder : folder kerja kita, berisi file yang (mungkin) sudah berubah sejak commit terakhir</p>
</li>
<li>
<p>staging : tempat persiapan changeset yang akan dicommit</p>
</li>
<li>
<p>commit : snapshot dari posisi folder dan file pada waktu tertentu</p>
</li>
<li>
<p>tip : commit paling ujung</p>
</li>
<li>
<p>head : nama lain tip</p>
</li>
<li>
<p>branch : head yang diberi nama</p>
</li>
<li>
<p>HEAD : head yang sedang aktif</p>
</li>
<li>
<p>merge : menggabungkan lebih dari satu commit</p>
</li>
</ul>
<h2 id="membuat-repository">Membuat Repository</h2>
<p>Untuk bisa mulai bekerja, kita harus memiliki repository dulu. Ada dua kemungkinan, kita membuat repository baru, atau kita membuat clone dari repository yang sudah ada.</p>
<table>
<thead>
<tr>
<th>Keterangan</th>
<th>Perintah</th>
</tr>
</thead>
<tbody>
<tr>
<td>membuat repository baru</td>
<td>git init</td>
</tr>
<tr>
<td>membuat repository baru di folder project-baru</td>
<td>git init project-baru</td>
</tr>
<tr>
<td>membuat repository untuk dishare</td>
<td>git init –bare project-baru</td>
</tr>
<tr>
<td>copy repository lain</td>
<td>git clone repo-url</td>
</tr>
</tbody>
</table>
<p>pilihan format URL</p>
<p>file:///path/ke/repo : clone dari folder lokal</p>
<p>/path/ke/repo : clone dari folder lokal, menggunakan hard link</p>
<p>http://server/path/ke/repo : clone melalui protokol http</p>
<p>username@server:path/ke/repo : clone melalui protokol ssh</p>
<h2 id="bekerja-dengan-git">Bekerja dengan Git</h2>
<p>Berikut ini adalah perintah yang dilakukan selama sesi coding.</p>
<p>Keterangan | Perintah
————————————————————————————————————————|—————————————–
Menambah file baru | git add namafile
Menghapus file | git rm namafile
Memasukkan perubahan di satu file ke staging area | git add namafile
memasukkan semua perubahan | git add .
memilih potongan kode yang akan dimasukkan | git add -p
memasukkan perubahan ke staging menggunakan menu | git add -i
melihat status perubahan file, mana yang masih di working dan mana yang sudah di staging | git status
mengeluarkan perubahan dari staging area | git reset – namafile
melihat perubahan yang belum dimasukkan ke staging area | git diff
melihat perubahan yang akan dicommit (sudah ada di staging area) | git diff –staged
melihat perubahan antara working folder dan commit terakhir | git diff HEAD
melihat file mana saja yang berubah | git diff –name-status abc123..def456
melakukan commit, editor akan diaktifkan untuk mengisi keterangan | git commit
melakukan commit, langsung mengisi keterangan | git commit -m “langsung isi keterangan di sini”
commit langsung semua perubahan, tanpa melalui staging | git commit -a
melihat commit history | git log
log lima commit terakhir | git log -5
log hanya menampilkan summary | git log –oneline
tampilkan commit summary dari semua branch dengan graph hubungan antar commit | git log –oneline –all –graph
membuat commit baru yang berkebalikan dengan (undo) commit terakhir | git revert HEAD
undo 2 commit terakhir | git revert HEAD~2
memindahkan HEAD ke commit-id yang diminta, staging disamakan dengan HEAD, working tetap seperti semula. <br />
Ini adalah opsi defaultnya reset | git reset –mixed
memindahkan HEAD ke commit-id yang diminta, isi working dan staging disamakan dengan commit-id tersebut | git reset –hard commit-id
memindahkan HEAD ke commit-id yang diminta, staging dan working tidak disentuh. Tidak mengubah output git status | git reset –soft
membuat working dan staging sama dengan HEAD | git reset –hard</p>
<h2 id="bekerja-paralel-menggunakan-branch">Bekerja paralel menggunakan branch</h2>
<p>Branch memungkinkan kita bekerja secara paralel, misalnya ada tim yang menambah fitur, dan ada tim yang melakukan bug fix.</p>
<table>
<thead>
<tr>
<th>Keterangan</th>
<th>Perintah</th>
</tr>
</thead>
<tbody>
<tr>
<td>membuat branch baru</td>
<td>git branch namabranch</td>
</tr>
<tr>
<td>pindah ke branch tersebut</td>
<td>git checkout namabranch</td>
</tr>
<tr>
<td>bikin branch sambil pindah</td>
<td>git checkout -b namabranch</td>
</tr>
<tr>
<td>membuat tracking branch untuk branch bugfix di origin</td>
<td>git checkout –track origin/bugfix</td>
</tr>
<tr>
<td>membuat tracking branch dengan nama berbeda dengan remote</td>
<td>git checkout -b myfix origin/bugfix</td>
</tr>
<tr>
<td>membandingkan branch satu dengan lainnya</td>
<td>git diff master..fitur-xx</td>
</tr>
<tr>
<td>membandingkan branch dengan titik awal branch tersebut</td>
<td>git diff master…fitur-xx</td>
</tr>
<tr>
<td>menggabungkan branch satu dengan lainnya</td>
<td>git checkout branch-tujuan</td>
</tr>
<tr>
<td> </td>
<td>git merge branch-yang-mau-diambil</td>
</tr>
<tr>
<td>Mengedit konflik :</td>
<td> </td>
</tr>
<tr>
<td>- edit konfliknya</td>
<td>git add namafile-yang-konflik</td>
</tr>
<tr>
<td>- remove markernya</td>
<td>git commit -m “merge fitur-xxx ke master”</td>
</tr>
<tr>
<td>membatalkan merge yang konflik</td>
<td>git reset –hard</td>
</tr>
</tbody>
</table>
<h2 id="bekerja-dengan-remote">Bekerja dengan remote</h2>
<p>Interaksi dengan remote repository</p>
<table>
<thead>
<tr>
<th>Keterangan</th>
<th>Perintah</th>
</tr>
</thead>
<tbody>
<tr>
<td>mendaftarkan remote repository</td>
<td>git remote add namaremote url</td>
</tr>
<tr>
<td>melihat daftar remote repository</td>
<td>git remote -v</td>
</tr>
<tr>
<td>menghapus remote repository</td>
<td>git remote rm namaremote</td>
</tr>
<tr>
<td>mengambil perubahan di remote</td>
<td>git remote update</td>
</tr>
<tr>
<td>mengambil perubahan di satu remote saja</td>
<td>git remote update namaremote</td>
</tr>
<tr>
<td>mengambil perubahan di remote, hapus branch di lokal yang sudah tidak ada di remote</td>
<td>git remote update –prune</td>
</tr>
<tr>
<td>mengambil perubahan sesuai refspec yang sudah dikonfigurasi</td>
<td>git fetch namaremote</td>
</tr>
<tr>
<td>mengambil perubahan kemudian dimerge ke branch lokal yang sesuai</td>
<td>pull = fetch + merge</td>
</tr>
<tr>
<td> </td>
<td>git pull namaremote</td>
</tr>
<tr>
<td>mengirim perubahan di lokal ke remote</td>
<td>git push nama-remote nama-branch-lokal:nama-branch-remote</td>
</tr>
<tr>
<td>mengirim perubahan di lokal ke remote, semua branch yang namanya bersesuaian akan dikirim</td>
<td>git push nama-remote</td>
</tr>
<tr>
<td>mengirim perubahan di branch lokal yang sedang aktif ke branch di remote dengan nama yang sama</td>
<td>git push nama-remote HEAD</td>
</tr>
<tr>
<td>menghapus branch di remote</td>
<td>git push nama-remote :nama-branch-remote</td>
</tr>
</tbody>
</table>
<p>Demikianlah perintah-perintah Git yang kita gunakan sehari-hari. Melengkapi daftar perintah di atas, diagram berikut dapat membantu pemahaman kita tentang konsep dan operasi di Git.</p>
<p><a href="/images/uploads/2011/01/git-300x284.png"><img src="/images/uploads/2011/01/git-300x284.png" alt=" " /></a></p>
Project Setup dengan Gradle2011-02-01T20:21:35+07:00https://software.endy.muhardin.com/java/project-setup-dengan-gradle<p>Project Setup dengan menggunakan Gradle dan Git</p>
<p>Hal pertama yang kita lakukan sebelum mulai bekerja tentunya adalah menyiapkan meja kerja dan peralatannya. Sama juga dengan mulai membuat aplikasi. Kita harus menyiapkan struktur folder, library dan framework, dan mengatur semuanya agar siap dikerjakan di meja kita, dalam hal ini IDE.</p>
<p>Di ArtiVisi, biasanya ini dikerjakan oleh programmer senior, yaitu <a href="http://martinusadyh.web.id/">Martinus</a> atau saya sendiri. Kegiatan project setup ini tidak terlalu tinggi frekuensinya, karena biasanya coding project yang existing jauh lebih sering daripada memulai project baru.</p>
<p>Yang jarang dikerjakan biasanya cepat dilupakan. Inilah alasan utama saya menulis posting kali ini, sebagai pengingat buat diri sendiri. Selain itu, mudah-mudahan ada manfaatnya juga untuk para pembaca sekalian.</p>
<p>Sebagai gambaran, tipikal aplikasi di ArtiVisi menggunakan <a href="http://endy.artivisi.com/blog/java/development-stack-2011/">stack standar 2011</a>. Jadi, project setup ini akan dibuat mengikuti stack standar tersebut.</p>
<p>Pertama kali, kita buat dulu projectnya. Satu aplikasi biasanya kita pecah menjadi beberapa komponen, yaitu :</p>
<ul>
<li>
<p>Domain Model dan Service API : ini kita pisahkan untuk memudahkan distribusi ke aplikasi client. Perhatikan bahwa yang saya maksud client di sini bukanlah customer pembeli aplikasi, melainkan aplikasi di sisi hilir misalnya user interface yang dibuat dengan Swing. Di sisi client, tidak perlu ada detail implementasi. Cukup class-class domain seperti Produk, Kategori, dsb. Juga kita sediakan service interface, yaitu method yang bisa digunakan untuk menjalankan proses bisnis.</p>
</li>
<li>
<p>Implementasi Service : ini adalah implementasi dari service interface di atas. Implementasi biasanya hanya ada di sisi server. Jadi, jar yang dihasilkan project ini tidak kita distribusikan ke client</p>
</li>
<li>
<p>Konfigurasi : file konfigurasi seperti jdbc.properties, logback-test.xml, smtp.properties, dan setting-setting lain kita juga pisahkan ke project sendiri. Ini tujuannya untuk memudahkan deployment. Seperti kita tahu, biasanya ada beberapa environment seperti development di laptop programmer, testing server, dan production server. Dengan memisahkan konfigurasi, kita bisa menghindari mendeploy konfigurasi development ke server production. Yang perlu diperhatikan di sini, hibernate.cfg.xml dan applicationContext.xml bukanlah file konfigurasi. Itu adalah file aplikasi, walaupun bentuknya xml dan tidak perlu dikompilasi.</p>
</li>
<li>
<p>User Interface : kalau aplikasi desktop, ini hanya satu project saja. Atau mungkin dua dengan konfigurasinya. Tapi untuk web, biasanya kita pecah dua juga. Yang satu berisi source code java, satu lagi berisi aplikasi web. Dengan demikian, bila ada perubahan di controller, kita cukup deploy 1 jar, tidak perlu upload 1 war.</p>
</li>
</ul>
<p>Sebagai ketentuan lain, biasanya nama package selalu kita awali dengan com.artivisi, dan struktur folder mengikuti standar Maven.</p>
<p>Mari kita mulai, berikut rangkaian perintah di linux untuk membuat struktur awal project.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p project-contoh/com.artivisi.contoh.{config,domain,service.impl,ui.springmvc,ui.web}/src/{main,test}/{java,resources}
mkdir -p project-contoh/com.artivisi.contoh.ui.web/src/main/webapp/WEB-INF
</code></pre></div></div>
<p>Outputnya bisa kita lihat sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find .
.
./com.artivisi.contoh.service.impl
./com.artivisi.contoh.service.impl/src
./com.artivisi.contoh.service.impl/src/test
./com.artivisi.contoh.service.impl/src/test/java
./com.artivisi.contoh.service.impl/src/test/resources
./com.artivisi.contoh.service.impl/src/main
./com.artivisi.contoh.service.impl/src/main/java
./com.artivisi.contoh.service.impl/src/main/resources
./com.artivisi.contoh.domain
./com.artivisi.contoh.domain/src
./com.artivisi.contoh.domain/src/test
./com.artivisi.contoh.domain/src/test/java
./com.artivisi.contoh.domain/src/test/resources
./com.artivisi.contoh.domain/src/main
./com.artivisi.contoh.domain/src/main/java
./com.artivisi.contoh.domain/src/main/resources
./com.artivisi.contoh.ui.springmvc
./com.artivisi.contoh.ui.springmvc/src
./com.artivisi.contoh.ui.springmvc/src/test
./com.artivisi.contoh.ui.springmvc/src/test/java
./com.artivisi.contoh.ui.springmvc/src/test/resources
./com.artivisi.contoh.ui.springmvc/src/main
./com.artivisi.contoh.ui.springmvc/src/main/java
./com.artivisi.contoh.ui.springmvc/src/main/resources
./com.artivisi.contoh.config
./com.artivisi.contoh.config/src
./com.artivisi.contoh.config/src/test
./com.artivisi.contoh.config/src/test/java
./com.artivisi.contoh.config/src/test/resources
./com.artivisi.contoh.config/src/main
./com.artivisi.contoh.config/src/main/java
./com.artivisi.contoh.config/src/main/resources
./com.artivisi.contoh.ui.web
./com.artivisi.contoh.ui.web/src
./com.artivisi.contoh.ui.web/src/test
./com.artivisi.contoh.ui.web/src/test/java
./com.artivisi.contoh.ui.web/src/test/resources
./com.artivisi.contoh.ui.web/src/main
./com.artivisi.contoh.ui.web/src/main/java
./com.artivisi.contoh.ui.web/src/main/webapp/WEB-INF
./com.artivisi.contoh.ui.web/src/main/resources
</code></pre></div></div>
<p>Berikutnya, kita lengkapi dengan dependensi jar. Di ArtiVisi, kita menggunakan Gradle.
Gradle meminta kita untuk mendaftarkan project yang terlibat dalam settings.gradle</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>include "com.artivisi.contoh.config"
include "com.artivisi.contoh.domain"
include "com.artivisi.contoh.service.impl"
include "com.artivisi.contoh.ui.springmvc"
include "com.artivisi.contoh.ui.web"
</code></pre></div></div>
<p>Dan ini build file Gradle.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
springVersion = "3.0.5.RELEASE"
springSecurityVersion = "3.0.5.RELEASE"
slf4jVersion = "1.6.1"
logbackVersion = "0.9.27"
jodaTimeVersion = "1.6.2"
sourceCompatibility = 1.6
subprojects {
apply plugin: 'java'
apply plugin: 'eclipse'
configurations {
all*.exclude group: "commons-logging", module: "commons-logging"
}
repositories {
mavenCentral()
}
dependencies {
compile "org.slf4j:jcl-over-slf4j:$slf4jVersion",
"org.slf4j:jul-to-slf4j:$slf4jVersion"
runtime "joda-time:joda-time:$jodaTimeVersion"
runtime "ch.qos.logback:logback-classic:$logbackVersion"
testCompile 'junit:junit:4.7'
}
group = 'com.artivisi.contoh'
version = '1.0-SNAPSHOT'
sourceCompatibility = 1.6
task wrapper(type: Wrapper) {
gradleVersion = '0.9.1'
jarFile = 'wrapper/wrapper.jar'
}
}
project('com.artivisi.contoh.domain') {
dependencies {
compile "org.hibernate:hibernate-entitymanager:3.4.0.GA"
compile "org.springframework:spring-tx:$springVersion",
"org.springframework:spring-orm:$springVersion",
"org.springframework:spring-jdbc:$springVersion"
}
}
project('com.artivisi.contoh.service.impl') {
dependencies {
compile project(':com.artivisi.contoh.domain')
compile "org.hibernate:hibernate-entitymanager:3.4.0.GA"
compile "org.springframework:spring-tx:$springVersion",
"org.springframework:spring-orm:$springVersion",
"org.springframework:spring-jdbc:$springVersion"
}
}
project('com.artivisi.contoh.ui.springmvc') {
dependencies {
compile project(':com.artivisi.contoh.service.impl')
compile "org.springframework:spring-webmvc:$springVersion",
"org.springframework:spring-aop:$springVersion"
compile "org.springframework.security:spring-security-web:$springSecurityVersion",
"org.springframework.security:spring-security-config:$springSecurityVersion"
compile "javax.validation:validation-api:1.0.0.GA",
"org.hibernate:hibernate-validator:4.0.2.GA"
}
}
project('com.artivisi.contoh.ui.web') {
apply plugin: 'war'
apply plugin: 'jetty'
dependencies {
compile project(':com.artivisi.contoh.ui.springmvc')
runtime project(':com.artivisi.contoh.config')
runtime "javax.servlet:jstl:1.1.2",
"taglibs:standard:1.1.2",
"opensymphony:sitemesh:2.4.2"
providedCompile "javax.servlet:servlet-api:2.5"
}
}
</code></pre></div></div>
<p>Build file ini sudah mendeskripsikan semua sub-projectnya. Sebetulnya kita bisa membuat buildfile di masing-masing project, tapi saya lebih suka terpusat seperti ini supaya terlihat keterkaitan antar project.</p>
<p>Karena saya menggunakan Eclipse, saya menambahkan metadata supaya projectnya bisa dibuka di Eclipse. Ini bisa kita lakukan dengan cara menjalankan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gradle eclipse
</code></pre></div></div>
<p>dalam masing-masing folder project. Tapi karena terlalu malas, saya gunakan satu baris perintah ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for d in */; do cd "$d"; gradle eclipse; cd ..; done
</code></pre></div></div>
<p>Untung saja pakai linux, jadi bisa coding di command prompt :D</p>
<p>Selanjutnya, kita bisa test dengan melakukan build di project paling hilir, yaitu ui.web</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd com.artivisi.contoh.ui.web
gradle war
</code></pre></div></div>
<p>Hasilnya ada di folder build/libs
Kita cek apakah semua dependensi sudah terpenuhi dengan perintah berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jar tvf build/libs/com.artivisi.contoh.ui.web-1.0-SNAPSHOT.war
</code></pre></div></div>
<p>Ini juga bisa langsung dijalankan dengan plugin Jetty yang ada dalam Gradle.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd com.artivisi.contoh.ui.web
gradle jetty
</code></pre></div></div>
<p>Outputnya bisa kita lihat di browser, dengan port 8080.
<a href="/images/uploads/2011/01/jetty-run-300x216.png"><img src="/images/uploads/2011/01/jetty-run-300x216.png" alt=" " /></a></p>
<p>Di situ ada link menuju aplikasi kita. Silahkan diklik.
<a href="/images/uploads/2011/01/klik-context-path-300x214.png"><img src="/images/uploads/2011/01/klik-context-path-300x214.png" alt=" " /></a></p>
<p>Folder WEB-INF masih terlihat, karena kita belum membuat web.xml. Berikut isi web.xml, masukkan dalam folder com.artivisi.contoh.ui.web/src/main/webapp/WEB-INF</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><web-app</span> <span class="na">version=</span><span class="s">"2.5"</span> <span class="na">xmlns=</span><span class="s">"http://java.sun.com/xml/ns/javaee"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"</span><span class="nt">></span>
<span class="c"><!-- Reads request input using UTF-8 encoding --></span>
<span class="nt"><filter></span>
<span class="nt"><filter-name></span>characterEncodingFilter<span class="nt"></filter-name></span>
<span class="nt"><filter-class></span>org.springframework.web.filter.CharacterEncodingFilter<span class="nt"></filter-class></span>
<span class="nt"><init-param></span>
<span class="nt"><param-name></span>encoding<span class="nt"></param-name></span>
<span class="nt"><param-value></span>UTF-8<span class="nt"></param-value></span>
<span class="nt"></init-param></span>
<span class="nt"><init-param></span>
<span class="nt"><param-name></span>forceEncoding<span class="nt"></param-name></span>
<span class="nt"><param-value></span>true<span class="nt"></param-value></span>
<span class="nt"></init-param></span>
<span class="nt"></filter></span>
<span class="nt"><filter-mapping></span>
<span class="nt"><filter-name></span>characterEncodingFilter<span class="nt"></filter-name></span>
<span class="nt"><url-pattern></span>/*<span class="nt"></url-pattern></span>
<span class="nt"></filter-mapping></span>
<span class="c"><!-- Handles all requests into the application --></span>
<span class="nt"><servlet></span>
<span class="nt"><servlet-name></span>Spring MVC Dispatcher Servlet<span class="nt"></servlet-name></span>
<span class="nt"><servlet-class></span>org.springframework.web.servlet.DispatcherServlet<span class="nt"></servlet-class></span>
<span class="nt"><init-param></span>
<span class="nt"><param-name></span>contextConfigLocation<span class="nt"></param-name></span>
<span class="nt"><param-value></span>
/WEB-INF/springmvc-context.xml
<span class="nt"></param-value></span>
<span class="nt"></init-param></span>
<span class="nt"><load-on-startup></span>1<span class="nt"></load-on-startup></span>
<span class="nt"></servlet></span>
<span class="nt"><servlet-mapping></span>
<span class="nt"><servlet-name></span>Spring MVC Dispatcher Servlet<span class="nt"></servlet-name></span>
<span class="nt"><url-pattern></span>/<span class="nt"></url-pattern></span>
<span class="nt"></servlet-mapping></span>
<span class="nt"></web-app></span>
</code></pre></div></div>
<p>Sekalian saja kita konfigurasi Spring MVC. Pasang file springmvc-context.xml ini di sebelahnya web.xml</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xmlns:context=</span><span class="s">"http://www.springframework.org/schema/context"</span>
<span class="na">xmlns:mvc=</span><span class="s">"http://www.springframework.org/schema/mvc"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"</span><span class="nt">></span>
<span class="c"><!-- Scans the classpath of this application for @Components to deploy as beans --></span>
<span class="nt"><context:component-scan</span> <span class="na">base-package=</span><span class="s">"com.artivisi.contoh.ui.web"</span> <span class="nt">/></span>
<span class="c"><!-- Configures the @Controller programming model --></span>
<span class="nt"><mvc:annotation-driven</span> <span class="nt">/></span>
<span class="c"><!-- mengganti default servletnya Tomcat dan Jetty --></span>
<span class="c"><!-- ini diperlukan kalau kita mapping DispatcherServlet ke / --></span>
<span class="c"><!-- sehingga tetap bisa mengakses folder selain WEB-INF, misalnya img, css, js --></span>
<span class="nt"><mvc:default-servlet-handler/></span>
<span class="c"><!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory --></span>
<span class="nt"><mvc:resources</span> <span class="na">mapping=</span><span class="s">"/resources/**"</span> <span class="na">location=</span><span class="s">"/resources/"</span> <span class="nt">/></span>
<span class="c"><!-- Application Message Bundle --></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"messageSource"</span> <span class="na">class=</span><span class="s">"org.springframework.context.support.ReloadableResourceBundleMessageSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"basename"</span> <span class="na">value=</span><span class="s">"/WEB-INF/messages/messages"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"cacheSeconds"</span> <span class="na">value=</span><span class="s">"0"</span> <span class="nt">/></span>
<span class="nt"></bean></span>
<span class="c"><!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory --></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"org.springframework.web.servlet.view.InternalResourceViewResolver"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"prefix"</span> <span class="na">value=</span><span class="s">"/WEB-INF/templates/jsp/"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"suffix"</span> <span class="na">value=</span><span class="s">".jsp"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="c"><!-- Forwards requests to the "/" resource to the "hello" view --></span>
<span class="nt"><mvc:view-controller</span> <span class="na">path=</span><span class="s">"/"</span> <span class="na">view-name=</span><span class="s">"hello"</span><span class="nt">/></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>Kita cek juga apakah projectnya sudah bisa dibuka di Eclipse. Mari kita import.</p>
<p>Pertama, arahkan workspace ke folder project-contoh.
<a href="/images/uploads/2011/01/01-pilih-workspace-300x154.png"><img src="/images/uploads/2011/01/01-pilih-workspace-300x154.png" alt=" " /></a></p>
<p>Setelah Eclipse terbuka, kita pilih menu Import Project, untuk membuka 4 project yang tadi sudah kita buat.
<a href="/images/uploads/2011/01/02-import-existing-300x272.png"><img src="/images/uploads/2011/01/02-import-existing-300x272.png" alt=" " /></a></p>
<p>Pilih folder induknya.
<a href="/images/uploads/2011/01/03-select-root-directory-272x300.png"><img src="/images/uploads/2011/01/03-select-root-directory-272x300.png" alt=" " /></a></p>
<p>Selesai, semua project kita bisa dibuka. Bahkan kita bisa menjalankan project ui.web dengan cara klik kanan Run in Server. Ini bisa dilihat dari icon project tersebut yang berbentuk bola dunia.</p>
<p><a href="/images/uploads/2011/01/04-import-result-300x139.png"><img src="/images/uploads/2011/01/04-import-result-300x139.png" alt=" " /></a></p>
<p>Selesai sudah, mari kita <a href="http://endy.artivisi.com/blog/aplikasi/sharing-repository-git/">share dengan rekan yang lain</a>.</p>
Konsep Clustering2011-01-30T04:15:14+07:00https://software.endy.muhardin.com/aplikasi/konsep-clustering<p>Di milis JUG, lagi-lagi ada yang tanya tentang load balancing, failover, dan clustering. Jawabannya masih sama sejak 10 tahun saya berkecimpung di urusan coding-mengcoding. Jadi, baiklah saya tulis di blog saja, supaya next time bisa jadi referensi.</p>
<p>Ini sebetulnya dua hal yang berbeda.
Load balancing ya membagi beban.
Failover ya mencegah single point of failure.</p>
<h3 id="load-balancing">Load Balancing</h3>
<p>Load balancer terdiri dari satu balancer dan banyak worker.
Bebannya dibagi2 ke semua worker dengan algoritma yang biasanya bisa dipilih.
Bisa merata (round robin) bisa juga dengan bobot (weighted), misalnya worker X mendapat 2 kali worker Y karena dia specnya lebih tinggi.
Atau bisa juga dynamic, artinya si LB akan mengetes kondisi semua worker, mana yang kira2 sedang idle itu yang dikasi.
Mana yang sedang idle ini nanti ada lagi settingnya, apakah melihat CPU usage pakai SNMP, melihat ping response time, whatever.</p>
<p>Failover minimal harus ada 2 titik.
Kalo kita implement LB aja, point of failure (POF) nya adalah si LB.
Begitu LB nya mati, ya udah semua worker gak bisa diakses.
Untuk mencegah ini, LB nya harus ada 2, satu aktif satu standby (pasif).</p>
<p>Contoh aplikasi load balancer :</p>
<ul>
<li>HAProxy</li>
<li>ldirectord (Ultra Monkey)</li>
<li>Pound</li>
</ul>
<p>Contoh aplikasi lain yang bisa jadi load balancer :</p>
<ul>
<li>Apache (mod_proxy_balancer)</li>
<li>Nginx</li>
<li>lighttpd</li>
<li>bind (DNS Server)</li>
</ul>
<h3 id="failover">Failover</h3>
<p>Contoh aplikasi failover :</p>
<ul>
<li><a href="http://www.linux-ha.org/wiki/Heartbeat">heartbeat</a></li>
<li><a href="http://clusterlabs.org/wiki/Main_Page">pacemaker</a></li>
<li><a href="http://corosync.github.io/corosync/">corosync</a></li>
<li><a href="http://www.pureftpd.org/project/ucarp">ucarp</a></li>
<li><a href="http://www.keepalived.org/">keepalived</a></li>
</ul>
<p>Failover artinya mengatasi kalau ada service yang mati. Ada dua jenis aplikasi untuk menangani failover :</p>
<ul>
<li>Network Oriented : keepalived, ucarp</li>
<li>Cluster Oriented : corosync, heartbeat</li>
</ul>
<p>Penjelasan lengkapnya bisa dibaca <a href="http://www.formilux.org/archives/haproxy/1003/3259.html">di sini</a>. Namun ringkasnya seperti ini:</p>
<p>Network oriented failover memastikan <strong>minimal satu</strong> service aktif, dan <strong>tidak apa-apa</strong> bila ada lebih dari satu service yang aktif. Ini cocok untuk dipasang di load balancer, karena load balancer tidak menyimpan state. Tidak masalah kalau user melihat ada dua LB, kadang diarahkan ke LB-1 dan kadang ke LB-2.</p>
<p>Cluster oriented failover memastikan <strong>hanya satu</strong> service yang aktif, dan <strong>tidak apa-apa</strong> bila tidak ada service yang aktif. Ini cocok untuk dipasang di database server, karena kita tidak mau database utama dan cadangan dua-duanya aktif. Bisa-bisa datanya tidak tersimpan dengan benar (<a href="http://en.wikipedia.org/wiki/Split-brain_(computing\)">split brain</a>). Untuk lebih jelas tentang cara kerja cluster-oriented failover, bisa dibaca <a href="http://blog.clusterlabs.org/blog/2010/pacemaker-heartbeat-corosync-wtf/">di sini</a>.</p>
<p>Nah, mudah2an sampe di sini jelas bahwa load balancing dan failover itu dua hal yang tidak saling terkait (orthogonal) dan biasanya dikombinasikan untuk mendapatkan konfigurasi yang robust dan performant.</p>
<blockquote>
<blockquote>
<p>Setahu saya konsep2x Clustering diatas berlaku pada saat hit pertama.
Pertanyaan saya.. Bagaimana jika request sudah terlayani tetapi ditengah-tengah proses server tiba2x down.. Apakah proses tersebut langsung di alihkan ke server yang lagi up? Jika iya apakah proses akan di restart dari awal atau server yang sedang up bisa melanjutkan sisa dari proses yang belum dikerjakan di server yang telah down?</p>
</blockquote>
</blockquote>
<blockquote>
</blockquote>
<h3 id="sticky-session">Sticky Session</h3>
<p>Tidak selalu, tergantung konfigurasinya.
Ada konfigurasi sticky session.
Artinya, pada hit pertama, si user akan diberikan penanda, biasanya berbentuk cookie.
Pada hit berikutnya, LB akan melihat cookienya, dan mengarahkan ke server yang sebelumnya sudah mengurus si user ini.</p>
<p>Ada juga konfigurasi non-sticky.
Artinya tiap hit dianggap hit baru, dan didistribusikan ke semua server sesuai algoritma yang dipilih, round robin, weighted, atau dynamic, sesuai penjelasan di atas.</p>
<p>Mau pilih yang mana? Ya tergantung kemampuan LB nya.
Ada yang bisa 2-2 nya sehingga bisa pilih, dan ada juga yang rada stupid sehingga terpaksa pakai non-sticky.
Istilahnya, LBnya layer berapa? Kalo layer 7 biasanya bisa sticky, kalo layer 4 ya gak bisa.
Lebih jauh tentang urusan layer-layeran ini bisa dibaca <a href="http://blog.loadbalancer.org/why-layer-7-sucks/">di sini</a> dan <a href="http://1wt.eu/articles/2006_lb/">di sini</a></p>
<p>Nah, apa impact sticky vs non-sticky?
Ini pengaruhnya ke session data.
Session data adalah data sementara masing-masing user.
Karena sifatnya sementara, maka biasanya tidak disimpan secara persistent di tabel database.
Contoh paling klasik adalah isi shopping cart.
Itu barang belum diorder, tapi sudah dipilih, sehingga biasanya belum disimpan di database.</p>
<p>Kalo pake non-sticky, si user pertama milih barang di server X.
Pada saat dia pilih barang kedua, dilayani server Y.
Karena pilihan pertama ada di server X, ya pas dia pilih barang kedua, cuma tercatat 1 barang padahal harusnya 2.</p>
<p>Ini tidak terjadi kalo kita pakai sticky balancer.
Request kedua dan seterusnya akan diarahkan ke server X lagi.</p>
<p>Jadi, sticky atau non-sticky itu impactnya ke temporary data user, sering disebut dengan istilah session data atau user state.</p>
<p>Nah, setelah jelas apa dampaknya sticky vs non-sticky, mari kita lanjut ke pertanyaan selanjutnya.</p>
<blockquote>
<blockquote>
<p>Kalau untuk Java EE Application Server apakah untuk pertanyaan saya di atas sudah ada featurenya atau perlu ada tambahan produk lagi untuk bisa sharing informasi terhadap state suatu proses yang dijalankan di satu server sehingga jika server tersebut down proses bisa dilanjutkan di server yang lain tanpa merestart proses?</p>
</blockquote>
</blockquote>
<blockquote>
</blockquote>
<h3 id="session-replication">Session Replication</h3>
<p>Mengenai urusan session/state management, ini sangat tergantung merek application server yang digunakan.
Secara umum, settingan standar appserver biasanya simpan data session di memori.
Kalau kita enable cluster, misalnya terdiri dari 4 worker, maka data session ini biasanya akan direplikasi ke satu worker lain.
Pada saat worker utama mati, request berikutnya akan diarahkan ke worker cadangannya, sehingga user gak kehilangan data belanjaan.
Biasanya, satu state itu disimpan ke 2 worker saja, bukan direplikasi ke semua untuk alasan efisiensi bandwidth.</p>
<p>Pada penjelasan di atas banyak sekali saya gunakan kata ‘biasanya’. Ini karena kapabilitas dan konfigurasi masing-masing merek appserver sangat berbeda sehingga sulit untuk menggeneralisir kondisinya.</p>
<p>Lalu bagaimana?
Saya biasanya mengambil pendekatan yang universal, yang jalan di semua appserver, sehingga tidak perlu pusing menghafal appserver apa bisa apa settingnya gimana.
Teknik universalnya sederhana: aplikasi webnya dibuat stateless.
Jangan ada simpan data di memori. Simpan semua di database, atau di distributed cache (misalnya memcached).</p>
<p>Di Java, data yang ada di memori antara lain : session variable, static variable, context variable.
Di PHP, CMIIW cuma session dan global variable aja.</p>
<p>Karena selama ini saya menggunakan teknik ini, jadi saya kurang up to date terhadap appserver apa bisa apa settingnya gimana.
Demikian juga tentang load balancer apa support sticky atau tidak, saya tidak pernah memikirkannya.
Pokoknya simpan state di distributed cache atau database, setelah itu mau pakai appserver Tomcat, Jetty, Glassfish, Weblogic, terserah.
Mau pakai load balancer Apache HTTPD, Nginx, lighty, HAProxy, Pound, Ultramonkey, juga terserah.</p>
<p>Demikian sekilas sharing mengenai load balancing dan clustering. Semoga menjadi cerah.</p>
Development Stack 20112011-01-25T20:33:18+07:00https://software.endy.muhardin.com/java/development-stack-2011<p>Posting ini adalah update dari <a href="http://endy.artivisi.com/blog/java/stack-2008-1/">posting tiga tahun yang lalu</a>. Tidak banyak yang berubah dalam stack ini, yang bisa berarti dua hal: pilihan tiga tahun yang lalu sudah tepat atau malas belajar selama 3 tahun ini.
Mudah-mudahan alasannya yang pertama :D</p>
<blockquote>
<p>Update : Gradle tidak jadi dipakai, karena kita tidak mau maintain 2 skillset. Maven 2 ternyata stabil dan bekerja sesuai harapan. Hudson terlibat kerusuhan dengan Oracle, akhirnya fork jadi Jenkins.</p>
</blockquote>
<h3 id="presentation-layer">Presentation Layer</h3>
<ul>
<li>
<p>Spring MVC</p>
</li>
<li>
<p>SiteMesh</p>
</li>
<li>
<p>Dojo Toolkit</p>
</li>
<li>
<p>ExtJS</p>
</li>
<li>
<p>Spring Security</p>
</li>
<li>
<p>Jasper Report</p>
</li>
<li>
<p>Jackson</p>
</li>
</ul>
<h3 id="business-layer">Business Layer</h3>
<ul>
<li>
<p>Spring Framework</p>
</li>
<li>
<p>Hibernate</p>
</li>
</ul>
<h3 id="library-lain-yang-sering-digunakan">Library lain yang sering digunakan</h3>
<ul>
<li>
<p>Logback</p>
</li>
<li>
<p>Joda Time</p>
</li>
<li>
<p>Velocity</p>
</li>
<li>
<p>JPos</p>
</li>
</ul>
<h3 id="infrastruktur">Infrastruktur</h3>
<ul>
<li>
<p>Version Control : Git + Gitosis</p>
</li>
<li>
<p>Testing Tools : JUnit, DBUnit, JMeter, Sonar</p>
</li>
<li>
<p>Issue Tracker : Redmine</p>
</li>
<li>
<p>Build Tools : <del>Gradle</del>, Maven</p>
</li>
<li>
<p>Continuous Integration : <del>Hudson</del> Jenkins</p>
</li>
<li>
<p>OS Programmer : Ubuntu Desktop</p>
</li>
<li>
<p>OS Server : Ubuntu Server, Debian</p>
</li>
</ul>
<h3 id="deployment-target">Deployment Target</h3>
<ul>
<li>
<p>Database Server : MySQL, Oracle</p>
</li>
<li>
<p>Application Server : Tomcat, Glassfish</p>
</li>
</ul>
<p>Praktis perubahan yang terjadi hanyalah dari Subversion ganti menjadi Git.
Nah, bagaimana menurut Anda? Pilihan tepat atau malas belajar?</p>
Sharing Repository Git2011-01-11T17:47:43+07:00https://software.endy.muhardin.com/aplikasi/sharing-repository-git<p>Skenario : selama ini kita coding di laptop sendiri saja. Kemudian ada kebutuhan untuk kolaborasi dengan orang lain melalui Git. Bagaimana caranya? Baiklah mari kita bahas di artikel ini.</p>
<h2 id="inisialisasi-repository-git">Inisialisasi Repository Git</h2>
<p>Pertama, kita inisialisasi dulu repository Git. Masuk ke dalam folder project dan ketikkan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git init
</code></pre></div></div>
<p>Git akan membuat repository kosong di dalam folder project, ditandai dengan adanya folder baru bernama .git</p>
<p>Selanjutnya, kita akan memasukkan semua file dan folder project kita ke dalam repository. Yang harus dimasukkan adalah file source code, baik itu Java, HTML, XML, dan sebagainya. Yang tidak perlu dimasukkan adalah file hasil kompilasi atau hasil generate. Kita perlu mendaftarkan file yang tidak ingin disimpan dalam file konfigurasi yang bernama .gitignore</p>
<p>File ini harus kita buat sendiri menggunakan text editor. Berikut contoh isi filenya, bila kita coding menggunakan Eclipse atau Netbeans</p>
<p>[gist id=773975]</p>
<p>Setelah kita setting ignore file, berikutnya kita masukkan semua file dan folder ke dalam antrian.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
</code></pre></div></div>
<p>Kemudian, simpan ke repository</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git commit -m "commit pertama project XXX"
</code></pre></div></div>
<p>Project sudah tersimpan di repository Git di komputer lokal kita. Mari kita upload ke server, atau dikenal dengan istilah push.</p>
<h2 id="share-repository">Share Repository</h2>
<p>Kita memerlukan server di mana kita memiliki ijin akses untuk melakukan push. Cara memperoleh ijin akses tidak dibahas pada artikel ini. Silahkan buat account di Github atau Gitorious. Bila ingin push ke repository perusahaan, minta informasinya pada admin Anda.</p>
<p>Setelah kita mendapatkan URL server yang bisa kita gunakan, daftarkan sebagai remote. Berikut perintahnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add <namaremote> <URL>
</code></pre></div></div>
<p>Contohnya seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add github git@github.com:endymuhardin/project-terbaru-saya.git
</code></pre></div></div>
<p>Pastikan remotenya sudah terdaftar dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote -v
</code></pre></div></div>
<p>Terakhir, mari kita push dengan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push <namaremote> <namabranch>
</code></pre></div></div>
<p>Contohnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push github master
</code></pre></div></div>
<p>Hore, project kita sudah naik ke server. Kita tinggal share URL tersebut ke rekan kerja kita.</p>
Menggunakan Gist2011-01-09T03:40:16+07:00https://software.endy.muhardin.com/aplikasi/menggunakan-gist<p>Gist adalah fitur yang disediakan oleh Github. Fungsi dasarnya mirip dengan pastebin, yaitu kita bisa paste text di sana, dan disharing dengan orang lain. Keunggulan Gist adalah dia sudah memiliki kemampuan version control dengan Git. Sehingga kita bisa fork, clone, modifikasi, dan push lagi ke repo utama dengan seluruh history tersimpan di sana.</p>
<p>Untuk bisa menggunakan Gist, kita harus memiliki account Github dulu. Setelah itu, kita bisa buat <a href="https://gist.github.com">gist di sini</a>.</p>
<p>Cara membuatnya tidak sulit. Cukup entri nama file, keterangan, dan isi text yang mau dishare.</p>
<p><a href="/images/uploads/2011/01/01-create-gist-300x204.png"><img src="/images/uploads/2011/01/01-create-gist-300x204.png" alt=" " /></a></p>
<p>Setelah itu, tekan Create Public Gist. Gist kita akan siap digunakan.</p>
<p><a href="/images/uploads/2011/01/02-gist-created-300x207.png"><img src="/images/uploads/2011/01/02-gist-created-300x207.png" alt=" " /></a></p>
<p>Gist yang sudah dibuat bisa dipasang di blog. Caranya, klik tombol embed.</p>
<p><a href="/images/uploads/2011/01/03-embed-link-300x205.png"><img src="/images/uploads/2011/01/03-embed-link-300x205.png" alt=" " /></a></p>
<p>Nanti akan muncul textfield berisi tag HTML untuk dipasang di blog, kira-kira seperti ini tagnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script src="https://gist.github.com/770832.js?file=HelloWorld.java"></script>
</code></pre></div></div>
<p>Tag ini bisa langsung dipasang di blog kita. Hasilnya seperti di bawah ini.</p>
<p>Kelemahan cara ini adalah dia membutuhkan javascript, dan isi filenya tidak terindeks oleh spider. Untuk mengatasinya, kita gunakan <a href="http://wordpress.org/extend/plugins/embed-github-gist/">plugin wordpress ini</a>.</p>
<p>Setelah digunakan, kita cukup memasang tag khusus seperti dijelaskan di websitenya. Ini contoh hasilnya</p>
<p>[gist id=770832]</p>
<p>Sekilas tidak terlihat bedanya antara pakai plugin dan tidak. Tapi coba lihat source halaman ini, klik kanan kemudian View Source. Yang menggunakan plugin, source codenya benar-benar ada tulisannya. Sedangkan yang pakai tag script tidak ada source code hello worldnya.</p>
<p>Nah, kalau sudah pakai ini, tidak perlu bingung lagi mewarnai source code di blog. Kalau mau revisi, tinggal edit aja di Github, dan otomatis di blog langsung terupdate.</p>
Instalasi Git di Windows2011-01-07T18:28:25+07:00https://software.endy.muhardin.com/aplikasi/instalasi-git-di-windows<p>Setelah kemarin kita bahas <a href="http://endy.artivisi.com/blog/lain/migrasi-subversion-ke-git/">migrasi di sisi server</a>, sekarang kita bahas instalasi di client. Kenapa yang dijelaskan hanya Windows, sedangkan Linux tidak? Well, ini karena di Linux instalasinya begitu mudah sehingga terlalu pendek kalau ingin dijadikan satu posting sendiri.</p>
<p>Gak percaya? Ini caranya install di Ubuntu. Buka command prompt, dan ketik</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install git-core git-svn git-gui gitk
</code></pre></div></div>
<p>Sedikit konfigurasi standar.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config --global user.name "Endy Muhardin"
git config --global user.email "endy.muhardin@geemail.com"
git config --global color.ui "true"
</code></pre></div></div>
<p>Kemudian, bila kita belum punya public key, silahkan bikin seperti <a href="http://endy.artivisi.com/blog/linux/login-ssh-dengan-private-key/">tutorial di sini</a>.</p>
<p>Dan selesailah sudah. Seperti saya bilang sebelumnya, singkat dan sama sekali gak seru. Gak ada screenshotnya :D</p>
<p>Nah, mari kita bahas instalasi di Windows.</p>
<p>Pertama, <a href="http://git-scm.com/download/win">unduh Git dari websitenya</a>. Pastikan kita mengambil yang sesuai dengan arsitektur komputer kita (32 bit atau 64 bit).</p>
<p>Setelah diunduh, tentu kita jalankan. Berikut screenshot next-next seperti biasa.</p>
<p><a href="https://lh3.googleusercontent.com/smXWv3F_rNxbpGk_Y0usTJhcuhf1ARfVHBzOInbWI87hJN5NUvXanKRniFLWhq1QusMJzHXA7sQ7ZBuxzYFkb2DG8K2ll61VzRL9rcyJ94KvWCSSFVnc0DKRSzr23cv9P2tFIfxnEmwr9eSu_3TcHnOEKEuYSAtmUij0XQVzTXoWxA5G4qRA_Rlew8YvLSa0tks0ygkMN-QJv-6ITK0S0-dHUEu4OJh0FI22N-d32GRq6fhra-b3HZ2w9d0NmdZZ3O6xD4PEyVkW7oHw37OuCVb9PpRJq-6Db_zzSuwUDUgH5UwqBRXsNKzNiNBZq5ZPVGkZVQuzrl9gjfRyyry2SHJHoFzh3IF7UC164Fy0lgTzqJFxruX0D22wUrWZD9dQuk2FXYhEQr5fWAu1qXVsL3vh5rWy7XFFpI0z838ViO8HxwUxRRoMs3IlJPSE-FeZO6cBZWx4ofO1HggJJnBwErxdnPiOX0tXVyWJ8KBxtsBGDnJMXqtZlVw0ftTdyFB_8y92BMWd1gnQ9f9TJgBL24HdR1tnO1MzSL8ncacfURQ=w1280-no"><img src="https://lh3.googleusercontent.com/smXWv3F_rNxbpGk_Y0usTJhcuhf1ARfVHBzOInbWI87hJN5NUvXanKRniFLWhq1QusMJzHXA7sQ7ZBuxzYFkb2DG8K2ll61VzRL9rcyJ94KvWCSSFVnc0DKRSzr23cv9P2tFIfxnEmwr9eSu_3TcHnOEKEuYSAtmUij0XQVzTXoWxA5G4qRA_Rlew8YvLSa0tks0ygkMN-QJv-6ITK0S0-dHUEu4OJh0FI22N-d32GRq6fhra-b3HZ2w9d0NmdZZ3O6xD4PEyVkW7oHw37OuCVb9PpRJq-6Db_zzSuwUDUgH5UwqBRXsNKzNiNBZq5ZPVGkZVQuzrl9gjfRyyry2SHJHoFzh3IF7UC164Fy0lgTzqJFxruX0D22wUrWZD9dQuk2FXYhEQr5fWAu1qXVsL3vh5rWy7XFFpI0z838ViO8HxwUxRRoMs3IlJPSE-FeZO6cBZWx4ofO1HggJJnBwErxdnPiOX0tXVyWJ8KBxtsBGDnJMXqtZlVw0ftTdyFB_8y92BMWd1gnQ9f9TJgBL24HdR1tnO1MzSL8ncacfURQ=w1280-no" alt="Dobel klik installer" /></a></p>
<p><a href="https://lh3.googleusercontent.com/k9fNsCKt6SHUGv8AdWWjMFBgGEZWC4crcekLQWJZq_uFu7iBshYGx_i4cmo7mSfPyXdQ_AAKH7PNFkyJlq2OJX1-E7WpUdRacpJo_yaOFHC09z3xCuAXySZMYOSy4n1h4Rv45LjE55R1MU2P4Rri5h_qZza2pfUCgulc5TW4x0jfyZLPDwzsxAT7QpK7RhcWLw8UpinLfU0yyoJs4BrjKaMlorTJpsVeddvVmnGKglXPbLSW1OFfOwZP5FQyEuYLjv4jNtc9QUotCRF-uOmqwqSOrV0QkSQBU5d7C7Eb3h28kH1Zl29C5fZs5Je-MJG5xGJcxSCjaDWzJjFuiKtcF5hkB_AFqsecy4exu6A5DghfZiiXICqe3HoYCbo6Y_wVm-fAQZxuYjlvKjILO8R-VllCtO3nSuV06KF7WyTDhiGibzpcVDfX7QhAnxBWjhBK7PbM9bj-Pa-D2O5mxXSTSBZD31AmDckG_xXbOgzU_lZnRaYnJUuMr-qSOpFME1Eq3D9nDtbQeMLDeCs8drlPnJ_hWl8bJ5qSYnn1hwbOOZ8=w1280-no"><img src="https://lh3.googleusercontent.com/k9fNsCKt6SHUGv8AdWWjMFBgGEZWC4crcekLQWJZq_uFu7iBshYGx_i4cmo7mSfPyXdQ_AAKH7PNFkyJlq2OJX1-E7WpUdRacpJo_yaOFHC09z3xCuAXySZMYOSy4n1h4Rv45LjE55R1MU2P4Rri5h_qZza2pfUCgulc5TW4x0jfyZLPDwzsxAT7QpK7RhcWLw8UpinLfU0yyoJs4BrjKaMlorTJpsVeddvVmnGKglXPbLSW1OFfOwZP5FQyEuYLjv4jNtc9QUotCRF-uOmqwqSOrV0QkSQBU5d7C7Eb3h28kH1Zl29C5fZs5Je-MJG5xGJcxSCjaDWzJjFuiKtcF5hkB_AFqsecy4exu6A5DghfZiiXICqe3HoYCbo6Y_wVm-fAQZxuYjlvKjILO8R-VllCtO3nSuV06KF7WyTDhiGibzpcVDfX7QhAnxBWjhBK7PbM9bj-Pa-D2O5mxXSTSBZD31AmDckG_xXbOgzU_lZnRaYnJUuMr-qSOpFME1Eq3D9nDtbQeMLDeCs8drlPnJ_hWl8bJ5qSYnn1hwbOOZ8=w1280-no" alt="Allow instalasi" /></a></p>
<p><a href="https://lh3.googleusercontent.com/Zw-GsIEUflCJPgJTcGY-9k5U8t1trWRDtMKi48fcudym2WhnnrR9PhZtPCqGPFX5ZEQuwuvIT_Lr9YE1dXLQ4_HRTfU1zYh554RunngG-jxiAjRlfLWoD-1bjaxpSArK84uTmM-nLAqlxCPl4T5sDStAVWr44OqRqi88sqEC39nchAFn9YQuzxt058y3lKr-Krzk5Zp0MsC0Paa1qxc08OqNohPeMLQvQhYTVfuDEBT6a1IBlQilMhQStUKfRY2OhxiZqRJkmVSweieIZCQr74Oee5QKmXa_EiAv1dnOabtJn51nbUeIuKvkoFhdHHruRPhQdMReqb_fsQD85C175gH5XpgkK9uyBsGSMTGElhzVaqyr11SKRYeIGTf818J8dZ6B1H_mXETYysM7XP2CdmYf-y7x1lpV2UxlU9fQIgx6NhOrgMI8g3Hw5_YANxB5aupyCEkhEYbxh5CxPo_gutjYNFx2NSpyMxZm3QXxBH11leDJJJOa_Jiu7cSg8KZm3K13WaG_yZQF43v9mp1e1YKQsdRtMLJVl19DemQFTYw=w1280-no"><img src="https://lh3.googleusercontent.com/Zw-GsIEUflCJPgJTcGY-9k5U8t1trWRDtMKi48fcudym2WhnnrR9PhZtPCqGPFX5ZEQuwuvIT_Lr9YE1dXLQ4_HRTfU1zYh554RunngG-jxiAjRlfLWoD-1bjaxpSArK84uTmM-nLAqlxCPl4T5sDStAVWr44OqRqi88sqEC39nchAFn9YQuzxt058y3lKr-Krzk5Zp0MsC0Paa1qxc08OqNohPeMLQvQhYTVfuDEBT6a1IBlQilMhQStUKfRY2OhxiZqRJkmVSweieIZCQr74Oee5QKmXa_EiAv1dnOabtJn51nbUeIuKvkoFhdHHruRPhQdMReqb_fsQD85C175gH5XpgkK9uyBsGSMTGElhzVaqyr11SKRYeIGTf818J8dZ6B1H_mXETYysM7XP2CdmYf-y7x1lpV2UxlU9fQIgx6NhOrgMI8g3Hw5_YANxB5aupyCEkhEYbxh5CxPo_gutjYNFx2NSpyMxZm3QXxBH11leDJJJOa_Jiu7cSg8KZm3K13WaG_yZQF43v9mp1e1YKQsdRtMLJVl19DemQFTYw=w1280-no" alt=" " /></a></p>
<p><a href="https://lh3.googleusercontent.com/iOGhMjFDWm7bcswGrKT6WcIt6nqv-3hchWBxn7AZT4q8LgO0m0xWauQanQGc8m3xaso4YX7LZ6P3uj9SirxDeCIOX8kdhHfcXXEyTKauqANDq_R60sTqlKTbV4g6qSwnnL5-zxc0up5-Gf-Fd7frv3bg0TRLDCGvtuUajsyVkvb1euRXIimOCwXa42URnDuITlvlqXTEASkZaIPXJMVWm5i0U9eSQcg6JXkhoi4iw1UBvjnxTpr8uw6V36sWVgeXBpIqSev-jPkNjXO8BpheMnAASdB6Ch5w0cUei_O9iP7KhnbxSLD2AxquEQkBbhUCrbb32srr7ayGbOwP46-Bb18e98eJQUouvkQktYNj_Gkg9IGRxkhvX1dAOhMw56WFTef9Db-AW6n9KybuTd2bCQHDaEddBJw35FPsMz_kp3sRocLr43r5kC93iofcu1mfNaQKLoWBAujriYNNhTkHcPh0sCDwRKFQ95C82OuBx0xUKwWkrWG55azrUw7k43lgmWYh47L-eI6DooxPnGM-bZlAx0p1l1mtlvrRmvujPdw=w1280-no"><img src="https://lh3.googleusercontent.com/iOGhMjFDWm7bcswGrKT6WcIt6nqv-3hchWBxn7AZT4q8LgO0m0xWauQanQGc8m3xaso4YX7LZ6P3uj9SirxDeCIOX8kdhHfcXXEyTKauqANDq_R60sTqlKTbV4g6qSwnnL5-zxc0up5-Gf-Fd7frv3bg0TRLDCGvtuUajsyVkvb1euRXIimOCwXa42URnDuITlvlqXTEASkZaIPXJMVWm5i0U9eSQcg6JXkhoi4iw1UBvjnxTpr8uw6V36sWVgeXBpIqSev-jPkNjXO8BpheMnAASdB6Ch5w0cUei_O9iP7KhnbxSLD2AxquEQkBbhUCrbb32srr7ayGbOwP46-Bb18e98eJQUouvkQktYNj_Gkg9IGRxkhvX1dAOhMw56WFTef9Db-AW6n9KybuTd2bCQHDaEddBJw35FPsMz_kp3sRocLr43r5kC93iofcu1mfNaQKLoWBAujriYNNhTkHcPh0sCDwRKFQ95C82OuBx0xUKwWkrWG55azrUw7k43lgmWYh47L-eI6DooxPnGM-bZlAx0p1l1mtlvrRmvujPdw=w1280-no" alt="Welcome" /></a></p>
<p><a href="https://lh3.googleusercontent.com/iOGhMjFDWm7bcswGrKT6WcIt6nqv-3hchWBxn7AZT4q8LgO0m0xWauQanQGc8m3xaso4YX7LZ6P3uj9SirxDeCIOX8kdhHfcXXEyTKauqANDq_R60sTqlKTbV4g6qSwnnL5-zxc0up5-Gf-Fd7frv3bg0TRLDCGvtuUajsyVkvb1euRXIimOCwXa42URnDuITlvlqXTEASkZaIPXJMVWm5i0U9eSQcg6JXkhoi4iw1UBvjnxTpr8uw6V36sWVgeXBpIqSev-jPkNjXO8BpheMnAASdB6Ch5w0cUei_O9iP7KhnbxSLD2AxquEQkBbhUCrbb32srr7ayGbOwP46-Bb18e98eJQUouvkQktYNj_Gkg9IGRxkhvX1dAOhMw56WFTef9Db-AW6n9KybuTd2bCQHDaEddBJw35FPsMz_kp3sRocLr43r5kC93iofcu1mfNaQKLoWBAujriYNNhTkHcPh0sCDwRKFQ95C82OuBx0xUKwWkrWG55azrUw7k43lgmWYh47L-eI6DooxPnGM-bZlAx0p1l1mtlvrRmvujPdw=w1280-no"><img src="https://lh3.googleusercontent.com/iOGhMjFDWm7bcswGrKT6WcIt6nqv-3hchWBxn7AZT4q8LgO0m0xWauQanQGc8m3xaso4YX7LZ6P3uj9SirxDeCIOX8kdhHfcXXEyTKauqANDq_R60sTqlKTbV4g6qSwnnL5-zxc0up5-Gf-Fd7frv3bg0TRLDCGvtuUajsyVkvb1euRXIimOCwXa42URnDuITlvlqXTEASkZaIPXJMVWm5i0U9eSQcg6JXkhoi4iw1UBvjnxTpr8uw6V36sWVgeXBpIqSev-jPkNjXO8BpheMnAASdB6Ch5w0cUei_O9iP7KhnbxSLD2AxquEQkBbhUCrbb32srr7ayGbOwP46-Bb18e98eJQUouvkQktYNj_Gkg9IGRxkhvX1dAOhMw56WFTef9Db-AW6n9KybuTd2bCQHDaEddBJw35FPsMz_kp3sRocLr43r5kC93iofcu1mfNaQKLoWBAujriYNNhTkHcPh0sCDwRKFQ95C82OuBx0xUKwWkrWG55azrUw7k43lgmWYh47L-eI6DooxPnGM-bZlAx0p1l1mtlvrRmvujPdw=w1280-no" alt="License" /></a></p>
<p><a href="https://lh3.googleusercontent.com/ARdqdf3oUDeNtRuEcccYFfz09OTs2yn79-lhiZGnKV8SpcnnECxOFvH_sVf8tW0NQ6iGiJFdg8F96blkheBpacDSrQZ7R6VOuYGloneRj30mb4kKC_YLVDFxkC8YJlprhavlE6CuVMHeGURCFXt7rTK_HL3MfU-uTyGlI0IYARn8J_xq0G2IRYwMCkGus9EcHe9SYnNM4ocEi1aVd1jB2TFmvdCu0WgSGRAKofrIk0AAraZ_S3yuLvl75AMT7h1Cb8kFaCgL8btq2Q_2hRWXKkipVnmVBCHJNx6SFclyOxIrQ0ekAgu398qwju5zr6Q-GOGcW5NV3CnPMkQBcZ9hKLZiSuHovUb_dbsTjsq70J4RSlrQTaOth83FVhDn-aaIVFd3hNsdfF4BGt4KXKqrVVRzN7qqPE4KaB493LMO_vXXGRvackrzs6B4ldXvPBtGQVcvgg_3lMCkyoZr068JQMCN00zo_weLKv8Jfw2tFx0dnuIcpLz-jkUfQTUZjfWbQa2sGfNshNPO1LC_dUYaYfrjOaJ9lbhsM-Knph2szWU=w1280-no"><img src="https://lh3.googleusercontent.com/ARdqdf3oUDeNtRuEcccYFfz09OTs2yn79-lhiZGnKV8SpcnnECxOFvH_sVf8tW0NQ6iGiJFdg8F96blkheBpacDSrQZ7R6VOuYGloneRj30mb4kKC_YLVDFxkC8YJlprhavlE6CuVMHeGURCFXt7rTK_HL3MfU-uTyGlI0IYARn8J_xq0G2IRYwMCkGus9EcHe9SYnNM4ocEi1aVd1jB2TFmvdCu0WgSGRAKofrIk0AAraZ_S3yuLvl75AMT7h1Cb8kFaCgL8btq2Q_2hRWXKkipVnmVBCHJNx6SFclyOxIrQ0ekAgu398qwju5zr6Q-GOGcW5NV3CnPMkQBcZ9hKLZiSuHovUb_dbsTjsq70J4RSlrQTaOth83FVhDn-aaIVFd3hNsdfF4BGt4KXKqrVVRzN7qqPE4KaB493LMO_vXXGRvackrzs6B4ldXvPBtGQVcvgg_3lMCkyoZr068JQMCN00zo_weLKv8Jfw2tFx0dnuIcpLz-jkUfQTUZjfWbQa2sGfNshNPO1LC_dUYaYfrjOaJ9lbhsM-Knph2szWU=w1280-no" alt="Lokasi Instalasi" /></a></p>
<p><a href="https://lh3.googleusercontent.com/kIWQdvfmDsmH6VuVYSJ3l8p87C0O33FBZyzsb89nWebvlrx-uMSCFXNGLpjb1DO8FoNeXrzxRH9PP7y7ngKb6M2cEUatKvkY3EDpLxX80lhDJlbBt2aEf1SbAaTIILp_CdWD8hKxD2zZc0Hf7R6L5ptzDbU0z0SyyrW99d1XElvtXvo7Guk37GSi0SprtKXLg_TiPeShybTIByiXpqKJVbbZH0dWs_zZtMHD1Iz9-mjNKKHIAKM0gItHlStvm9De0t28SYJI9LnYvWjnS5ZeoyyNMbwHHOkl5JP-6TUco4j0r73qFEkY9-Kv48qu_kFXSumneWSYdr16I3cvBHm5eQbHq8kOMe21IakAnjQtLupsF2nIihGEVKcWfsQOTjGxyvGvMMSkxv5c71Ui05-AOX7xAMMRNmlNra1ql3sMVFtpM9PjBlXYu9EeqmhZFKwGICIe51Wpv6QxrS9B8SIU5GjA5v6W5emrRAbcma7qvVk12h4bv87w9-jOExZU3YF756n2h0FOQI81szEK8nZhIHbOV7N_uUvbq4ov6idKbkc=w1280-no"><img src="https://lh3.googleusercontent.com/kIWQdvfmDsmH6VuVYSJ3l8p87C0O33FBZyzsb89nWebvlrx-uMSCFXNGLpjb1DO8FoNeXrzxRH9PP7y7ngKb6M2cEUatKvkY3EDpLxX80lhDJlbBt2aEf1SbAaTIILp_CdWD8hKxD2zZc0Hf7R6L5ptzDbU0z0SyyrW99d1XElvtXvo7Guk37GSi0SprtKXLg_TiPeShybTIByiXpqKJVbbZH0dWs_zZtMHD1Iz9-mjNKKHIAKM0gItHlStvm9De0t28SYJI9LnYvWjnS5ZeoyyNMbwHHOkl5JP-6TUco4j0r73qFEkY9-Kv48qu_kFXSumneWSYdr16I3cvBHm5eQbHq8kOMe21IakAnjQtLupsF2nIihGEVKcWfsQOTjGxyvGvMMSkxv5c71Ui05-AOX7xAMMRNmlNra1ql3sMVFtpM9PjBlXYu9EeqmhZFKwGICIe51Wpv6QxrS9B8SIU5GjA5v6W5emrRAbcma7qvVk12h4bv87w9-jOExZU3YF756n2h0FOQI81szEK8nZhIHbOV7N_uUvbq4ov6idKbkc=w1280-no" alt="Components" /></a></p>
<p><a href="https://lh3.googleusercontent.com/sKT-YNauPhfSI52JU41qSVXsgHONhYN9h2H_A2Jlz8DviK-CZQeyo5LCmlxpqKmCK9M-pcFNMpZpSQviA1AHDTBEQpmOFQZlO7fvgo2g3BhFBcHdywSHwTrLiPvT8o1_MezeMyKRZTEmH6BNYCVNofMrqzYRfKciyTlhSUjmrr1pRifv-CgjiiPyBTbECNRJlM-jhGf29V3-dxnJKsOtQIjEchxA3_jNdxhrpidkup3l7vNz1PBNvZep7UKYcg_cLz0kS59p1nMlJiqzKqxc0RERKXbRolivBT9ZkUwYN1AnyKV9t9lKUEfNdkABVcDTA70DGozGplhtsOrnLgv41G7RqGfdzc8mj3Vxzu8BVjXWj1b_smohXjTDD10UI19zKuv2p48azpVvuWyQE5T_V5jHCXjvG3YxMwXvESS_0SdOcyXlMsWPyJbsiWheEnniWhEsDt-xadMDAmXhrM6FJs8T9kiL0FHDdeFk94mot56AFZ5e7lmYnhl3DNpoA5YVtWdF1eKsJ_FS_FR5m3EUD0BS7y6aL0HnYEMJkoUbskM=w1280-no"><img src="https://lh3.googleusercontent.com/sKT-YNauPhfSI52JU41qSVXsgHONhYN9h2H_A2Jlz8DviK-CZQeyo5LCmlxpqKmCK9M-pcFNMpZpSQviA1AHDTBEQpmOFQZlO7fvgo2g3BhFBcHdywSHwTrLiPvT8o1_MezeMyKRZTEmH6BNYCVNofMrqzYRfKciyTlhSUjmrr1pRifv-CgjiiPyBTbECNRJlM-jhGf29V3-dxnJKsOtQIjEchxA3_jNdxhrpidkup3l7vNz1PBNvZep7UKYcg_cLz0kS59p1nMlJiqzKqxc0RERKXbRolivBT9ZkUwYN1AnyKV9t9lKUEfNdkABVcDTA70DGozGplhtsOrnLgv41G7RqGfdzc8mj3Vxzu8BVjXWj1b_smohXjTDD10UI19zKuv2p48azpVvuWyQE5T_V5jHCXjvG3YxMwXvESS_0SdOcyXlMsWPyJbsiWheEnniWhEsDt-xadMDAmXhrM6FJs8T9kiL0FHDdeFk94mot56AFZ5e7lmYnhl3DNpoA5YVtWdF1eKsJ_FS_FR5m3EUD0BS7y6aL0HnYEMJkoUbskM=w1280-no" alt="Menu Folder" /></a></p>
<p>Perhatikan, untuk langkah selanjutnya, kita perlu mengganti opsi default menjadi pilihan yang kedua.</p>
<p><a href="https://lh3.googleusercontent.com/1MJ4BA1MOdlaraKZs9kwuHhTa1Ny416MvtHXQ2zXhRkvdDrdjBtzRekA67b3tpyvHAfNuVPrFfII=w1280-no"><img src="https://lh3.googleusercontent.com/1MJ4BA1MOdlaraKZs9kwuHhTa1Ny416MvtHXQ2zXhRkvdDrdjBtzRekA67b3tpyvHAfNuVPrFfII=w1280-no" alt="Default Windows Command Prompt" /></a></p>
<p><a href="https://lh3.googleusercontent.com/W6zL37LSxwQlvtDzpsaJ6zH1M-CDyLltDWYHeq_LD6br335iOdi5RkgJFyu0Kz-7Ie0UsAQyh_8vxlClIgMNxF0xe2iKGhsungYERVSw-ZlYLB_O3XLoQMDX-g25j34ivbr9lGmbbZPmyKssXvVoZHkofMGTlv848l1n7UwXjHhfDA0FYNiHd3R4t3y8cz2KBkxRrTo7c4sFIYmJMoHDja75fN3V_ILGnfhoX3UZdJ9YLqIC4ZhDYMo0XfGuEqbb8a3JwhesMxJOv0kfnDN6bBtdGIBlN2ixFl1hrj_dp1pSgc8OB4hzHEJt6sb31LpGB0Cy3Atk7maBoV2FBTYa5tABpQL2_kAfRJtiICKlD7V2pchLl5er6uATs7OJzk5-G1jYX5PeIPiMtpRxjJzBMSbk2Sy3C-nl8-Qv2Hk6rnIZB1KBMqImCzyxse8p3Y2KYPXElFDzXDI-v4vw4zY78CwFqE3zSnPSddHDnjr2Svww2hTSUY_185TNvVb6GXIhCyPPe8hGU5A3kGYbJNatoXDM7S2qdUrphRDJKWdedRY=w1280-no"><img src="https://lh3.googleusercontent.com/W6zL37LSxwQlvtDzpsaJ6zH1M-CDyLltDWYHeq_LD6br335iOdi5RkgJFyu0Kz-7Ie0UsAQyh_8vxlClIgMNxF0xe2iKGhsungYERVSw-ZlYLB_O3XLoQMDX-g25j34ivbr9lGmbbZPmyKssXvVoZHkofMGTlv848l1n7UwXjHhfDA0FYNiHd3R4t3y8cz2KBkxRrTo7c4sFIYmJMoHDja75fN3V_ILGnfhoX3UZdJ9YLqIC4ZhDYMo0XfGuEqbb8a3JwhesMxJOv0kfnDN6bBtdGIBlN2ixFl1hrj_dp1pSgc8OB4hzHEJt6sb31LpGB0Cy3Atk7maBoV2FBTYa5tABpQL2_kAfRJtiICKlD7V2pchLl5er6uATs7OJzk5-G1jYX5PeIPiMtpRxjJzBMSbk2Sy3C-nl8-Qv2Hk6rnIZB1KBMqImCzyxse8p3Y2KYPXElFDzXDI-v4vw4zY78CwFqE3zSnPSddHDnjr2Svww2hTSUY_185TNvVb6GXIhCyPPe8hGU5A3kGYbJNatoXDM7S2qdUrphRDJKWdedRY=w1280-no" alt="Line Endings" /></a></p>
<p><a href="https://lh3.googleusercontent.com/5X6VwjAYuY3Bz65NRdzknWZzB5HcXtq9Qn79-xXrvjuRF0G4qKQ_HEkKxeSF_1r9_o0KMYodKR7PY3rxGiFvc9aqk0maWBnFP_cjrsG8vrLjBhf0__9VX35NbQXEP_PMN8frd90SC0SMXuKlUaH8rGlXXDVrxJ4aOVI3Huov3iS4IKy5BCjjBlO80S8Nu9J0xM87hgoGzquzufRHQjS7llL56DUcvEqEh_79bxPXmWZBLvMXc-rfnmXf3sRz-z9z1nGjHrlrTkSUBBfBiwRbxst0S3Yxsf9_3QgCxJtFKrculko95Sqe_El18I-_ScUF15RSNqxwOzBgE8lndtIliri6QB8_npmXogjdfpTi4Qw85BhQ7qV2VzBOWbDiYUAZF1wJtE2Ayclu1oxpYtv-l7BbPLVb8ZAcAgVh4G3ggt5jpLC-kIBGUBrP33Of359PEt9E-fG8xo6K6zuxVQhJmmH1kgpRYdT4l2FCdmbARQmYYn_zNoH8qmyQ0KYs_6bl_HMRFW28gN_80M6s4iCIOlQB4W9uqDiZTUh2dH_p1Ps=w1280-no"><img src="https://lh3.googleusercontent.com/5X6VwjAYuY3Bz65NRdzknWZzB5HcXtq9Qn79-xXrvjuRF0G4qKQ_HEkKxeSF_1r9_o0KMYodKR7PY3rxGiFvc9aqk0maWBnFP_cjrsG8vrLjBhf0__9VX35NbQXEP_PMN8frd90SC0SMXuKlUaH8rGlXXDVrxJ4aOVI3Huov3iS4IKy5BCjjBlO80S8Nu9J0xM87hgoGzquzufRHQjS7llL56DUcvEqEh_79bxPXmWZBLvMXc-rfnmXf3sRz-z9z1nGjHrlrTkSUBBfBiwRbxst0S3Yxsf9_3QgCxJtFKrculko95Sqe_El18I-_ScUF15RSNqxwOzBgE8lndtIliri6QB8_npmXogjdfpTi4Qw85BhQ7qV2VzBOWbDiYUAZF1wJtE2Ayclu1oxpYtv-l7BbPLVb8ZAcAgVh4G3ggt5jpLC-kIBGUBrP33Of359PEt9E-fG8xo6K6zuxVQhJmmH1kgpRYdT4l2FCdmbARQmYYn_zNoH8qmyQ0KYs_6bl_HMRFW28gN_80M6s4iCIOlQB4W9uqDiZTUh2dH_p1Ps=w1280-no" alt="Terminal Emulator" /></a></p>
<p><a href="https://lh3.googleusercontent.com/2bUKaHYAtkkYeiSMBWZ28iKH-8m2KdQoqIj8KtMMpzRgwX51kWDHyfpnRfQrfuyJ3z1nM21aOghz0pcLM5DLXS-TFC5MzplhvRtYNFiPsOKINgzEPf90QhMyzOHymUQSjk-8efu1Cjw2FU_L47B0GmG2zXEb2M-tgNgkEqLNlHF9OqgWwmOjMJzjX-H6yEUHXF9Bqljj9B5LVZt83fgnQQR9asQ1LhiuamONAyen-8XqlbFUnRfccy_88tMT1CeExIzk5Ssudtehua5HlKbOWoVw4qYmwUKZwRa-KnAJlz9dW0dVT2nUAJZDAg9PCLh0V24Z9QctWt7qncDhhhf8wytKKho-A8QJp9lO5F2IDU6lPn1Dz8Pb3ZgE0-Ctc7Ns6n51njFl9i1MjhcwihTLLn3NLFDr4Kl1d_m_4o_JPnL9UYjIHVWAW1x3nIu1bbk2zEb5Mk13VjBtY-NpnUEWm_h3Hsy2Lqp28bcWov5mcD5CY-W4VJGPiQKYWI9BwQLIA63_Yye6MzLVR9BfJ2NIQ4emtbwsd29hKC0GvdYfFr0=w1280-no"><img src="https://lh3.googleusercontent.com/2bUKaHYAtkkYeiSMBWZ28iKH-8m2KdQoqIj8KtMMpzRgwX51kWDHyfpnRfQrfuyJ3z1nM21aOghz0pcLM5DLXS-TFC5MzplhvRtYNFiPsOKINgzEPf90QhMyzOHymUQSjk-8efu1Cjw2FU_L47B0GmG2zXEb2M-tgNgkEqLNlHF9OqgWwmOjMJzjX-H6yEUHXF9Bqljj9B5LVZt83fgnQQR9asQ1LhiuamONAyen-8XqlbFUnRfccy_88tMT1CeExIzk5Ssudtehua5HlKbOWoVw4qYmwUKZwRa-KnAJlz9dW0dVT2nUAJZDAg9PCLh0V24Z9QctWt7qncDhhhf8wytKKho-A8QJp9lO5F2IDU6lPn1Dz8Pb3ZgE0-Ctc7Ns6n51njFl9i1MjhcwihTLLn3NLFDr4Kl1d_m_4o_JPnL9UYjIHVWAW1x3nIu1bbk2zEb5Mk13VjBtY-NpnUEWm_h3Hsy2Lqp28bcWov5mcD5CY-W4VJGPiQKYWI9BwQLIA63_Yye6MzLVR9BfJ2NIQ4emtbwsd29hKC0GvdYfFr0=w1280-no" alt="File Cache" /></a></p>
<p><a href="https://lh3.googleusercontent.com/jP_dIgQNFaqsdr3AIPVAIaVx88yAZg4RlkdsSm2P25Yp1BysGikuF2gVfOYktqHaeLASYocPUQJD4idBrWxLOdA6K1LcBVDO_YLD1XBDlOxr1jmxpC3ZRJfKly_zx0enSQ_om0OqVl4yR7Yme3RxmfuP9mnNP2jVAKr85452QLiMnVAzvE0vg1w7KAQTqBnL33RJt6eeFdsXiK8CnO_dv1JIRHAqX39vYv7caJsTf5DLU0gTOYwlSMc8gEtPjnEOD7POE1_8m5w6uhXVbxFFTq_laaOjOk64wnOXUDK_HF9v_oc-pe-fqyhHqBu-wN5WAWYg1RkxwN_IXlTZ8FngqVQuG6nSCpVY5YcyaUpPlOOa-qNp6k-RZ8gQacDagVBlBnNCWS3Rofo7ml-Z4DAANGcOJsXFUNuHGcbhPvZAFniYqa9D2Y7Afek9iACzqeKWIUvuKemzaZY1yu4Iye8dyztyq833l0Ww7mGQOaFol5Nuz9KiI5SJSB53yi0btMk8PLcWxBzgVOJAHpECoX47Cmv3ruim5IweDRYKMp3nDhk=w1280-no"><img src="https://lh3.googleusercontent.com/jP_dIgQNFaqsdr3AIPVAIaVx88yAZg4RlkdsSm2P25Yp1BysGikuF2gVfOYktqHaeLASYocPUQJD4idBrWxLOdA6K1LcBVDO_YLD1XBDlOxr1jmxpC3ZRJfKly_zx0enSQ_om0OqVl4yR7Yme3RxmfuP9mnNP2jVAKr85452QLiMnVAzvE0vg1w7KAQTqBnL33RJt6eeFdsXiK8CnO_dv1JIRHAqX39vYv7caJsTf5DLU0gTOYwlSMc8gEtPjnEOD7POE1_8m5w6uhXVbxFFTq_laaOjOk64wnOXUDK_HF9v_oc-pe-fqyhHqBu-wN5WAWYg1RkxwN_IXlTZ8FngqVQuG6nSCpVY5YcyaUpPlOOa-qNp6k-RZ8gQacDagVBlBnNCWS3Rofo7ml-Z4DAANGcOJsXFUNuHGcbhPvZAFniYqa9D2Y7Afek9iACzqeKWIUvuKemzaZY1yu4Iye8dyztyq833l0Ww7mGQOaFol5Nuz9KiI5SJSB53yi0btMk8PLcWxBzgVOJAHpECoX47Cmv3ruim5IweDRYKMp3nDhk=w1280-no" alt="Proses Instalasi" /></a></p>
<p><a href="https://lh3.googleusercontent.com/OuEs6hdM9KDKBxKHNHt_UOtUiXLQWmWU0nxGTZKBnmD87onTM6BrPTPv03TwzY0llz2QRtwhf5TH-Ftao042eeeWsDwWdKamfp7QdBecfPl_eN2D8_wbV5QP-vcFW_Stw0lXwEKntFhNhIiMNTZSRjYqgSrPW7io3hU6IjUajjI8hpoIk4MyEKyPKWKXMQa3QKHPVjDiglzYbIxOFa7ZYmIgRik5ruIvu-9QrtvzHb4rzXvJu09FWG07laH9gSO7Osg1UUqOriEZgKfvNyEXrDCP-H4Yn2rnbkPskEKh7hQURjfnfP8pJxBYZkUzDMZLWxOP1dZ3F9sK803Q4R8brRWGkXAIIMk3w6re1PexEyGXcbniJbOnq-c4z0t2gIxv_n8SZCu5UYWwI0CsyawOi2qGTxIWZWaoTBzUSBCvHPVOkogkv5Ka1f1p7g7Wvg5SYWv_U0eupy27giNCjzW-fTo5WvLOqmnMeQP2PAVItbg8OLZa_x7puujt_03bsoAI9Pn9W743HXNYhMZwLVjoEyASEcRGpT3ViETYsRuqHsw=w1280-no"><img src="https://lh3.googleusercontent.com/OuEs6hdM9KDKBxKHNHt_UOtUiXLQWmWU0nxGTZKBnmD87onTM6BrPTPv03TwzY0llz2QRtwhf5TH-Ftao042eeeWsDwWdKamfp7QdBecfPl_eN2D8_wbV5QP-vcFW_Stw0lXwEKntFhNhIiMNTZSRjYqgSrPW7io3hU6IjUajjI8hpoIk4MyEKyPKWKXMQa3QKHPVjDiglzYbIxOFa7ZYmIgRik5ruIvu-9QrtvzHb4rzXvJu09FWG07laH9gSO7Osg1UUqOriEZgKfvNyEXrDCP-H4Yn2rnbkPskEKh7hQURjfnfP8pJxBYZkUzDMZLWxOP1dZ3F9sK803Q4R8brRWGkXAIIMk3w6re1PexEyGXcbniJbOnq-c4z0t2gIxv_n8SZCu5UYWwI0CsyawOi2qGTxIWZWaoTBzUSBCvHPVOkogkv5Ka1f1p7g7Wvg5SYWv_U0eupy27giNCjzW-fTo5WvLOqmnMeQP2PAVItbg8OLZa_x7puujt_03bsoAI9Pn9W743HXNYhMZwLVjoEyASEcRGpT3ViETYsRuqHsw=w1280-no" alt="Selesai" /></a></p>
<p>Sampai di sini, instalasi Git sebetulnya sudah selesai. Tapi kita perlu membuat pasangan public dan private key supaya bisa mengakses Github, Bitbucket, atau provider lain melalui protokol SSH. Dengan menggunakan protokol SSH, kita tidak perlu mengetik username/password setiap melakukan sinkronisasi dengan remote repository.</p>
<h3 id="membuat-key-pair">Membuat Key Pair</h3>
<p>Pertama, kita jalankan dulu Git Bash. Ini akan membuka command prompt khusus yang disediakan Git.</p>
<p><a href="https://lh3.googleusercontent.com/pa5U80HlXvvEfbO8MVF_lu4P4Rk9shKbqjQYVS-JSZmfTb2iGRl79b1uRhQWWAQFOE7w8xCrmFXEAZC4qqR60bqpSLJzDkWHQFs-osdBjtBRyC8BvTKTwRMMvQoV2aht3iPBuPqosdQJe34Gb0uUK0tFGswJah70Fq29upDd9k6GOB7VuM1FRL9yiRMR5hFNL9BFmnBM4DGIEsSDZbR0SqgCYkBba48ZP1jwdye7N9TmMVx5hkgF7itHifzTFyIiKFEhz3MTYOaPk3hIwRIKuG5ip39aNne05y92HKxIJ5yaXNfTU92P4gHE7vjx80RxV9MGOYgPIcaF64wAGV8Jz8v9yC72-Ysj7fwskEJNa9V_hAgrEDztiwdNMiyxF-E8IHadWYzvhlejzZxMAV_-pC_XhWUOw2zXAfYy6TP0frptk4nb6ZantQuKgR58pS9klCwZeu6CxQ6mlR_AYEI_p8mBJORRSJmXhPXww_LcAEk8oD7i5dJIlSZY2t7OWPH9QlukliVLti0l4-1-u82KTXOnzfw3WOHI5uqpX8W2puQ=w1280-no"><img src="https://lh3.googleusercontent.com/pa5U80HlXvvEfbO8MVF_lu4P4Rk9shKbqjQYVS-JSZmfTb2iGRl79b1uRhQWWAQFOE7w8xCrmFXEAZC4qqR60bqpSLJzDkWHQFs-osdBjtBRyC8BvTKTwRMMvQoV2aht3iPBuPqosdQJe34Gb0uUK0tFGswJah70Fq29upDd9k6GOB7VuM1FRL9yiRMR5hFNL9BFmnBM4DGIEsSDZbR0SqgCYkBba48ZP1jwdye7N9TmMVx5hkgF7itHifzTFyIiKFEhz3MTYOaPk3hIwRIKuG5ip39aNne05y92HKxIJ5yaXNfTU92P4gHE7vjx80RxV9MGOYgPIcaF64wAGV8Jz8v9yC72-Ysj7fwskEJNa9V_hAgrEDztiwdNMiyxF-E8IHadWYzvhlejzZxMAV_-pC_XhWUOw2zXAfYy6TP0frptk4nb6ZantQuKgR58pS9klCwZeu6CxQ6mlR_AYEI_p8mBJORRSJmXhPXww_LcAEk8oD7i5dJIlSZY2t7OWPH9QlukliVLti0l4-1-u82KTXOnzfw3WOHI5uqpX8W2puQ=w1280-no" alt="Menu Git Bash" /></a></p>
<p>Selanjutnya, jalankan perintah <code class="language-plaintext highlighter-rouge">ssh-keygen</code>. Kita akan ditanyai nama file yang akan dibuat. Ikuti saja defaultnya, yaitu <code class="language-plaintext highlighter-rouge">id_rsa</code> untuk private key, dan <code class="language-plaintext highlighter-rouge">id_rsa.pub</code> untuk public key.</p>
<p>Kita juga akan dimintai password untuk membuka private key. Tekan <code class="language-plaintext highlighter-rouge">Enter</code> bila tidak mau pakai password.</p>
<p><a href="https://lh3.googleusercontent.com/09whf_RJXX4AEtq89gdeRNhBQLPkjREzi66J4cCRjwL16trH893IU8wIvNwlyNAQXXmAcqhs8-3cavgncnq4up5A-nDTKZ6vFWCwrS7smTrj1R8Kj0MrkYjpHrUltCi30XW-Hj2vyZw0d7_yuyWWvyGzh2UZoihsuJYhESTlslwbhPEVYv2a68A2IVrORm77o0Jjlxx3WqaPiP4q0vmEjEhdtzTdNaKjCov8SBrcSOELmfZ_pb1lQ3OIOCUjej2vjuo3qx-twFHb-KFdAdR37vmhQ7HNS62_tigXg8r3b9ZDJk-Tb22bPBDqARiRXY8H7F_KgsHcRwm-XuCXRZi4jnTcBz8w1oSB4UW4HzXb6wtUyZsq4Pqlk68WsoDxuzG2aAJxwFgNqHHzhSpa42ZtQm0BtWx3DK7anfWBLZJTkyVI3Rser8GNbQ5otZTT0c2Yn0gDp7r4BD7KeSykKLLmUWQSe7QAkd3Yq5MPJL1AXNM79jlzeASLxcOffYYOd9mF2lgx35JFKs23fg9V8n_3irqvsX5bjzHUs_MPX8ylTik=w1280-no"><img src="https://lh3.googleusercontent.com/09whf_RJXX4AEtq89gdeRNhBQLPkjREzi66J4cCRjwL16trH893IU8wIvNwlyNAQXXmAcqhs8-3cavgncnq4up5A-nDTKZ6vFWCwrS7smTrj1R8Kj0MrkYjpHrUltCi30XW-Hj2vyZw0d7_yuyWWvyGzh2UZoihsuJYhESTlslwbhPEVYv2a68A2IVrORm77o0Jjlxx3WqaPiP4q0vmEjEhdtzTdNaKjCov8SBrcSOELmfZ_pb1lQ3OIOCUjej2vjuo3qx-twFHb-KFdAdR37vmhQ7HNS62_tigXg8r3b9ZDJk-Tb22bPBDqARiRXY8H7F_KgsHcRwm-XuCXRZi4jnTcBz8w1oSB4UW4HzXb6wtUyZsq4Pqlk68WsoDxuzG2aAJxwFgNqHHzhSpa42ZtQm0BtWx3DK7anfWBLZJTkyVI3Rser8GNbQ5otZTT0c2Yn0gDp7r4BD7KeSykKLLmUWQSe7QAkd3Yq5MPJL1AXNM79jlzeASLxcOffYYOd9mF2lgx35JFKs23fg9V8n_3irqvsX5bjzHUs_MPX8ylTik=w1280-no" alt="Generate public dan private key" /></a></p>
<p>Selanjutnya, kita akan menemui kedua file private dan public key di folder <code class="language-plaintext highlighter-rouge">C:\Users\namauser\.ssh</code> seperti ini.</p>
<p><a href="https://lh3.googleusercontent.com/DmSVC2raFeIldhAQhrHh8dcLHvb5eS4k1xb3pfh7w_3KGqhx0bMebWmsI76AKV6-wCerMrHo-FJ1TXIjyIP5XB6ZymDiB5D3I5cEzItJjZBeAeIxdACWfZGKy_Gjjp9ZfVVd5z-nKn4tx-p8oQSqEq8zSSH6EqfW3k5YVAj5OfHZnp68KsFiBJqnVObGAQLSR7ByUgut9cq1KhKNNBebeZJ6FioXVq9F2lg-hDt4wmwpHjitIoiJawpQ9HWA64PNg_boo6GqHJy7JAcCP7usGy0ljA5BJk5mjALa4R22ojGXp_1LvfnwWVO8G4cuJ33s6m7xf6Qp99vDRkmhCdC3tKKseN_qNBvSgHbr2XDxKkQZBUUqowWREZEIdMMBKQpLOBYZ-jg-XBKJDrd2ZLt0oVoctqfoGKW86V1vZhNhdvfZ4rSP_VwG19CAqW-FY_KKmR1Y-MNf1ftLyHEh3fRpPoADJTf_X2inWS2-rghwcDKupCFhrNSr1VVMxkarfySNrrxLutrrcLnnu-VMoIU3fvZRszShbhGY00gIG2Mwqt4=w1280-no"><img src="https://lh3.googleusercontent.com/DmSVC2raFeIldhAQhrHh8dcLHvb5eS4k1xb3pfh7w_3KGqhx0bMebWmsI76AKV6-wCerMrHo-FJ1TXIjyIP5XB6ZymDiB5D3I5cEzItJjZBeAeIxdACWfZGKy_Gjjp9ZfVVd5z-nKn4tx-p8oQSqEq8zSSH6EqfW3k5YVAj5OfHZnp68KsFiBJqnVObGAQLSR7ByUgut9cq1KhKNNBebeZJ6FioXVq9F2lg-hDt4wmwpHjitIoiJawpQ9HWA64PNg_boo6GqHJy7JAcCP7usGy0ljA5BJk5mjALa4R22ojGXp_1LvfnwWVO8G4cuJ33s6m7xf6Qp99vDRkmhCdC3tKKseN_qNBvSgHbr2XDxKkQZBUUqowWREZEIdMMBKQpLOBYZ-jg-XBKJDrd2ZLt0oVoctqfoGKW86V1vZhNhdvfZ4rSP_VwG19CAqW-FY_KKmR1Y-MNf1ftLyHEh3fRpPoADJTf_X2inWS2-rghwcDKupCFhrNSr1VVMxkarfySNrrxLutrrcLnnu-VMoIU3fvZRszShbhGY00gIG2Mwqt4=w1280-no" alt="Lokasi file public dan private key" /></a></p>
<p>Kedua file bisa dibuka dengan text editor. Yang perlu kita buka hanyalah public key saja.</p>
<p><a href="https://lh3.googleusercontent.com/4VkMJu88-Cqdoyv28P29M3i4pn5CABQrm9pvq4xDTgucFAvemvvfirjZmz-NbNGb1XJdqVcIZyuN=w1280-no"><img src="https://lh3.googleusercontent.com/4VkMJu88-Cqdoyv28P29M3i4pn5CABQrm9pvq4xDTgucFAvemvvfirjZmz-NbNGb1XJdqVcIZyuN=w1280-no" alt="Public key dibuka dengan Notepad" /></a></p>
<p>Isi public key ini nantinya akan kita pasang di Github</p>
<h2 id="clone-dari-github">Clone dari Github</h2>
<p>Untuk bisa clone dari github, pertama kali kita harus punya account Github. Silahkan <a href="https://github.com/plans">daftar dulu</a>.</p>
<p>Setelah punya account, login, dan kita akan melihat dashboard.</p>
<p><a href="/images/uploads/2011/01/01-github-dashboard-300x208.png"><img src="/images/uploads/2011/01/01-github-dashboard-300x208.png" alt=" " /></a></p>
<p>Klik account setting, dan masuk ke menu SSH Public Keys</p>
<p><a href="/images/uploads/2011/01/02-github-add-pubkey-300x206.png"><img src="/images/uploads/2011/01/02-github-add-pubkey-300x206.png" alt=" " /></a></p>
<p>Pastekan public key yang sudah kita generate pada langkah sebelumnya. Setelah diadd, public key kita akan terdaftar. Kita boleh pasang public key banyak-banyak, karena biasanya satu public key mencerminkan satu komputer. Bisa saja kita punya PC dan juga Laptop.
<a href="/images/uploads/2011/01/03-github-add-pubkey-completed-300x201.png"><img src="/images/uploads/2011/01/03-github-add-pubkey-completed-300x201.png" alt=" " /></a></p>
<h2 id="setting-public-key-di-bitbucket">Setting Public Key di Bitbucket</h2>
<p>Selain Github, provider lain yang juga populer adalah <a href="https://bitbucket.org">Bitbucket</a>. Untuk mendaftarkan public key di Bitbucket, kita bisa akses menu di kanan atas, yang ada gambar avatar kita.</p>
<p><a href="https://lh3.googleusercontent.com/M6IG6mrZAf6wb6JGb7Yu1pkRWQa1rQSVqHRjIdkTF0gM1N2aGDm0veQ_0IgnzZbNrpJnTgRCZauL=w269-h317-no"><img src="https://lh3.googleusercontent.com/M6IG6mrZAf6wb6JGb7Yu1pkRWQa1rQSVqHRjIdkTF0gM1N2aGDm0veQ_0IgnzZbNrpJnTgRCZauL=w269-h317-no" alt="Menu Kanan Atas Bitbucket" /></a></p>
<p>Pilih Bitbucket Settings, kita akan mendapatkan halaman Settings</p>
<p><a href="https://lh3.googleusercontent.com/CZ1FnMFZ4Yk_9tsKmYw0MGQ5DmKNNxlVqj3ZcrUiY2EIt0jImm9gAkHJJmKyRVWH7L7D55hxBozI=w714-h672-no"><img src="https://lh3.googleusercontent.com/CZ1FnMFZ4Yk_9tsKmYw0MGQ5DmKNNxlVqj3ZcrUiY2EIt0jImm9gAkHJJmKyRVWH7L7D55hxBozI=w714-h672-no" alt="Bitbucket Setting" /></a></p>
<p>Klik SSH Keys di kiri bawah. Kita akan mendapati halaman untuk menambah SSH Keys.</p>
<p><a href="https://lh3.googleusercontent.com/s3V2zo8Ccn12qgV8amcnNe65BDuGhLnhgvj92kZ8W2foI-NcV5b3HC3eKbefMNaKBnDXT_rmPLkd=w1078-h341-no"><img src="https://lh3.googleusercontent.com/s3V2zo8Ccn12qgV8amcnNe65BDuGhLnhgvj92kZ8W2foI-NcV5b3HC3eKbefMNaKBnDXT_rmPLkd=w1078-h341-no" alt="Daftar SSH Key" /></a></p>
<p>Klik Add Key untuk menambah public key SSH. Selanjutnya, copy dan paste isi public key kita ke kotak yang disediakan.</p>
<p><a href="https://lh3.googleusercontent.com/yIwCYUr2bh9mLNOjudYorK-vu7PX7ZdlssLu3MKiKzi9itJ3hvwjqnlUeaw0ZFo09nLeJepwdR1T=w811-h501-no"><img src="https://lh3.googleusercontent.com/yIwCYUr2bh9mLNOjudYorK-vu7PX7ZdlssLu3MKiKzi9itJ3hvwjqnlUeaw0ZFo09nLeJepwdR1T=w811-h501-no" alt="Copy Paste Key" /></a></p>
<p>Klik Save, dan public key kita sudah terdaftar</p>
<p><a href="https://lh3.googleusercontent.com/qRNOGpqSMq2UB6bfCQi6qubgglZ8czLaDVBgXt0WsuXsXMrNMGgbofjk0eqG8dRclZYV4wZLYxrC=w1074-h346-no"><img src="https://lh3.googleusercontent.com/qRNOGpqSMq2UB6bfCQi6qubgglZ8czLaDVBgXt0WsuXsXMrNMGgbofjk0eqG8dRclZYV4wZLYxrC=w1074-h346-no" alt="Daftar SSH Key Baru" /></a></p>
<p>Kita juga akan mendapatkan notifikasi lewat email bahwa ada SSH key baru yang didaftarkan</p>
<p><a href="https://lh3.googleusercontent.com/FlHecTtZY4ZfSw0LA3QAiNu6GmN4V-oiHA_GDirwdvz3oBSEbfEhy0hmtCmQ5GbYfPcr1GVWOIl7=w878-h386-no"><img src="https://lh3.googleusercontent.com/FlHecTtZY4ZfSw0LA3QAiNu6GmN4V-oiHA_GDirwdvz3oBSEbfEhy0hmtCmQ5GbYfPcr1GVWOIl7=w878-h386-no" alt="Notifikasi Email" /></a></p>
<h2 id="mengunduh-isi-repository">Mengunduh Isi Repository</h2>
<p>Setelah public key didaftarkan, selanjutnya kita lihat repository yang kita punya.
<a href="/images/uploads/2011/01/04-github-repositories-300x183.png"><img src="/images/uploads/2011/01/04-github-repositories-300x183.png" alt=" " /></a></p>
<p>Kalau belum punya repository, Anda bisa fork <a href="https://github.com/endymuhardin/belajarGit">repository belajarGit punya saya</a>, sehingga nanti Anda punya repo belajarGit sendiri.</p>
<p>Perintahnya adalah sebagai berikut (jalankan dari command line)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:endymuhardin/belajarGit.git
</code></pre></div></div>
<p>Sekarang repository sudah ada di local, dan siap digunakan. Bagaimana cara menggunakannya, stay tuned. Akan dibahas di posting berikutnya.</p>
Migrasi Subversion ke Git2011-01-03T06:02:23+07:00https://software.endy.muhardin.com/aplikasi/migrasi-subversion-ke-git<p>Di tahun yang baru ini, kami di ArtiVisi juga beralih menggunakan version control baru, yaitu Git.
Menggantikan Subversion yang sudah kita gunakan sejak 2008.</p>
<p>Ada banyak keunggulan Git dibandingkan Subversion, diantaranya:</p>
<ul>
<li>
<p>offline operation. Git adalah distributed/decentralized version control system (DVCS), artinya tidak membutuhkan server terpusat untuk bisa bekerja. Keunggulan ini berakibat pada keunggulan berikutnya, yaitu:</p>
</li>
<li>
<p>commit sesuai task, bukan sesuai koneksi internet. Dulu, karena koneksi internet terbatas, programmer commit hanya pada saat ada internet. Akibatnya satu commit berisi perubahan untuk banyak task, tercampur aduk tidak jelas peruntukannya. DVCS memungkinkan programmer untuk commit walaupun tidak ada koneksi internet, dan melakukan sinkronisasi pada saat offline.</p>
</li>
<li>
<p>fitur staging area di Git, memungkinkan kita untuk mengatur isi commit secara detail.</p>
</li>
<li>
<p>fitur rebase untuk mengatur commit. Commit yang teratur akan memudahkan review.</p>
</li>
<li>
<p>branch dan merge yang lebih baik daripada subversion. Karena branch dan merge mudah, maka kita bisa menerapkan berbagai teknik workflow dalam mengelola development.</p>
</li>
<li>
<p>website social coding. Github dan Gitorious merupakan Facebook-nya para programmer. Untuk bisa terlibat di dalamnya, terlebih dulu kita harus bisa Git.</p>
</li>
</ul>
<p>Selain Git, ada juga DVCS lain seperti Mercurial (hg), Bazaar (bzr), dsb. Git dipilih karena :</p>
<ul>
<li>
<p>popularitas. Semakin populer, tutorial dan aplikasi pendukung semakin banyak, sehingga semakin nyaman digunakan. Saat ini yang paling populer cuma dua, yaitu git dan hg.</p>
</li>
<li>
<p>local/named branch. Ini fitur penting, tapi entah kenapa tidak ada di core hg. Sepertinya ada di extension, tapi yang jelas merupakan workaround dan bukan didesain sejak awal. Tanpa named branch, pilihan workflow menjadi terbatas.</p>
</li>
<li>
<p>Social coding Git (Github dan Gitorious) jauh lebih superior daripada Mercurial (Bitbucket)</p>
</li>
</ul>
<p>Beberapa faktor di atas adalah alasan kenapa Git yang dipilih.</p>
<p>Baiklah, sekarang saatnya migrasi. Kita akan mengkonversi repository Subversion menjadi repository Git.
Berikut langkah-langkah yang akan kita lakukan:</p>
<ol>
<li>
<p>Dump repository Subversion</p>
</li>
<li>
<p>Restore lagi di laptop supaya cepat</p>
</li>
<li>
<p>Buat authorsfile</p>
</li>
<li>
<p>Buat ignore file</p>
</li>
<li>
<p>Clone tanpa metadata</p>
</li>
<li>
<p>Konversi branch</p>
</li>
<li>
<p>Konversi tags</p>
</li>
<li>
<p>Clone hasil konversi menjadi bare repository</p>
</li>
</ol>
<h2 id="dump-repository-subversion">Dump repository Subversion</h2>
<p>Seperti biasa, sebelum melakukan apapun, lakukan backup dulu. Just in case.
Perintahnya gampang.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>svnadmin dump /path/ke/repository | bzip2 -c9 > dump-repository-yyyyMMdd.dmp.bz2
</code></pre></div></div>
<h2 id="restore-lagi-di-laptoppc-supaya-cepat">Restore lagi di laptop/PC supaya cepat</h2>
<p>Langkah ini optional, kalau kita ingin melakukannya di komputer kita sendiri, bukan di server.
Tapi sebaiknya dilakukan, karena nanti kita akan checkout beberapa kali yang pasti membutuhkan waktu lama jika dilakukan ke server.</p>
<p>Perintah restore gampang.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bzcat dump-repository-yyyyMMdd.dmp.bz2 | svnadmin load /path/ke/repo/svn/di/lokal
</code></pre></div></div>
<h2 id="buat-authorsfile">Buat authorsfile</h2>
<p>Setelah kita memiliki repository Subversion, kita perlu mengambil daftar nama orang-orang yang pernah commit. Ini akan kita butuhkan pada waktu konversi. Nama committer ini diambil dari hasil checkout Subversion. Jadi mari kita checkout dulu.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>svn checkout file:///path/ke/repo/svn/di/lokal checkout-project-svn
</code></pre></div></div>
<p>Karena lokal, harusnya hanya membutuhkan beberapa menit saja.</p>
<p>Setelah dilakukan checkout, kita membutuhkan script untuk mengambil nama committer. Berikut isi scriptnya, simpan saja dengan nama extract-svn-authors.sh</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/usr/bin/env bash
authors=$(svn log -q | grep -e '^r' | awk 'BEGIN { FS = "|" } ; { print $2 }' | sort | uniq)
for author in ${authors}; do
echo "${author} = NAME <USER@DOMAIN>";
done
</code></pre></div></div>
<p>Jalankan script tersebut di dalam folder hasil checkout.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd checkout-project-svn
sh /path/ke/script/extract-svn-authors.sh > nama-committers.txt
</code></pre></div></div>
<p>Ini akan menghasilkan file nama-committers.txt yang berisi nama committer seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>endy = NAME <USER@DOMAIN>
</code></pre></div></div>
<p>Editlah file ini supaya mencerminkan nama dan email yang benar, seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>endy = Endy Muhardin <endy.muhardin@geemail.com>
</code></pre></div></div>
<h2 id="buat-ignore-file">Buat ignore file</h2>
<p>Dalam mengerjakan project, ada file-file yang ada di folder kerja, tapi tidak kita masukkan ke repository. Misalnya file hasil kompilasi, setting IDE, dan sebagainya. File dan folder hasil generate ini biasanya kita daftarkan di ignore list, supaya tidak ikut dicommit ke repository. Kita perlu mengkonversi format ignore di Subversion (svn property ignore) menjadi format ignore versi Git (yaitu file .gitignore).</p>
<p>Untuk membuatnya, kita clone dulu repository Subversion menjadi repository Git. Ini dilakukan di folder yang berbeda dengan hasil checkout Subversion di langkah sebelumnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ..
git svn clone --stdlayout -A nama-committers.txt file:///path/ke/repo git-svn-migrasi-project-dengan-metadata
</code></pre></div></div>
<p>Setelah diclone, konversi ignore list nya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd git-svn-migrasi-project-dengan-metadata
git svn show-ignore > .gitignore
</code></pre></div></div>
<h2 id="clone-tanpa-metadata">Clone tanpa metadata</h2>
<p>Selanjutnya, kita lakukan clone lagi. Kali ini tanpa menyertakan metadata, sehingga hasilnya bersih. Metadata ini digunakan bila kita ingin tetap commit ke repository Subversion, tapi ingin menggunakan Git sebagai frontend.</p>
<p>Perintahnya mirip seperti sebelumnya, kali ini kita tambahkan opsi tanpa metadata.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ..
git svn clone --no-metadata --stdlayout -A nama-committers.txt file:///path/ke/repo git-svn-migrasi-project-tanpa-metadata
</code></pre></div></div>
<p>Ini akan menghasilkan folder git-svn-migrasi-project-tanpa-metadata berisi repository Subversion yang sudah dikonversi menjadi repository Git. Semua langkah selanjutnya akan dilakukan di dalam folder ini.</p>
<p>Setelah selesai, kita masukkan file .gitignore ke repo Git yang baru ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd git-svn-migrasi-project-tanpa-metadata
cp ../git-svn-migrasi-project-dengan-metadata/.gitignore ./
git add .
git commit -m "add ignore list"
</code></pre></div></div>
<h2 id="konversi-branch">Konversi branch</h2>
<p>Branch yang ada di Subversion harus kita konversi menjadi branch di Git.
Berikut perintahnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git branch -r | grep -v tags | sed -rne 's, *([^@]+)$,\1,p' | while read branch; do echo "git branch $branch $branch"; done | sh
</code></pre></div></div>
<p>Verifikasi hasilnya dengan perintah ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git branch -a
</code></pre></div></div>
<p>Seharusnya semua branch yang ada di repository Subversion akan terlihat di dalam repository Git ini.</p>
<h2 id="konversi-tags">Konversi tags</h2>
<p>Lakukan perintah berikut untuk mengkonversi tag Subversion menjadi tag Git.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git branch -r | sed -rne 's, *tags/([^@]+)$,\1,p' | while read tag; do echo "git tag $tag 'tags/${tag}^'; git branch -r -d tags/$tag"; done | sh
</code></pre></div></div>
<p>Verifikasi dengan perintah ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git tag -l
</code></pre></div></div>
<p>Pastikan semua tag yang tadinya ada di repository Subversion sudah terdaftar di repository Git.</p>
<h2 id="clone-hasil-konversi-menjadi-bare-repository">Clone hasil konversi menjadi bare repository</h2>
<p>Setelah nama committer, ignore list, branch, dan tags berhasil kita pindahkan, inilah langkah terakhir. Kita clone sekali lagi menjadi repository bare supaya bisa dishare dengan orang lain. Biasanya repository bare ini kita publish dengan Gitosis, gitweb, atau aplikasi server lainnya.</p>
<p>Perintah ini dilakukan di luar repository Git yang kita gunakan pada langkah sebelumnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ..
git clone --bare git-svn-migrasi-project-tanpa-metadata nama-project.git
</code></pre></div></div>
<p>Ini akan menghasilkan satu folder dengan nama nama-project.git berisi repository Git yang siap dishare.</p>
<p>Demikian posting tahun baru. Semoga kita semua lebih sukses di tahun 2011 ini.</p>
Prototyping2010-07-07T01:41:17+07:00https://software.endy.muhardin.com/manajemen/prototyping<p>Pada waktu kita melakukan requirement gathering, harapan kita adalah agar requirement yang kita dapatkan di fase requirement tidak jauh bergeser dari requirement final setelah project closing. Kalau pergeserannya jauh, akan mengakibatkan waktu dan biaya pengerjaan project menjadi molor dan akibatnya kedua belah pihak akan dirugikan.</p>
<p>Pergeseran requirement ini bisa disebabkan beberapa hal, misalnya :</p>
<ol>
<li>
<p>business analyst (BA) kurang pengalaman, sehingga tidak bisa mengidentifikasi varian-varian skenario. Akibatnya terjadi banyak ‘hidden requirement’</p>
</li>
<li>
<p>business analyst kurang teliti, sehingga salah memahami penjelasan user</p>
</li>
<li>
<p>Perubahan bisnis client, sehingga requirementnya juga berubah</p>
</li>
<li>
<p>perbedaan persepsi antara user dan analyst atau programmer</p>
</li>
</ol>
<p>Untuk masalah 1 dan 2, solusinya adalah dengan mengganti BA dengan orang yang lebih berpengalaman. Newbie sebaiknya tidak menjadi BA. Bolehlah magang BA, tapi jangan diandalkan untuk jadi BA utama.</p>
<p>Poin 3 juga biasanya tidak masalah. Client biasanya cukup tahu diri kalau terjadi hal ini, sehingga tidak keberatan dimintai charge tambahan.</p>
<p>Nah untuk poin 4, biasanya sulit dideteksi sampai aplikasi kita deliver. Sering terjadi, usernya OK OK saja pada fase requirement, dan tiba-tiba pada waktu kita deliver aplikasinya, dia langsung bingung karena aplikasinya ‘aneh’.</p>
<p>Agar poin 4 ini tidak terjadi, sebaiknya kita melakukan prototyping. Bagaimana cara melakukan prototyping yang baik?</p>
<p>Prototyping itu idealnya :</p>
<ol>
<li>
<p>Murah meriah dan cepat
Dalam 1 hari kita harus bisa menggambar minimal 10 screen.
Begitu usernya selesai ngomong/gambar di papan tulis, screennya juga harus langsung jadi.
Jangan sampai effort untuk prototyping lebih besar dari effort untuk coding.</p>
</li>
<li>
<p>Gampang diubah
Tujuan prototype adalah supaya user bisa merasakan seperti apa aplikasinya nanti.
Kalau dirasakan kurang sesuai, tentunya dia ingin mengubahnya.
Nah, jangan sampai minta geser tombol aja harus nunggu 30 menit.</p>
</li>
<li>
<p>Mirip aslinya.
Kalo ini lebih ke sisi development.
Biar efisien, begitu prototype sign off, kita bisa mulai paralel coding dan bikin user manual.
Kalo prototypenya udah bener2 mirip, bisa langsung discreenshot dan dipasang di user manual.
Jadi begitu aplikasi jadi, user manual juga selesai.</p>
</li>
<li>
<p>Terlihat belum selesai
Ini agak kontradiktif dengan tips #3. Kalau prototype kita sangat mirip aplikasi betulan, client akan memiliki persepsi bahwa aplikasinya sudah hampir selesai. Padahal belum ada coding sama sekali. Oleh karena itu, sangat penting kita tekankan ke client bahwa masih ada jangka waktu yang lama sebelum aplikasi betulannya selesai.</p>
</li>
</ol>
<p>Ada beberapa tools yang bisa digunakan untuk prototyping, yaitu</p>
<h2 id="netbeans"><a href="http://www.netbeans.org">Netbeans</a></h2>
<p>Untuk project aplikasi desktop, inilah yang biasa kami gunakan di ArtiVisi. Screen dapat dibuat dengan sangat cepat, lengkap dengan behavior standar seperti popup dialog, scroll table, dsb.</p>
<p>Untuk project web, biasanya kita langsung coding di HTML dan Dojo, tentunya tanpa koneksi ke back end.</p>
<p>Sebagai nilai tambah lain, setelah prototype di-approve client, programmer bisa langsung meneruskan coding.</p>
<h2 id="balsamiq-mockup"><a href="http://www.balsamiq.com/products/mockups">Balsamiq Mockup</a></h2>
<p>Tools ini berbayar dan dijalankan menggunakan Adobe AIR.</p>
<p><a href="http://pencil.evolus.vn">Pencil</a>
Tools ini lumayan bagus, dijalankan sebagai Firefox Add Ons. Sudah ada palette untuk berbagai UI component seperti combo box, text area, dsb. Setelah selesai menggambar, kita bisa langsung mengekspornya menjadi image.</p>
<p>Berikut contoh mockup yang baru saja saya buat menggunakan Pencil.</p>
<p>Ini versi ‘bagus’ yang mirip aslinya.</p>
<p><a href="/images/uploads/2010/07/windows_login-300x238.png"><img src="/images/uploads/2010/07/windows_login-300x238.png" alt="Mockup Login Screen ala Windows XP " /></a></p>
<p>Supaya client sadar bahwa ini adalah prototype, kita bisa gunakan versi yang coret-coretan.</p>
<p><a href="https://lh4.googleusercontent.com/-1hcFb859pOI/U1acWK5Y8YI/AAAAAAAAFqw/KxhM_jXmybE/w771-h474-no/entri_anggota.png"><img src="https://lh4.googleusercontent.com/-1hcFb859pOI/U1acWK5Y8YI/AAAAAAAAFqw/KxhM_jXmybE/w771-h474-no/entri_anggota.png" alt="Mockup versi coretan" /></a></p>
<p>Source file untuk mockup di atas bisa diunduh <a href="../../downloads/aplikasi-perpustakaan.ep">di sini</a>.</p>
<p>Demikianlah sedikit tips dan trik. Semoga bermanfaat.</p>
Mendebug Database Production2010-06-30T14:44:11+07:00https://software.endy.muhardin.com/java/mendebug-database-production<p>Suatu aplikasi, walaupun sudah go-live di environment production, tetap bisa saja mengalami error dan bug. Bug ini seringkali tidak ditemukan di environment development karena berbagai hal, misalnya variasi data, jumlah data, dan sebagainya.</p>
<p>Langkah pertama ketika kita mengetahui ada bug tentunya adalah melokalisir masalah. Pada kondisi mana saja bug tersebut muncul. Setelah itu, kita dapat memfokuskan pencarian masalah di lokasi tersebut. Ini lebih efisien daripada kita harus menelusuri keseluruhan sistem.</p>
<p>Misalnya kita sudah berhasil melokalisir masalah, yaitu transaksi di bulan tertentu. Langkah selanjutnya adalah memindahkan data production di lokasi tersebut ke environment development. Ini kita lakukan supaya kita bebas bereksperimen dengan data tersebut tanpa khawatir membahayakan data production.</p>
<p>Masalahnya, tools backup database yang tersedia biasanya tidak bisa digunakan untuk mengambil sebagian data. Walaupun bisa (mysqldump menyediakan opsi where untuk membatasi record yang diambil), biasanya terbatas hanya di satu tabel saja. Sedangkan untuk bisa merestore-nya di development, kita butuh semua relasinya.</p>
<p>Sebagai contoh, coba lihat skema berikut.</p>
<p><a href="/images/uploads/2010/06/diagram-er-payment.png"><img src="/images/uploads/2010/06/diagram-er-payment.png" alt=" " /></a></p>
<p>Untuk mengambil data payment, tentunya kita juga harus menarik data lain yang berelasi dengannya, yaitu di tabel grup loket, loket, payment value, payment info dan fee loket. Ini sangat sulit dilakukan, apalagi kalau data payment tersebut jumlahnya ratusan ribu record.</p>
<p>Untunglah ada tools untuk mengatasi masalah ini, namanya <a href="http://jailer.sourceforge.net/">Jailer</a>. Dengan menggunakan Jailer, kita dapat menentukan tabel mana yang akan diambil datanya (payment), kriteria pengambilan (bulan tertentu saja), dan relasi mana saja yang ingin kita ambil. Hasilnya adalah satu set data lengkap dengan dependensinya yang bisa kita restore di development.</p>
<h2 id="persiapan-jailer">Persiapan Jailer</h2>
<p>Pertama, tentunya kita unduh dulu Jailer di websitenya. Jangan lupa teriakkan, “Hidup Open Source !!!”, karena aplikasi ini tersedia secara gratis berkat gerakan open source.</p>
<p>Setelah berhasil diunduh, extract ke folder tertentu. Jailer tidak menyertakan driver untuk koneksi ke database, sehingga kita harus sediakan sendiri. Karena saya menggunakan MySQL, saya masukkan file mysql-connector.jar ke dalam folder lib. Kita mengikutkan driver database ke folder Jailer karena nantinya folder ini akan kita pack dan upload ke server production.</p>
<p>Jailer ini akan kita jalankan di mesin development yang sudah terisi skema database. Kita akan coba dulu ambil data di development, kalau sudah sukses baru kita jalankan di production.</p>
<p>Ada dua script untuk menjalankan jailer, yaitu jailerGUI dan jailer. jailerGUI digunakan untuk mendesain pengambilan data, sedangkan jailer adalah antarmuka command line untuk menjalankan pengambilan data. Karena kita ingin mendesain proses pengambilannya, kita gunakan jailerGUI.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sh jailerGUI.sh
</code></pre></div></div>
<p>Berikut adalah tampilan awal Jailer.</p>
<p><a href="/images/uploads/2010/06/01-jailer-welcome-300x206.png"><img src="/images/uploads/2010/06/01-jailer-welcome-300x206.png" alt="Jailer Welcome Page " /></a></p>
<p>Jailer memberitahu kita bahwa belum ada data model yang bisa dikerjakan, dan menyarankan kita untuk menganalisa database. Klik Analyze Database. Selanjutnya Jailer akan meminta informasi cara koneksi ke database.</p>
<p><a href="/images/uploads/2010/06/02-analyze-db-300x134.png"><img src="/images/uploads/2010/06/02-analyze-db-300x134.png" alt="Analyze Database " /></a></p>
<p>Isikan informasi koneksi database dan driver yang digunakan. Driver yang kita gunakan adalah yang tadi sudah kita copy ke folder lib.</p>
<p><a href="/images/uploads/2010/06/03-mysql-connectorj-300x128.png"><img src="/images/uploads/2010/06/03-mysql-connectorj-300x128.png" alt="Konfigurasi MySQL " /></a></p>
<p>Klik OK untuk menganalisa database.</p>
<p><a href="/images/uploads/2010/06/04-connect-to-db-300x212.png"><img src="/images/uploads/2010/06/04-connect-to-db-300x212.png" alt="Koneksi ke database " /></a></p>
<p>Setelah itu, Jailer akan menghubungi database untuk mengambil informasi. Lognya akan ditampilkan di log output. Jailer akan memberi tahu kita tabel-tabel yang tidak ada primary keynya. Jailer tidak dapat memproses tabel tanpa primary key.</p>
<p><a href="/images/uploads/2010/06/05-autodetect-result-300x233.png"><img src="/images/uploads/2010/06/05-autodetect-result-300x233.png" alt="Hasil Autodetect " /></a></p>
<p>Klik tabel yang berwarna merah, dan definisikan primary keynya. Primary key yang kita definisikan di sini hanya digunakan Jailer, sehingga tidak perlu khawatir skema aslinya akan berubah.</p>
<p><a href="/images/uploads/2010/06/06-edit-table-pk-300x200.png"><img src="/images/uploads/2010/06/06-edit-table-pk-300x200.png" alt="Add Primary Key " /></a></p>
<p>Setelah itu, klik OK. Jailer akan menampilkan screen Extraction Model Editor. Pilih tabel payment, di dropdown subject, karena inilah tabel yang akan kita gunakan sebagai pusat extraction.</p>
<p><a href="/images/uploads/2010/06/07-payment-relasi-300x206.png"><img src="/images/uploads/2010/06/07-payment-relasi-300x206.png" alt="Pilih tabel yang ingin diimport " /></a></p>
<p>Jailer mendeteksi relasi antar tabel berdasarkan constraint foreign key yang kita pasang di database. Kadangkala ada tabel-tabel yang berelasi, namun tidak ada constraintnya. Entah karena malas mendefinisikan, atau memang sengaja tidak dikaitkan. Kita bisa mendaftarkan relasi tanpa constraint ini dengan membuka lagi Data Model Editor, kemudian klik Add di kotak Association sebelah kanan.</p>
<p><a href="/images/uploads/2010/06/08-relasi-non-fk-300x234.png"><img src="/images/uploads/2010/06/08-relasi-non-fk-300x234.png" alt="Relasi non Foreign Key " /></a></p>
<p>Setelah diklik OK, maka skema relasi di Extraction Model Editor akan berubah sesuai relasi yang ditambahkan. Sama dengan definisi primary key di atas, relasi ini hanya disimpan oleh Jailer dan tidak diaplikasikan ke skema database.</p>
<p><a href="/images/uploads/2010/06/09-payment-relasi-2-300x168.png"><img src="/images/uploads/2010/06/09-payment-relasi-2-300x168.png" alt="Relasi Payment - Loket " /></a></p>
<p>Kita perlu mendefinisikan batasan record payment yang akan diambil, yaitu yang terjadi di bulan Juni 2010. Dalam bentuk SQL, berikut adalah query yang digunakan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from payment where date_format(paid_date, '%Y-%m') = '2010-06'
</code></pre></div></div>
<p>Kita ambil expression setelah where dan pasang di textfield where dalam Extraction Model Editor.</p>
<p><a href="/images/uploads/2010/06/10-payment-where-300x206.png"><img src="/images/uploads/2010/06/10-payment-where-300x206.png" alt="Restriction " /></a></p>
<p>Simpan dulu extraction modelnya.</p>
<p><a href="/images/uploads/2010/06/11-save-extraction-300x208.png"><img src="/images/uploads/2010/06/11-save-extraction-300x208.png" alt="Save Extraction " /></a></p>
<p>Beri nama yang representatif, misalnya payment-201006. Jailer akan menyimpan extraction model ini dalam format csv. Kalau sudah memahami formatnya, kita bisa membuatnya dengan text editor tanpa GUI (kalau mau).</p>
<p>Setelah tersimpan, kita bisa klik Export Data sehingga memunculkan dialog berikut.</p>
<p><a href="/images/uploads/2010/06/12-data-export-300x283.png"><img src="/images/uploads/2010/06/12-data-export-300x283.png" alt="Export Data " /></a></p>
<p>Di screen tersebut kita bisa mengatur konfigurasi pengambilan data. Bagi saya, nilai defaultnya sudah memadai sehingga tidak ada yang diubah.</p>
<p>Di box paling bawah ada command line yang bisa kita copy-paste untuk dijalankan tanpa GUI. Copy saja isinya ke text file untuk digunakan nanti.</p>
<p>Yang harus kita isi di screen ini adalah textfield Into. Ini adalah nama file yang akan menampung script SQL berisi data yang diinginkan. Isi saja dengan nama payment-201006.sql.</p>
<p><a href="/images/uploads/2010/06/13-export-into-300x205.png"><img src="/images/uploads/2010/06/13-export-into-300x205.png" alt="Export Into File " /></a></p>
<p>Setelah itu, klik Export Data. Jailer akan segera bekerja dan menampilkan hasilnya dalam bentuk tree.</p>
<p><a href="/images/uploads/2010/06/14-export-result-300x168.png"><img src="/images/uploads/2010/06/14-export-result-300x168.png" alt="Hasil Export " /></a></p>
<p>Di situ kita bisa lihat berapa row yang akan diambil dari masing-masing tabel.
Seperti kita lihat, cukup signifikan, yaitu 2000an record. Ini disebabkan karena jailer mengambil record secara rekursif tanpa ada batasan.</p>
<p>Setelah dianalisa, kita hanya ingin mengambil tabel-tabel yang berkaitan langsung, yaitu payment, payment_info, payment_value, dan fee_loket. Sedangkan tabel sisanya dapat diabaikan karena bersifat pelengkap atau master data yang sudah ada di database development.</p>
<p>Dengan melihat ke tree-nya, kita bisa memutus relasi fee_loket ke loket, karena dari situlah semua data lain akan ikut terbawa.</p>
<p>Tutup screennya, dan kembali ke Extraction Model Editor.</p>
<p><a href="/images/uploads/2010/06/15-restricted-dependency-300x168.png"><img src="/images/uploads/2010/06/15-restricted-dependency-300x168.png" alt="Membatasi Relasi " /></a></p>
<p>Di kotak Association, expand node yang ingin kita putuskan, yaitu fee loket. Klik relasi loket, dan centang checkbox disabled di pojok kiri bawah. Setelah itu, jalankan lagi Export Data.</p>
<p><a href="/images/uploads/2010/06/16-restricted-dependency-warning-300x153.png"><img src="/images/uploads/2010/06/16-restricted-dependency-warning-300x153.png" alt="Warning Restricted Dependency " /></a></p>
<p>Jailer akan mengingatkan bahwa dengan membatasi dependensi, referential integrity akan rusak, karena relasi foreign key dari fee_loket ke loket akan terputus. Klik saja Yes, karena di database development kita tabel loket sudah terisi lengkap.</p>
<p>Inilah hasilnya</p>
<p><a href="/images/uploads/2010/06/17-restricted-export-result-300x82.png"><img src="/images/uploads/2010/06/17-restricted-export-result-300x82.png" alt="Hasil Export setelah dibatasi " /></a></p>
<p>Seperti kita lihat di atas, kita cuma mendapatkan 84 record dan pengambilan data berhenti di tabel fee_loket.
Periksa output payment-201006.sql di folder Jailer untuk memastikan hasilnya sudah benar.</p>
<p>Setelah sukses dijalankan di database development, compress lagi jailer yang sudah dimodifikasi barusan dan upload ke server production. Setibanya di server production, extract, kemudian jalankan script yang tadi kita copy-paste.</p>
<p>Kalau baru pertama kali dijalankan, script ini akan menimbulkan error sebagai berikut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./export-payment-201006.sh
2010-06-28 14:15:08,114 [main] INFO - Jailer 3.4.5
2010-06-28 14:15:08,117 [main] INFO - added 'lib/mysql-connector-java-5.1.6-bin.jar' to classpath
2010-06-28 14:15:08,119 [main] INFO - exporting 'extractionmodel/payment-201006.csv' to 'payment-201006.sql'
2010-06-28 14:15:08,700 [main] INFO - begin guessing SQL dialect
2010-06-28 14:15:08,711 [main] INFO - end guessing SQL dialect
2010-06-28 14:15:08,718 [main] ERROR - Can't find working tables! Run 'bin/jailer.sh create-ddl' and execute the DDL-script first!
java.lang.RuntimeException: Can't find working tables! Run 'bin/jailer.sh create-ddl' and execute the DDL-script first!
at net.sf.jailer.entitygraph.EntityGraph.create(EntityGraph.java:122)
at net.sf.jailer.Jailer.export(Jailer.java:1142)
at net.sf.jailer.Jailer.jailerMain(Jailer.java:1064)
at net.sf.jailer.Jailer.jailerMain(Jailer.java:989)
at net.sf.jailer.Jailer.main(Jailer.java:967)
Caused by: java.sql.SQLException: "Table 'ppobgsp_test.JAILER_GRAPH' doesn't exist" in statement "Insert into JAILER_GRAPH(id, age) values (2104021762, 1)"
at net.sf.jailer.database.Session.executeUpdate(Session.java:470)
at net.sf.jailer.entitygraph.EntityGraph.create(EntityGraph.java:120)
... 4 more
Error: java.lang.RuntimeException: Can't find working tables! Run 'bin/jailer.sh create-ddl' and execute the DDL-script first!
2010-06-28 14:15:08,724 [main] ERROR - working directory is /opt/downloads/java/tools/test/integration-test/jailer
</code></pre></div></div>
<p>Ini disebabkan karena Jailer ternyata membuat beberapa tabel di database untuk kebutuhan internalnya. Ini dapat dilihat pada database development kita.</p>
<p><a href="/images/uploads/2010/06/18-jailer-tables-300x226.png"><img src="/images/uploads/2010/06/18-jailer-tables-300x226.png" alt="Tabel Internal Jailer " /></a></p>
<p>Untuk menggenerate tabel di atas, kita jalankan jailer dengan opsi create-ddl. Ini akan menghasilkan SQL di layar. SQL ini harus kita jalankan di database production supaya tabelnya terbentuk.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sh jailer.sh create-ddl
DROP TABLE JAILER_ENTITY;
DROP TABLE JAILER_DEPENDENCY;
DROP TABLE JAILER_SET;
DROP TABLE JAILER_GRAPH;
DROP TABLE JAILER_CONFIG;
DROP TABLE JAILER_TMP;
CREATE TABLE JAILER_CONFIG
(
jversion VARCHAR(20),
jkey VARCHAR(200),
jvalue VARCHAR(200)
) ;
INSERT INTO JAILER_CONFIG(jversion, jkey, jvalue) values('3.4.5', 'magic', '837065098274756382534403654245288');
CREATE TABLE JAILER_GRAPH
(
id INTEGER NOT NULL,
age INTEGER NOT NULL
-- ,CONSTRAINT jlr_pk_graph PRIMARY KEY(id)
) ;
CREATE TABLE JAILER_ENTITY
(
r_entitygraph INTEGER NOT NULL,
PK0 BIGINT , PK1 VARCHAR(255) , PK2 VARCHAR(255) , PK3 INT , PK4 VARCHAR(255) , PK5 BIGINT ,
birthday INTEGER NOT NULL,
type VARCHAR(120) NOT NULL,
PRE_PK0 BIGINT , PRE_PK1 VARCHAR(255) , PRE_PK2 VARCHAR(255) , PRE_PK3 INT , PRE_PK4 VARCHAR(255) , PRE_PK5 BIGINT ,
PRE_TYPE VARCHAR(120),
orig_birthday INTEGER,
association INTEGER
-- , CONSTRAINT jlr_fk_graph_e FOREIGN KEY (r_entitygraph) REFERENCES JAILER_GRAPH(id)
) ;
CREATE INDEX jlr_enty_brthdy ON JAILER_ENTITY (r_entitygraph, birthday, type) ;
CREATE INDEX jlr_enty_upk1 ON JAILER_ENTITY (r_entitygraph , PK0, PK1, PK2, PK3, PK4, PK5, type) ;
CREATE TABLE JAILER_SET
(
set_id INTEGER NOT NULL,
type VARCHAR(120) NOT NULL,
PK0 BIGINT , PK1 VARCHAR(255) , PK2 VARCHAR(255) , PK3 INT , PK4 VARCHAR(255) , PK5 BIGINT
) ;
CREATE INDEX jlr_pk_set1 ON JAILER_SET (set_id , PK0, PK1, PK2, PK3, PK4, PK5, type) ;
CREATE TABLE JAILER_DEPENDENCY
(
r_entitygraph INTEGER NOT NULL,
assoc INTEGER NOT NULL,
depend_id INTEGER NOT NULL,
traversed INTEGER,
from_type VARCHAR(120) NOT NULL,
to_type VARCHAR(120) NOT NULL,
FROM_PK0 BIGINT , FROM_PK1 VARCHAR(255) , FROM_PK2 VARCHAR(255) , FROM_PK3 INT , FROM_PK4 VARCHAR(255) , FROM_PK5 BIGINT ,
TO_PK0 BIGINT , TO_PK1 VARCHAR(255) , TO_PK2 VARCHAR(255) , TO_PK3 INT , TO_PK4 VARCHAR(255) , TO_PK5 BIGINT
-- , CONSTRAINT jlr_fk_graph_d FOREIGN KEY (r_entitygraph) REFERENCES JAILER_GRAPH(id)
) ;
CREATE INDEX jlr_dep_from1 ON JAILER_DEPENDENCY (r_entitygraph, assoc , FROM_PK0, FROM_PK1, FROM_PK2, FROM_PK3, FROM_PK4, FROM_PK5) ;
CREATE INDEX jlr_dep_to1 ON JAILER_DEPENDENCY (r_entitygraph , TO_PK0, TO_PK1, TO_PK2, TO_PK3, TO_PK4, TO_PK5) ;
CREATE TABLE JAILER_TMP
(
c1 INTEGER,
c2 INTEGER
) ;
INSERT INTO JAILER_CONFIG(jversion, jkey, jvalue) values('3.4.5', 'upk', '679547784');
</code></pre></div></div>
<p>Setelah tabelnya siap, jalankan kembali script yang error di atas. Berikut outputnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./export-payment-201006.sh
2010-06-28 14:12:31,175 [main] INFO - Jailer 3.4.5
2010-06-28 14:12:31,190 [main] INFO - added 'lib/mysql-connector-java-5.1.6-bin.jar' to classpath
2010-06-28 14:12:31,191 [main] INFO - exporting 'extractionmodel/payment-201006.csv' to 'payment-201006.sql'
2010-06-28 14:12:32,850 [main] INFO - SQL dialect is MYSQL
2010-06-28 14:12:32,925 [main] INFO - gather statistics after 0 inserted rows...
2010-06-28 14:12:32,966 [main] INFO - reading file 'renew.sql'
2010-06-28 14:12:32,966 [main] INFO - 0 statements (100%)
2010-06-28 14:12:32,967 [main] INFO - successfully read file 'renew.sql'
2010-06-28 14:12:32,977 [main] INFO - exporting payment Where date_format(paid_date,'%Y-%m') = '2010-06'
2010-06-28 14:12:33,028 [main] INFO - day 1, progress: payment
2010-06-28 14:12:33,039 [main] INFO - starting 4 jobs
2010-06-28 14:12:33,040 [main] INFO - gather statistics after 3 inserted rows...
2010-06-28 14:12:33,041 [main] INFO - reading file 'renew.sql'
2010-06-28 14:12:33,042 [main] INFO - 0 statements (100%)
2010-06-28 14:12:33,042 [main] INFO - successfully read file 'renew.sql'
2010-06-28 14:12:33,047 [main] INFO - resolving payment -> payment_info (inverse-FKE25C3F47AB64A229) 1:n on B.id_payment=A.id...
2010-06-28 14:12:33,105 [main] INFO - 66 entities found resolving payment -> payment_info (inverse-FKE25C3F47AB64A229) 1:n on B.id_payment=A.id
2010-06-28 14:12:33,105 [main] INFO - resolving payment -> cetak_ulang (inverse-FK45B985E0AB64A229) 1:n on B.id_payment=A.id...
2010-06-28 14:12:33,126 [main] INFO - 0 entities found resolving payment -> cetak_ulang (inverse-FK45B985E0AB64A229) 1:n on B.id_payment=A.id
2010-06-28 14:12:33,126 [main] INFO - resolving payment -> fee_loket (inverse-FK9632AFFEAB64A229) 1:n on B.id_payment=A.id...
2010-06-28 14:12:33,129 [main] INFO - 3 entities found resolving payment -> fee_loket (inverse-FK9632AFFEAB64A229) 1:n on B.id_payment=A.id
2010-06-28 14:12:33,131 [main] INFO - resolving payment -> payment_value (inverse-FK69DD09F8AB64A229) 1:n on B.id_payment=A.id...
2010-06-28 14:12:33,142 [main] INFO - 12 entities found resolving payment -> payment_value (inverse-FK69DD09F8AB64A229) 1:n on B.id_payment=A.id
2010-06-28 14:12:33,143 [main] INFO - executed 4 jobs
2010-06-28 14:12:33,143 [main] INFO - day 2, progress: payment_info, fee_loket, payment_value
2010-06-28 14:12:33,144 [main] INFO - skip reversal association payment_info -> payment
2010-06-28 14:12:33,144 [main] INFO - skip reversal association fee_loket -> payment
2010-06-28 14:12:33,147 [main] INFO - skip reversal association payment_value -> payment
2010-06-28 14:12:33,147 [main] INFO - starting 1 jobs
2010-06-28 14:12:33,148 [main] INFO - executed 1 jobs
2010-06-28 14:12:33,149 [main] INFO - exported payment Where date_format(paid_date,'%Y-%m') = '2010-06'
2010-06-28 14:12:33,149 [main] INFO - total progress: payment_info, payment, fee_loket, payment_value
2010-06-28 14:12:33,149 [main] INFO - export statistic:
2010-06-28 14:12:33,169 [main] INFO - Exported Rows: 84
2010-06-28 14:12:33,169 [main] INFO - fee_loket 3
2010-06-28 14:12:33,169 [main] INFO - payment 3
2010-06-28 14:12:33,172 [main] INFO - payment_info 66
2010-06-28 14:12:33,172 [main] INFO - payment_value 12
2010-06-28 14:12:33,173 [main] INFO - writing file 'payment-201006.sql'...
2010-06-28 14:12:33,178 [main] INFO - independent tables: payment
2010-06-28 14:12:33,179 [main] INFO - starting 1 jobs
2010-06-28 14:12:33,380 [main] INFO - executed 1 jobs
2010-06-28 14:12:33,380 [main] INFO - independent tables: payment_info, fee_loket, payment_value
2010-06-28 14:12:33,384 [main] INFO - starting 3 jobs
2010-06-28 14:12:33,447 [main] INFO - executed 3 jobs
2010-06-28 14:12:33,447 [main] INFO - cyclic dependencies for:
2010-06-28 14:12:33,447 [main] INFO - starting 0 jobs
2010-06-28 14:12:33,448 [main] INFO - executed 0 jobs
2010-06-28 14:12:33,448 [main] INFO - gather statistics after 84 inserted rows...
2010-06-28 14:12:33,450 [main] INFO - reading file 'renew.sql'
2010-06-28 14:12:33,450 [main] INFO - 0 statements (100%)
2010-06-28 14:12:33,454 [main] INFO - successfully read file 'renew.sql'
2010-06-28 14:12:33,456 [main] INFO - starting 0 jobs
2010-06-28 14:12:33,467 [main] INFO - executed 0 jobs
2010-06-28 14:12:33,486 [main] INFO - file 'payment-201006.sql' written.
</code></pre></div></div>
<p>Selesai sudah, data yang kita inginkan ada di file payment-201006.sql, siap diunduh dan dijalankan di database development.</p>
<p>Semoga bermanfaat, kalau ada yang kurang jelas, silahkan baca <a href="http://jailer.sourceforge.net/exporting-data.htm">tutorial resminya</a>.</p>
Tuning Performance2010-05-05T15:24:18+07:00https://software.endy.muhardin.com/programming/tuning-performance<p>Setelah aplikasi dicoding dengan benar, biasanya langkah berikutnya adalah tuning performance. Hal ini banyak ditanyakan di berbagai milis pemrograman yang saya ikuti. Agar tidak berkali-kali menulis jawaban yang sama, berikut artikel tentang metodologi saya dalam melakukan tuning performance aplikasi.</p>
<h2 id="0-miliki-tujuan-yang-jelas">0. Miliki tujuan yang jelas</h2>
<p>Misalnya :
Mampu menghandle 100 request/detik dengan response time < 2 detik
dengan 1 juta record di database. Bisa jadi pada tahap ini, ternyata keputusannya adalah tidak perlu tuning, karena performance aplikasi yang sekarang sudah memenuhi keinginan.</p>
<h2 id="1-pastikan-aplikasinya-berjalan-benar">1. Pastikan aplikasinya berjalan benar</h2>
<p>Make it right, then make it fast.
Gak ada gunanya mentuning aplikasi buggy.
Kita juga harus punya perangkat pengetes yang lengkap.
Supaya nanti setelah tuning, bisa dipastikan bahwa tuningnya tidak menimbulkan bug baru.</p>
<h2 id="2-pasang-monitor-di-aplikasi">2. Pasang monitor di aplikasi</h2>
<p>Misalnya :</p>
<ul>
<li>CPU usage</li>
<li>Memory usage</li>
<li>Aktifitas harddisk</li>
<li>Aktifitas database</li>
</ul>
<p>Di linux, CPU dan Memory usage bisa <a href="http://www.linuxforums.org/articles/using-top-more-efficiently_89.html">dipantau dengan top</a>,
sedangkan aktifitas harddisk dengan <a href="http://royontechnology.blogspot.com/2007/06/using-iostat-for-monitoring-disk.html">iostat</a>.
Di MySQL, aktifitas database bisa dimonitor dengan perintah <a href="http://dev.mysql.com/doc/refman/5.1/en/show-processlist.html">show processlist</a>.</p>
<h2 id="3-setelah-monitor-siap-penyiksaan-dimulai">3. Setelah monitor siap, penyiksaan dimulai</h2>
<p>Berikan load yang tinggi ke aplikasi dengan menggunakan tools penyiksaan seperti misalnya <a href="http://jakarta.apache.org/jmeter/">JMeter</a>.
Tingkatkan terus loadnya sampai response time tidak lagi memenuhi syarat.
Misalnya, pada 30 request/detik, response time menjadi 10 detik.</p>
<h2 id="4-cari-bottlenecknya">4. Cari bottlenecknya</h2>
<p>Amati monitor, aspek mana yang overload.
Apakah CPU, I/O, atau memori.
Perhatikan juga aktifitas database untuk mencari penyebabnya.</p>
<h2 id="5-lakukan-tuning">5. Lakukan tuning</h2>
<p>Silahkan dioprek dengan metode trial and error.
Biasanya pada tahap ini saya mencari kolom mana yang perlu diindex,
bagian mana di source code yang perlu diperbaiki,
atau konfigurasi seperti apa yang optimal.</p>
<h2 id="6-test-lagi">6. Test lagi</h2>
<p>Setelah dioprek, jalankan lagi tools penyiksaan.
Kalau langkah no #5 benar, biasanya bottlenecknya akan pindah.
Misalnya, tadinya CPU maxed out 100%, setelah tuning jadi santai 10%,
tapi memory usage jadi 80%.</p>
<h2 id="7-ulangi-langkah-5-dan-6">7. Ulangi langkah #5 dan #6</h2>
<p>Ulangi terus tuning dan test sampai aplikasi memenuhi tujuan yang diset di langkah #0.
Inilah pentingnya langkah #0, supaya kita tahu kapan harus berhenti.</p>
<p>Beberapa hal yang harus diingat dalam tuning performance:</p>
<ol>
<li>
<p>Tidak ada pil ajaib, masing-masing kasus berbeda. Kadang masalahnya ada di index database, kadang di prosesor, dsb.</p>
</li>
<li>
<p>Jangan main tebak-tebakan, semua keputusan harus berdasarkan hasil monitoring. Soalnya seringkali tebakan kita salah.</p>
</li>
<li>
<p>Jangan lupakan maintenance source code. Proses tuning mungkin saja akan membuat source code menjadi kompleks dan sulit dibaca. Jangan sampai kita mengorbankan kerapian coding demi sedikit peningkatan performance. Lebih baik upgrade hardware daripada mengotori source code.</p>
</li>
<li>
<p>Tahu kapan harus berhenti. Tuning merupakan pekerjaan yang menarik, mirip seperti bermain game. Oleh karena itu penting bagi kita untuk punya tujuan. Begitu tujuan dicapai, segera berhenti. Lebih baik menambah fitur yang memiliki business value daripada terus menerus berkutat dengan performance.</p>
</li>
</ol>
<p>Demikian sekilas tentang tuning. Semoga bermanfaat.</p>
Fase Requirement2010-04-18T04:20:29+07:00https://software.endy.muhardin.com/manajemen/fase-requirement<p>Dari seluruh fase yang ada di project, fase Requirement Development adalah yang paling penting. Bila kita melakukan kegiatan requirement dengan asal-asalan, akibatnya antara lain :</p>
<ul>
<li>
<p>aplikasi sudah selesai dibuat, tapi tidak sesuai dengan keinginan user</p>
</li>
<li>
<p>pada fase coding, banyak terjadi delay karena ternyata ada requirement yang belum jelas</p>
</li>
<li>
<p>pada fase coding, banyak pekerjaan harus diulang karena salah memahami requirement</p>
</li>
</ul>
<p>Kenapa saya sebut dengan istilah Requirement Development, bukan Requirement Gathering seperti yang umum dipakai orang? Sebabnya adalah karena requirement yang baik itu tidak didapat dengan mudah. Tidak seperti memungut barang di jalanan (gathering), melainkan harus melalui proses yang iteratif (development). Kita tidak bisa mendapatkan requirement yang baik sekali jalan. Kita harus terus menerus melakukan investigasi, klarifikasi, verifikasi, agar requirement yang didapat benar-benar bagus kualitasnya.</p>
<p>Pada artikel ini, kita akan membahas bagaimana cara membuat requirement yang baik.</p>
<p>Sebelum ke masalah teknis, kita lihat dulu, kenapa kita harus melalui fase requirement? Kenapa tidak langsung coding saja? Kan customer ingin aplikasi jadi, bukan notulensi meeting dan setumpuk dokumen?</p>
<h2 id="tujuan-requirement">Tujuan Requirement</h2>
<p>Kita melakukan requirement development karena kita ingin tahu apa yang ingin dibuat. Setelah mengetahui apa yang ingin dibuat, barulah kita bisa:</p>
<ul>
<li>
<p>memilih anggota tim yang tepat</p>
</li>
<li>
<p>memperkirakan biaya dan waktu yang dibutuhkan untuk membuatnya</p>
</li>
<li>
<p>memilih arsitektur dan teknologi yang sesuai</p>
</li>
</ul>
<p>Jadi, proses requirement membantu kita untuk melakukan project planning.
Selain itu, proses requirement juga membantu kita mencegah project menjadi molor dan merugi. Kalau kita langsung terjun coding, maka akan banyak waktu terbuang untuk melakukan perubahan. Misalnya, kalau kita sudah coding, ternyata user minta tambahan satu field entry, maka kita terpaksa mengubah tampilan, skema database, format report, dokumentasi user (kalau sudah ada), dan mungkin banyak lagi. Tapi kalau perubahan ini dilakukan pada fase requirement, paling effortnya cuma mengubah dummy dan dokumentasi user story saja.</p>
<p>Requirement juga bisa membantu kita mengendalikan perubahan dalam project, seperti kita akan lihat di bagian selanjutnya.</p>
<p>Setelah kita tahu tujuannya, mari kita ke langkah pertama yang paling menentukan.</p>
<h2 id="identifikasi-usernya">Identifikasi Usernya</h2>
<p>Segera setelah project dimulai, kita akan segera dihujani jadwal meeting untuk membahas requirement. Nah di sini kita harus jeli. Mendengarkan user itu penting, tapi yang lebih penting lagi adalah menjawab pertanyaan, siapa usernya?</p>
<p>Ada bermacam-macam jenis user, dan ini menentukan informasi apa yang ingin kita dengarkan, dan informasi mana yang kita abaikan.</p>
<ul>
<li>
<p>End User. Ini adalah user yang nantinya akan menggunakan aplikasi. Kita harus mendengarkan user ini, terutama dari sisi usability. Apakah aplikasi kita nyaman dipakai, mudah digunakan, indah dilihat, dan sebagainya. Tapi, jangan sekali-kali mengambil keputusan tentang proses bisnis dengan user ini. Jangan juga memutuskan untuk menambah/mengurangi fitur <strong>hanya</strong> berdasarkan pendapat user ini. Kita membutuhkan user selanjutnya, yaitu :</p>
</li>
<li>
<p>Sponsor atau Client. Ini adalah orang yang akan membayar invoice untuk pembuatan aplikasi. Semua keputusan penting (proses bisnis dan list fitur adalah penting) harus disetujui Client. Dan jangan salah, seringkali pendapat End User tidak sama dengan pendapat Client. Misalnya, Client menginginkan suatu transaksi harus melalui approval supervisor, manager, dan direktur secara berjenjang. Tapi tentunya fitur ini akan memberatkan End User, karena ada banyak proses yang harus dilalui. Nah, tentunya Anda tahu siapa yang harus kita dengarkan. Nah, jadi kapan-kapan melakukan requirement development, cari tahu dulu siapa yang mengotorisasi bilyet giro :D</p>
</li>
<li>
<p>Konsultan Internal. Seringkali di organisasi client, ada seseorang yang cukup senior dari sisi teknis. Bisa jadi dia adalah divisi IT di organisasi client, atau orang luar yang dipercaya oleh client. Apakah orang ini harus kita dengarkan pendapatnya? Tergantung dari seberapa besar pengaruhnya terhadap client. Kita bisa mengetes pengaruhnya dengan membuat satu fitur yang tidak sesuai dengan pendapat konsultan internal ini, dan lihat apa reaksi client. Kalau client setuju dengan kita, berarti pengaruhnya tidak besar, dan pendapat selanjutnya bisa kita diabaikan.</p>
</li>
<li>
<p>Customer. Ada kalanya Client membeli aplikasi kita untuk dijual lagi ke orang lain. Nah orang lain ini disebut Customer. Sukses atau tidaknya project kita banyak ditentukan oleh berapa customer yang bisa didapatkan client. Jadi, penting juga untuk kita mengetahui profil customer (kalau ada). Apa business objective dari customer, sehingga bisa kita akomodasi dengan baik</p>
</li>
</ul>
<p>Baiklah, saya sudah memetakan, si A adalah End User, si B Client, si C konsultan internal, dan si D adalah customernya. Apakah sekarang sudah bisa kita mulai interviewnya? Ok ok .. mari kita suruh Business Analyst (BA) untuk melakukan tugasnya. Oh, tunggu dulu, BA belum direkrut? Bagaimana kualifikasinya?</p>
<h2 id="kualifikasi-business-analyst">Kualifikasi Business Analyst</h2>
<p>Pertama, BA harus menguasai proses bisnis. Kalau kita ingin membuat aplikasi akunting, BAnya harus mengerti akuntansi. Aturan sederhana dan logis, tapi masih banyak saja perusahaan yang mengirim programmer untuk melakukan requirement development.</p>
<p>Secara umum, BA harus sudah punya pengetahuan dasar dulu sebelum dia ketemu client. Kalau kita kirim programmer, maka dia bukan melakukan requirement development, tapi dia akan belajar bisnis proses ke client. Seperti kita akan lihat, ini pasti akan menimbulkan delay, karena :</p>
<blockquote>
<p>Seorang BA harus bisa membedakan mana proses bisnis yang fundamental dan jarang berubah (karenanya boleh dihardcode), mana yang kondisional dan sering berubah (sehingga harus configurable)</p>
</blockquote>
<p>Programmer yang belajar bisnis proses ke client tidak akan bisa membedakan ini. Sebagai contoh, mari kita lihat prosedur procurement.</p>
<p>Proses bisnis fundamentalnya adalah, ada pengajuan (purchase request), kemudian dilanjutkan dengan minta quotation ke vendor (request for quotation), memilih vendor, baru melakukan pemesanan (purchase order).
Ini adalah flow fundamental, dan boleh di-hardcode.</p>
<p>Kemudian end user akan bilang, purchase request akan dilakukan oleh masing-masing dept, approval dilakukan manager, dst, dst. Siapa yang mengentri, siapa yang mengapprove, dan pada nilai transaksi berapa dia boleh approve, ini adalah kondisional dan harus bisa dikonfigurasi.</p>
<p>Nah, seorang BA yang baik harus bisa membedakan kedua hal ini.</p>
<p>Coba temani BA anda pada saat sesi interview dengan end user. Kalau dia pernah bilang begini,</p>
<blockquote>
<p>Oh, di perusahaan Anda prosesnya begini ya? Biasanya yang umum dilakukan orang adalah seperti ini. Proses Anda kurang optimal karena blablabla. Apakah proses Anda mau berubah, atau aplikasi yang ingin ikut proses Anda dengan konsekuensi ABC?</p>
</blockquote>
<p>Nah, kapan-kapan dia minta naik gaji, jangan buru-buru ditolak. Ini BA bagus. Dia menguasai bidangnya, dan tahu best practices.</p>
<p>Banyak perusahaan yang ingin bikin produk dari project tapi tidak kunjung berhasil. Misalnya, ada client minta dibuatkan aplikasi gudang, trus manajemen mikir, “Wah kayaknya ini kalo dibikin jadi produk bakalan prospek”. Tapi ternyata setelah project selesai, aplikasinya tidak applicable di perusahaan lain. Ini salah satu sebabnya adalah BA yang kurang pengalaman sehingga tidak tahu mana fitur yang generik berlaku umum dan mana yang spesifik hanya untuk perusahaan tertentu saja.</p>
<p>Tahu bisnis proses saja masih kurang, BA yang baik juga paham usability. Seperti kita tahu, ada banyak cara untuk mengentri transaksi. Bisa dientri via screen, bisa upload file, bisa via HP, bisa import dari aplikasi lain, dsb. BA yang baik bisa memberikan rekomendasi pada programmer mengenai user experience. Bagaimana urutan screen, penempatan komponen, apakah pilihan tertentu disajikan dengan dropdown, radio, atau lookup.</p>
<p>Kalau BAnya tidak paham usability, aplikasi kita akan benar secara proses bisnis, tapi tidak enak digunakan. Programmer tidak bisa menentukan usability, karena dia tidak tahu bagaimana biasanya end-user menggunakan fitur tertentu.</p>
<p>Ok, BA saya sudah canggih, paham bisnis proses, tau best practices, dan pernah magang sama <a href="http://www.useit.com">Jakob Nielsen</a>. Bisa kita mulai interview user?</p>
<h2 id="interview-user">Interview User</h2>
<p>Pada fase ini, biarkan saja BA menjalankan tugasnya. Dia akan berbicara dengan user, dan menanyakan hal-hal berikut:</p>
<ul>
<li>
<p>Flow global dari awal sampai akhir. Untuk aplikasi procurement, berarti dari request pembelian, sampai barang diterima.</p>
</li>
<li>
<p>Flow detail untuk masing-masing tahap. Contohnya, bagaimana detail flow proses request pembelian</p>
</li>
<li>
<p>Variasi skenario. Di sini BA akan mengidentifikasi percabangan dari tiap flow. Apa saja variasi skenarionya, perbedaan datanya, role user yang mengaksesnya, kondisi outputnya, dan sebagainya.</p>
</li>
</ul>
<p>Jangan lupa untuk meminta :</p>
<ul>
<li>
<p>Contoh report yang diinginkan</p>
</li>
<li>
<p>Sampel data transaksi untuk kita test di internal</p>
</li>
<li>
<p>Rumus atau formula perhitungan</p>
</li>
</ul>
<p>Tergantung clientnya, ada kemungkinan dia akan meminta perjanjian kerahasiaan sebelum mengeluarkan data-data di atas.</p>
<p>Setelah interview, BA pulang ke kantor, dan akan membuat dokumentasi requirement.</p>
<h2 id="dokumentasi-requirement">Dokumentasi Requirement</h2>
<p>Untuk apa kita membuat dokumentasi? Tujuannya adalah</p>
<ul>
<li>
<p>Untuk mengidentifikasi kalau ada hal yang kurang jelas, sehingga bisa langsung ditanyakan</p>
</li>
<li>
<p>Sebagai bahan untuk verifikasi dengan user, apakah pemahaman kita sudah sama dengan yang dimaksud user.</p>
</li>
<li>
<p>Sebagai pedoman untuk programmer</p>
</li>
<li>
<p>Untuk mencegah project molor</p>
</li>
</ul>
<p>Lho, bagaimana bisa dokumen requirement mencegah project molor? Ya bisa saja, berikut alasannya</p>
<ul>
<li>
<p>Kalau ada kesalahan dan ditemukan pada fase ini, biaya perbaikannya jauh lebih kecil daripada kalau ditemukan pada fase coding. Misalnya ada kesalahan rumus perhitungan. Kalau kesalahan ini ditemukan pada fase requirement, paling biayanya cuma mengedit user story. Tapi kalau ditemukan pada waktu UAT, bisa-bisa butuh 2-3 hari untuk fixingnya. Ini <a href="http://www.stevemcconnell.com/articles/art08.htm">katanya Steve McConnell</a>, bukan bikin-bikinan saya.</p>
</li>
<li>
<p>Setelah requirement disign off, semua perubahan harus melalui change procedure. Ini mencegah project molor karena user terus menerus berubah pikiran. Sekarang maunya A, besok B, lusa ganti lagi.</p>
</li>
</ul>
<p>Lalu, apa saja yang harus didokumentasikan? Daripada panjang lebar, silahkan lihat <a href="http://software.endy.muhardin.com/downloads/user-story.odt">template User Story ArtiVisi</a>. Di situ sudah kita siapkan form isian apa saja yang harus dicantumkan.</p>
<p>Coba lihat dulu, supaya nyambung dengan pembahasan di bawah.</p>
<blockquote>
<p>Lho kenapa ada flow pengetesan? Apa bedanya dengan test scenario?</p>
</blockquote>
<p>Sama saja, yang kita maksud flow pengetesan memang adalah test scenario. Lalu apakah wajib dibuat pada fase requirement? Kami sangat menganjurkan untuk membuatnya, dengan alasan sebagai berikut:</p>
<ul>
<li>
<p>Dengan memikirkan bagaimana nanti pengetesannya, kualitas user story akan meningkat. BA terpaksa memikirkan step-by-step bagaimana aplikasi akan digunakan, apa inputnya, dan apa outputnya. Dengan memikirkan ini, semua variasi dan kebutuhan input, dan ekspektasi output mau tidak mau akan terpikirkan dan teridentifikasi sejak dini. Ini akan mengurangi requirement yang ambigu, tidak lengkap, atau tidak mungkin diimplementasikan</p>
</li>
<li>
<p>Test scenario yang ditandatangani user merupakan exit strategy bagi vendor. Kalau client sudah setuju dengan skenario testnya, maka vendor cukup membuat aplikasi yang lulus test tersebut. Setelah lulus test, jangan ditambah-tambahi lagi. Ini akan mencegah programmer menambah fitur-fitur menarik namun tidak memiliki business value.</p>
</li>
</ul>
<p>Seperti bisa dilihat pada template, kita mengharuskan adanya screen prototype di dokumen requirement. Screen ini dirancang oleh BA (makanya dia harus paham usability), dan kalau mau, bisa dibuatkan dummy-nya. Dummy bisa dibuat dengan apapun teknologi yang murah dan cepat. Begitu desain screen jadi, seharusnya tidak lebih dari 2 jam waktu yang dibutuhkan untuk membuat dummy-nya, bahkan untuk screen kompleks sekalipun.</p>
<p>Yang harus ada di desain screen adalah :</p>
<ul>
<li>
<p>Input field, harus jelas komponennya, apakah text, radio, dsb</p>
</li>
<li>
<p>Contoh isian. Jangan membuat input kosong, buatlah seolah-olah sudah diisi user. Ini akan memudahkan pada saat presentasi</p>
</li>
<li>
<p>Contoh output. Demikian juga dengan output hasil query, report, dsb. Jangan tampilkan tabel kosong. Isilah dengan data statis barang beberapa baris, agar user mempunyai gambaran bagaimana hasil akhirnya</p>
</li>
</ul>
<p>Tidak perlu repot-repot mengimplementasikan dummy ini. Semuanya adalah data statis yang langsung diketik apa adanya.</p>
<p>Dokumen user story dan dummy dipresentasikan ke end user dan direvisi sesuai input. Pada fase ini, end user bebas membuat perubahan apapun yang diinginkan. Perubahan bebas untuk diakomodasi, asal jangan pernah melupakan siapa yang tandatangan otorisasi bilyet giro :D</p>
<h2 id="sign-off">Sign Off</h2>
<p>Setelah semua user story diiterasi dengan end user sampai puas, maka tiba saatnya untuk melakukan feature-freeze. Semua dokumentasi requirement diupdate sehingga sesuai kondisi terakhir, lalu minta approval tertulis dari client. Ingatlah selalu, <strong>approval client, bukan end-user, bukan konsultan internal</strong>. Ini adalah aktivitas paling critical dan harus dilakukan. Segala usaha proses requirement akan percuma tanpa sign off client.</p>
<h2 id="change-management">Change Management</h2>
<p>Sebelum sign off, end user bebas mengajukan perubahan apapun. Setelah sign off, semua perubahan harus melalui change procedure. Intinya adalah, perubahan diajukan secara tertulis, diestimasi penambahan durasi dan costnya, lalu diajukan ke manajemen, baik vendor maupun client. Kalau salah satu pihak tidak setuju, maka perubahan tidak akan dijalankan.</p>
<p><a href="/images/uploads/2010/02/change-procedure_id-724x1024.png"><img src="/images/uploads/2010/02/change-procedure_id-724x1024.png" alt="Prosedur Change Management " /></a></p>
<p>Lebih jelas tentang informasi apa saja yang dibahas di change management, bisa melihat <a href="http://software.endy.muhardin.com/downloads/change-request.odt">template change request ArtiVisi</a>.</p>
<p>Seperti kita lihat, di sini faktor sign off sangat berperan. Tanpa ada sign off, tidak ada batas kapan user bisa berubah seenaknya, dan kapan tidak boleh.</p>
<p>Kalau change management dijalankan dengan baik, project akan lebih terkontrol. Walaupun ada kemunduran, kedua belah pihak sadar apa sebabnya. Semua perubahan diketahui manajemen, sehingga tidak ada bos yang tiba-tiba muncul dan bilang</p>
<blockquote>
<p>Ini project kenapa gak beres-beres?</p>
</blockquote>
<p>Kunci sukses change management adalah mulai dari awal, dan perhatikan hal kecil. Kita sebagai vendor sering mengabaikan prosedur ini dengan berbagai alasan, diantaranya</p>
<ul>
<li>
<p>Ah perubahannya terlalu kecil, kalo langsung diimplement cuma 5 menit, tapi kalo change procedure bisa 2 hari.</p>
</li>
<li>
<p>Kita tidak mau terlihat birokratis seperti pegawai kelurahan perpanjang KTP</p>
</li>
<li>
<p>Kita berbaik hati pada client, masa perubahan sedikit saja hitung-hitungan banget</p>
</li>
</ul>
<p>Ini merupakan kesalahan besar. Dengan memberlakukan change procedure bahkan untuk hal kecil, kita akan menimbulkan kesadaran di client bahwa kita mengelola project dengan ketat. Dengan demikian, mereka tidak sembarangan meminta perubahan. Client juga akan menyadari bahwa perubahan kecil saja akan berdampak pada keseluruhan project.</p>
<p>Kalau kita memang ingin berbaik hati pada client, silahkan digratiskan. Tapi prosedur tetap dijalankan. Jadi kalo tiba-tiba ada bos client yang tanya seperti di atas, tinggal kita sodori binder berisi daftar change request yang sudah diapprove.</p>
<p>Change procedure juga ada bonusnya, yaitu tidak banyak mengganggu programmer. Estimasi dan approval mostly dilakukan oleh business analyst dan project manager. Dan belum tentu juga client setuju. Kita akan menghemat banyak waktu, konsentrasi, dan pikiran programmer yang tidak perlu memikirkan usulan perubahan yang ternyata tidak disetujui.</p>
<p>Di change request juga ada timing kapan change akan diberlakukan. Untuk menjaga konsentrasi dan ritme tim, PM bisa menggunakan opsi ini untuk menunda change ke iterasi selanjutnya.</p>
<h2 id="requirement-traceability">Requirement Traceability</h2>
<p>Yang satu ini titipan dari CMMI. Di process area Requirement Management, CMMI mengharuskan adanya <em>bidirectional traceability</em>. Artinya, setiap hal di requirement harus bisa ditelusuri dokumen desain mana yang membahasnya, baris kode mana yang mengimplementasikannya, test scenario mana yang memverifikasinya. Demikian juga sebaliknya (makanya disebut bidirectional), setiap baris kode, harus bisa ditelusuri requirement mana yang membutuhkannya.</p>
<p>Ini ide yang bagus. Dengan melaksanakan ini, kita memastikan bahwa effort coding kita benar-benar efisien. Tidak ada effort terbuang percuma untuk fitur-fitur yang tidak perlu. Demikian juga sebaliknya, kita memastikan bahwa semua requirement sudah diimplementasikan dan tidak ada yang ketinggalan.</p>
<p>Walaupun demikian, ide bagus belum tentu realistis di lapangan. Saat ini di ArtiVisi, kita baru bisa merelasikan antara baris kode dengan requirement dengan menggunakan Trac. Tapi tidak untuk dokumen lainnya seperti user manual, test scenario, dsb. Dan itupun tidak bidirectional.</p>
<p>Kalau ingin tahu bagaimana ini diimplementasikan, silahkan lihat <a href="http://sourceforge.net/projects/osrmt/">aplikasi ini</a>.</p>
<p>Oh iya, kalau kita sudah melakukan semua anjuran di artikel ini, lengkap dengan Requirement Traceability, kita sudah siap untuk diaudit untuk proses area Requirement Management (Maturity Level 2) dan Requirement Development (Maturity Level 3) :D</p>
<p>Demikian penjelasan tentang fase requirement. Semoga bermanfaat.</p>
Tips Melaporkan Error2010-04-15T04:38:02+07:00https://software.endy.muhardin.com/java/tips-melaporkan-error<blockquote>
<p>Update 2011-12-02: karena masih aja banyak yang belum paham, saya tambahkan template pertanyaan di akhir artikel.</p>
</blockquote>
<p>Sebagai programmer, setiap hari kita menghadapi software yang error. Bentuknya macam-macam, misalnya:</p>
<ul>
<li>
<p>Aplikasi yang kita buat error</p>
</li>
<li>
<p>Peserta milis menggunakan framework X dan mengalami masalah</p>
</li>
<li>
<p>Rekan sesama programmer membutuhkan bantuan</p>
</li>
<li>
<p>dsb</p>
</li>
</ul>
<p>Pada sebagian besar kasus, kita dengan senang hati akan membantu. Sudah menjadi sifat programmer untuk memiliki rasa keingintahuan yang tinggi dan senang terhadap teka-teki. Aplikasi yang error adalah teka-teki yang menarik.</p>
<p>Sayangnya, seringkali informasi error yang kita terima tidak lengkap sehingga butuh usaha tambahan untuk mengorek kejadian yang sebenarnya. Inilah yang membuat programmer seringkali keburu malas, sehingga akhirnya kegiatan solving error menjadi tidak fun lagi.</p>
<p>Berikut adalah beberapa tips untuk membuat laporan error yang baik, supaya programmer yang akan memecahkan masalah tersebut bisa segera bekerja dengan efektif.</p>
<p>Prinsip utama dalam melaporkan error adalah sebagai berikut</p>
<blockquote>
<p>Programmer bukan <del>Mama Loren</del> Baby Djenar atau Ki Joko Bodo. Dia tidak bisa membaca pikiran ataupun melakukan telepati. Jadi jangan menganggap programmer melihat apa yang dilihat user. User harus menjelaskan apa yang dia lihat.</p>
</blockquote>
<h2 id="jelaskan-lokasinya">Jelaskan lokasinya</h2>
<p>Seringkali si informan error berkata seperti ini,</p>
<blockquote>
<p>Help, aplikasinya error nih</p>
</blockquote>
<p>Nah, yang menerima informasi jelas bingung. Aplikasi yang mana?
Kalo client yang jadi informan, bisa jadi kita punya banyak project di sana.
Walaupun projectnya cuma satu, tapi aplikasi kan bisa terdiri dari banyak modul, screen, fitur, dsb.</p>
<p>Jadi, jelaskan di mana errornya. Di aplikasi apa, modul apa, screen yang mana. Terdengar simple, tapi nyatanya ada saja yang melewatkan hal pertama ini.</p>
<p>Mari kita lanjutkan.</p>
<h2 id="tujuan">Tujuan</h2>
<p>Kita menggunakan aplikasi tentu ingin mencapai tujuan tertentu. Ingin menyimpan data customer, menampilkan laporan bulanan, dan lain sebagainya. Nah, tujuan ini harus disampaikan ke programmer. Soalnya sering terjadi percakapan seperti ini :</p>
<p>User (U) : Pak, fitur daftar produknya error.<br />
Programmer (P) : Harusnya bisa kok, sudah kami test di sini tidak ada masalah.<br />
U : Tapi saya coba tidak bisa<br />
P : Coba jelaskan langkahnya<br />
U : Saya buka menu daftar produk, muncul tabel berisi produk.
Kemudian saya klik dua kali nama produk dalam tabel, saya ganti isinya.
Setelah itu, saya tutup screennya.
Pas dibuka lagi, datanya tetap sama.<br />
P : Lho, memangnya Ibu mau melakukan apa?<br />
U : Saya mau edit harga produknya<br />
P : Walah, bukan dari situ Bu. Gunakan menu edit produk</p>
<p>Nah, seringkali tujuan user tidak sesuai dengan fitur aplikasi. Jadi, beri tahukan tujuan user pada programmer.</p>
<h2 id="langkah-reproduksi">Langkah Reproduksi</h2>
<p>Begitu programmer akan mencoba memperbaiki error, dia akan mencoba mereproduksi error tersebut di komputernya sendiri. Nah di sinilah biasanya terjadi percakapan seperti ini :</p>
<p>U : Ini aplikasi error, tidak bisa simpan data<br />
P : Oh, di sini saya coba bisa kok<br />
U : Di sini tidak bisa<br />
P : Di sini bisa<br />
U : Tidak bisa<br />
P : Bisa<br />
dst</p>
<p>Akan lebih produktif kalau percakapannya seperti ini :</p>
<p>U : Ini aplikasi error, tidak bisa simpan data<br />
P : Apa yang dilakukan?</p>
<p>U : Seperti ini :</p>
<ol>
<li>Buka screen Edit Produk</li>
<li>Pilih kategori Komputer dan Elektronik</li>
<li>Isi kode produk</li>
<li>Isi nama produk</li>
<li>Harga dikosongkan</li>
<li>Tekan tombol simpan</li>
</ol>
<p>Nah, dengan percakapan seperti ini, programmer bisa mengulangi apa yang dilakukan user.
Apakah langkah repro saja sudah cukup? Belum, masih ada 1 hal penting lainnya.</p>
<h2 id="harapan-dan-kenyataan">Harapan dan Kenyataan</h2>
<p>Mari kita lanjutkan percakapan di atas.</p>
<p>U : Setelah ditekan tombol simpan, datanya tidak tersimpan.<br />
P : Di tempat saya masuk kok ke database.<br />
U : Di sini, setelah ditekan simpan tidak terjadi apa-apa<br />
P : Maksudnya?<br />
U : Ya harusnya kan ada pesan, “Data sudah tersimpan”<br />
Ini tidak ada.</p>
<p>Nah, di sini harapan user adalah ada notifikasi dari aplikasi bahwa data sudah tersimpan. Tapi kenyataannya tidak ada notifikasi apa-apa dari aplikasi. User mengira ini error,
padahal programmer memang tidak menyediakan notifikasi tersebut, walaupun datanya sudah masuk ke database. Dari sini, programmer bisa menambahkan notifikasi sesuai harapan user.</p>
<p>Jadi, harapan dan kenyataan harus disampaikan pada programmer.</p>
<h2 id="environment">Environment</h2>
<p>Ini maksudnya adalah kondisi di mana aplikasi dijalankan, seperti</p>
<ul>
<li>
<p>Sistem Operasi</p>
</li>
<li>
<p>Versi Aplikasi</p>
</li>
<li>
<p>Aplikasi lain yang terinstal</p>
</li>
</ul>
<p>Sering terjadi aplikasi error hanya di Linux saja, atau di Mac saja, tapi berjalan lancar di Windows. Atau sebaliknya. Dengan memberi informasi ini, programmer bisa lebih terarah dalam mencari kesalahan dalam kode program.</p>
<p>Demikian juga dengan versi aplikasi dan aplikasi lain yang terinstal. Seringkali sudah diketahui bahwa aplikasi kita tidak kompatibel dengan versi library tertentu, versi OS tertentu, atau dengan aplikasi lain. Misalnya seperti ini :</p>
<p>Tanya (T) : Saya instal Tomcat tapi error<br />
Jawab (J) : Errornya gimana?<br />
T : Katanya port 8080 tidak bisa digunakan. <br />
J : Apakah ada webserver lain yang terinstal?<br />
T : Web server sih tidak ada, saya cuma install database Oracle saja.<br />
J : Oh, Oracle itu membawa web server sendiri, jalannya juga di 8080.<br />
Coba matikan Oraclenya, atau ganti port Tomcat ke angka lain.</p>
<p>Demikian beberapa tips melaporkan error.</p>
<p>Berikut ringkasannya :</p>
<ol>
<li>
<p>Jelaskan Lokasinya</p>
</li>
<li>
<p>Sebutkan Tujuan</p>
</li>
<li>
<p>Langkah Reproduksi</p>
</li>
<li>
<p>Harapan dan Kenyataan</p>
</li>
<li>
<p>Environment</p>
</li>
</ol>
<p>Berikut template yang bisa digunakan untuk mengajukan pertanyaan.</p>
<p>Saya ingin …… (misal : mendeploy aplikasi di glassfish),
untuk itu saya melakukan langkah2 berikut :</p>
<ol>
<li>….</li>
<li>….</li>
<li>….</li>
</ol>
<p>Setelah saya lakukan langkah di atas, saya mengharapkan hasil sbb:</p>
<ol>
<li>…..</li>
<li>…..</li>
<li>…..</li>
</ol>
<p>Tapi ternyata kok malah muncul hasil seperti ini :</p>
<ol>
<li>….</li>
<li>….</li>
<li>….</li>
</ol>
<p>Sebagai tambahan informasi, saya menggunakan :</p>
<ol>
<li>Sistem Operasi … versi …</li>
<li>Bahasa Pemrograman … versi …</li>
<li>Database … versi …</li>
<li>Framework/Tools … versi …</li>
</ol>
<p>Dengan mengikuti tips ini berarti Anda sudah membantu kami untuk membantu Anda.
:D</p>
Dokumentasi Project2010-03-10T07:41:06+07:00https://software.endy.muhardin.com/manajemen/dokumentasi-project<p>Pertanyaan yang sering muncul dalam pengelolaan proyek software adalah, “Dokumen apa saja yang harus dibuat?”</p>
<p>Tidak ada jawaban absolut untuk pertanyaan ini, semuanya tergantung situasi dan kondisi. Bahkan di satu perusahaan yang sama, kelengkapan dokumen projectnya bisa saja berbeda antar project.</p>
<p>Dalam artikel ini, kita akan membahas bagaimana menentukan dokumen yang digunakan dalam project.</p>
<blockquote>
<p>Disclaimer : Semua yang dijelaskan pada artikel ini ditulis berdasarkan pengalaman dan kebutuhan internal ArtiVisi. Kebutuhan Anda belum tentu sama dengan kami.</p>
</blockquote>
<p>Pertama, kita harus tahu dulu latar belakang kenapa dokumen project dibutuhkan. Setidaknya ada beberapa kegunaan dokumen project, yaitu</p>
<ul>
<li>
<p>sebagai media komunikasi di internal tim, dengan client, dengan manajemen, dan pihak lain yang berkepentingan (stakeholder)</p>
</li>
<li>
<p>sebagai catatan historis jalannya project</p>
</li>
<li>
<p>sebagai alat bantu untuk melihat kondisi terkini project (project visibility)</p>
</li>
<li>
<p>sebagai kontrak legal bila terjadi perselisihan</p>
</li>
</ul>
<p>Setelah kita mengetahui apa saja kegunaan dokumen, mari kita teliti satu persatu berdasarkan fase dalam project.</p>
<h2 id="fase-sales">Fase Sales</h2>
<p>Biasanya pada saat ada tawaran project, kita akan membuat proposal yang relatif tebal (> 10 halaman). Kami di ArtiVisi saat ini sudah hampir tidak pernah membuat proposal project, karena beberapa alasan berikut :</p>
<ul>
<li>
<p>semakin tebal dokumen, semakin tidak dibaca</p>
</li>
<li>
<p>membuang effort percuma untuk membuat dan maintenance</p>
</li>
</ul>
<p>Fungsi proposal sebenarnya adalah panduan awal untuk membuat agreement. Poin-poin yang ada di proposal akan dipindahkan ke agreement yang selanjutnya akan ditandatangani kedua belah pihak.</p>
<p>Kami menyederhanakan proposal menjadi quotation biasa, yang berisi informasi sbb :</p>
<ul>
<li>
<p>scope pekerjaan</p>
</li>
<li>
<p>out of scope</p>
</li>
<li>
<p>estimasi durasi</p>
</li>
<li>
<p>requirement khusus (misalnya performance requirement, integrasi dengan aplikasi lain, dsb)</p>
</li>
<li>
<p>nilai project</p>
</li>
<li>
<p>termin pembayaran</p>
</li>
</ul>
<p>Ada beberapa informasi yang biasa ada di proposal dan tidak ada di quotation, yaitu</p>
<ul>
<li>
<p>teknologi yang digunakan. Biasanya client kami menurut saja apa solusi yang kita rekomendasikan. Kalaupun ada requirement khusus, akan ditulis di bagian requirement khusus di quotation</p>
</li>
<li>
<p>skema layer, tier, database diagram, dsb. Biasanya, implementasi internal aplikasi tidak perlu dipikirkan client. Client cukup menentukan what to be built, dan kami yang memikirkan how to build. Kalau client ingin ikut cawe-cawe urusan internal, biasanya kita tawarkan outsourcing programmer saja, bukan project software.</p>
</li>
</ul>
<p>Selanjutnya, setelah quotation dinegosiasikan dan disetujui, tiba saatnya membuat agreement, atau perjanjian kerja sama. Ada beberapa poin yang dicantumkan dalam agreement, yaitu :</p>
<ul>
<li>
<p>scope pekerjaan</p>
</li>
<li>
<p>out of scope</p>
</li>
<li>
<p>estimasi durasi</p>
</li>
<li>
<p>klausul keterlambatan delivery</p>
</li>
<li>
<p>termin pembayaran</p>
</li>
<li>
<p>klausul keterlambatan pembayaran</p>
</li>
<li>
<p>prosedur change management</p>
</li>
</ul>
<p>Dari beberapa poin di atas, poin klausul keterlambatan delivery harus mendapat perhatian khusus. Keterlambatan delivery bisa terjadi karena banyak sekali sebab. Tidak semua diantaranya adalah kesalahan tim project. Oleh karena itu, kalau ada klausul seperti ini di agreement, tim project harus mengeluarkan effort ekstra untuk melakukan project tracking. Dengan demikian, bila terjadi keterlambatan, ada data yang lengkap mengenai riwayat dan penyebab keterlambatan tersebut.</p>
<p>Selesai bagian agreement, mari kita masuk ke tahap berikutnya.</p>
<h2 id="project-planning">Project Planning</h2>
<p>Tujuan dari dilakukannya dokumentasi pada fase ini adalah untuk mengkomunikasikan bagaimana project akan berjalan, baik ke internal tim maupun ke client. Berikut informasi yang ada dalam project plan :</p>
<ul>
<li>
<p>Milestone dan Delivery : list urutan delivery yang akan disampaikan, isi dari masing-masing delivery, dan estimasi tanggalnya</p>
</li>
<li>
<p>Daftar Task untuk Milestone berikut. Kita tidak melakukan breakdown untuk milestone lainnya, karena masih banyak ketidakpastiannya. Breakdown task hanya dilakukan untuk milestone yang ada di depan mata. Begitu suatu milestone akan selesai, baru dilakukan breakdown task untuk milestone selanjutnya.</p>
</li>
<li>
<p>Daftar Resiko Project : ini adalah hal-hal yang berpotensi menghambat jalannya project, seperti misalnya ada PIC client yang akan resign, teknologi yang belum familiar, dsb</p>
</li>
</ul>
<p>Kami tidak membuat Gantt chart. Sebabnya karena Gantt chart sangat menekankan pada dependensi antar task. Sedangkan di project software, dependensi sangat mudah berubah. Misalnya, kita definisikan Modul B dikerjakan setelah Modul A. Ternyata karena PIC client di modul A sedang ada training di luar kota, diputuskan bahwa Modul B akan dikerjakan duluan. Atau, tadinya direncanakan skema database akan dikerjakan sebelum desain UI. Tapi ternyata karena satu dan lain hal, terpaksa UI dikerjakan duluan.</p>
<p>Hal-hal seperti ini cukup sering terjadi dalam project. Sehingga penggunaan Gantt chart justru akan merepotkan kita dalam mengupdate project plan. Kita tahu, semakin sulit dokumen diubah, semakin malas kita mengubahnya, dan akhirnya dokumen tersebut menjadi tidak up to date.</p>
<h2 id="project-tracking">Project Tracking</h2>
<p>Project tracking adalah kegiatan untuk memantau jalannya project. Dalam kegiatan ini, project manager melihat kemajuan project, mengidentifikasi masalah yang terjadi, dan mencarikan solusinya. Kalau masalah yang terjadi berada di luar kemampuan PM, dia akan melakukan eskalasi, yaitu meminta bantuan ke atasannya.</p>
<p>Di ArtiVisi, kita cuma menggunakan satu dokumen untuk melakukan project tracking, yaitu progress report mingguan, yang berisi informasi sbb:</p>
<ul>
<li>
<p>Daftar task yang dikerjakan minggu ini dan statusnya, apakah sudah selesai, sedang dikerjakan, atau belum dimulai</p>
</li>
<li>
<p>Daftar task yang akan dikerjakan minggu depan</p>
</li>
<li>
<p>Daftar deliverable yang akan diberikan minggu depan</p>
</li>
<li>
<p>Resiko project saat ini. Daftar ini dibuat pada saat planning, terus menerus dipantau setiap minggu, dan dilaporkan statusnya</p>
</li>
<li>
<p>Masalah yang terjadi dalam project dan action plan yang dilakukan</p>
</li>
<li>
<p>Perubahan terhadap estimasi awal</p>
</li>
</ul>
<p>Dokumen ini dibuat oleh PM setelah berkonsultasi dengan pasukannya, kemudian dikirim ke manajemen internal dan client. Dengan tidak adanya Gantt chart, kita tidak perlu mengeluarkan effort ekstra untuk mengupdate Gantt chart.</p>
<h2 id="fase-requirement">Fase Requirement</h2>
<p>Pada fase ini, tim project menganalisa kebutuhan user sebagai patokan di fase coding. ArtiVisi cuma membuat satu dokumen pada fase ini, yaitu User Story. Dokumen user story berisi informasi sbb:</p>
<ul>
<li>
<p>User Goal : tujuan yang ingin dicapai client dalam menggunakan fitur ini</p>
</li>
<li>
<p>Ijin Akses : security level untuk menjalankan fitur ini</p>
</li>
<li>
<p>Penjelasan : deskripsi naratif tentang fitur ini</p>
</li>
<li>
<p>Flow aplikasi : langkah-langkah untuk menjalankan fitur ini</p>
</li>
<li>
<p>Desain screen : screenshot prototype atau scan paper prototype</p>
</li>
<li>
<p>Rincian field : penjelasan masing-masing komponen dalam desain screen</p>
</li>
<li>
<p>Prasyarat : hal-hal yang harus terjadi/ada sebelum fitur ini bisa dijalankan</p>
</li>
<li>
<p>Kondisi awal : kondisi aplikasi (data, screen, dsb) sebelum fitur dijalankan</p>
</li>
<li>
<p>Kondisi akhir : kondisi aplikasi setelah fitur selesai dijalankan</p>
</li>
<li>
<p>Karakteristik khusus : kebutuhan khusus seperti response time, usability, dsb</p>
</li>
<li>
<p>Flow pengetesan : bagaimana cara mengetes fitur ini</p>
</li>
<li>
<p>Sign Off : persetujuan user bahwa deskripsi dalam fitur ini sudah sesuai keinginan</p>
</li>
</ul>
<p>Nantinya akan ada banyak dokumen User Story sesuai jumlah fitur dalam aplikasi. Setelah User Story ditandatangani, semua perubahan harus melalui change procedure, yaitu dengan mengisi change request form. Berikut informasi yang ada di dalam change request form :</p>
<ul>
<li>
<p>Penjelasan Perubahan : deskripsi dari apa saja yang ingin diubah</p>
</li>
<li>
<p>Alasan Perubahan : mengapa perubahan ini diajukan</p>
</li>
<li>
<p>Benefit : keuntungan bila perubahan diimplementasikan</p>
</li>
<li>
<p>Dampak : akibat terhadap durasi, effort, dokumen, source code bila perubahan jadi diimplementasikan</p>
</li>
<li>
<p>Estimasi : estimasi effort, durasi, cost untuk menjalankan perubahan ini</p>
</li>
<li>
<p>Approval : persetujuan manajemen baik kedua belah pihak terhadap perubahan ini</p>
</li>
</ul>
<h2 id="fase-desain">Fase Desain</h2>
<p>Di fase desain biasanya kami mendesain beberapa hal berikut:</p>
<ul>
<li>
<p>skema database</p>
</li>
<li>
<p>interkoneksi antar modul</p>
</li>
<li>
<p>protokol komunikasi</p>
</li>
<li>
<p>format data</p>
</li>
</ul>
<p>Walaupun prosesnya dilakukan, tapi tidak ada dokumen permanen yang dihasilkan. Skema database misalnya. Desain dibuat di papan tulis, dan langsung ditulis dalam bentuk source code (SQL atau Hibernate mapping). Bila suatu saat diperlukan diagram, akan digenerate dengan tools dari database development. Protokol komunikasi dan format data akan dibuat di dokumen user story sebagai requirement internal.</p>
<h2 id="fase-coding">Fase Coding</h2>
<p>Pada fase ini, kita menghasilkan source code dan user manual.</p>
<h2 id="fase-uat">Fase UAT</h2>
<p>Pada fase ini, kita membuat dua dokumen, yaitu hasil pengetesan sesuai skenario di User Story dan Berita Acara UAT. Biasanya (tapi tidak selalu), berita acara dan hasil pengetesan digunakan sebagai lampiran penagihan.</p>
<h2 id="fase-implementasi">Fase Implementasi</h2>
<p>Pada fase ini, cuma satu dokumen yang dihasilkan, yaitu Berita Acara Serah Terima Aplikasi. Dokumen ini dibuat dan ditandatangani setelah kegiatan implementasi selesai dilakukan. Beberapa kegiatan dalam fase ini antara lain :</p>
<ul>
<li>
<p>Instalasi Aplikasi</p>
</li>
<li>
<p>Training User</p>
</li>
<li>
<p>Deployment Aplikasi</p>
</li>
<li>
<p>Paralel Run</p>
</li>
</ul>
<p>Demikian dokumentasi project yang kita buat selama project. Tidak terlalu banyak kan? Berikut daftarnya</p>
<ol>
<li>
<p>Quotation</p>
</li>
<li>
<p>Agreement</p>
</li>
<li>
<p>Project Plan</p>
</li>
<li>
<p>Progress Report</p>
</li>
<li>
<p>User Story</p>
</li>
<li>
<p>Requirement Sign Off</p>
</li>
<li>
<p>Change Request Form (kalau perlu)</p>
</li>
<li>
<p>User Manual</p>
</li>
<li>
<p>Berita Acara UAT</p>
</li>
<li>
<p>Berita Acara Serah Terima Aplikasi</p>
</li>
</ol>
<p>Kesimpulannya, buatlah dokumen sesuai kebutuhan, bukan sesuai hype yang sedang trend saat ini dan bukan juga sesuai warisan leluhur.</p>
Manajemen Proyek Sederhana2010-02-17T04:55:40+07:00https://software.endy.muhardin.com/manajemen/manajemen-proyek-sederhana<p>Di berbagai milis yang saya ikuti, ada diskusi menarik yang muncul. Apa saja yang harus dilakukan untuk dalam project management? Bagaimana SOP (standard operating procedure) nya? Tools apa yang digunakan?</p>
<p>Saya sudah mencoba berbagai metodologi manajemen proyek sepanjang karir saya. Ala koboi di jaman jahiliah dulu, ala waterfall di perusahaan terdahulu, ala CMMI di BaliCamp, dan berbagai ala-ala lainnya. Sepanjang perjalanan tersebut, saya juga membaca tentang berbagai metodologi seperti XP, Scrum, Crystal, RUP, dsb.</p>
<p>Dari semuanya, tentu tidak ada metodologi yang bisa digunakan di semua kondisi. Segalanya serba tergantung keadaan. Walaupun demikian, hasil pengalaman bertahun-tahun tersebut sudah memberikan saya pemahaman tentang latar belakang di aturan-aturan CMMI maupun di metodologi lainnya. Begitu kita mendapatkan alasan dibalik aturan tersebut, kita bisa mengambil esensinya dan mengimplementasikannya dengan bentuk lain yang sesuai dengan situasi kita.</p>
<p>Berikut adalah manajemen proyek ala ArtiVisi.</p>
<h2 id="fase-dan-siklus">Fase dan Siklus</h2>
<p>Sebelum membahas lebih jauh tentang manajemen proyek, kita luruskan dulu beberapa istilah. Kita mengenal istilah fase dan siklus (lifecycle)</p>
<p>Fase adalah tahapan yang dilalui dalam membuat aplikasi. Biasanya, dalam satu fase akan terdiri dari banyak task.</p>
<p>Fase yang ada di internal ArtiVisi adalah sebagai berikut :</p>
<ol>
<li>Requirement Development</li>
<li>Coding</li>
<li>User Acceptance Test</li>
<li>Implementasi</li>
</ol>
<p>Keempat fase ini berjalan secara serial, artinya, fase coding baru bisa dimulai setelah requirement development selesai, dsb.
Mendengar ini, pembaca yang beraliran progresif revolusioner pasti langsung demo di Bundaran HI, “Waterfall is dead, long live Agile !!!”</p>
<p>Tunggu dulu sebentar, waterfall dan iteratif itu adalah lifecycle, bukan fase.</p>
<p>Lalu apa itu lifecycle? Lifecycle adalah urutan kita menjalankan fase tersebut <strong>di keseluruhan project</strong>. Bagaimana maksudnya ini?</p>
<p>Begini, misalnya kita membuat aplikasi akunting. Aplikasi ini terdiri dari beberapa modul yaitu; ledger, hutang/piutang, dan manajemen kas. Lifecycle waterfall adalah apabila kita menjalankan project dengan urutan seperti ini :</p>
<ol>
<li>
<p>Project Planning</p>
<ol>
<li>Modul Ledger</li>
<li>Modul Hutang/Piutang</li>
<li>Modul Kas</li>
</ol>
</li>
<li>
<p>Requirement Development</p>
<ol>
<li>Modul Ledger</li>
<li>Modul Hutang/Piutang</li>
<li>Modul Kas</li>
</ol>
</li>
<li>
<p>Coding</p>
<ol>
<li>Modul Ledger</li>
<li>Modul Hutang/Piutang</li>
<li>Modul Kas</li>
</ol>
</li>
<li>
<p>UAT</p>
<ol>
<li>Modul Ledger</li>
<li>Modul Hutang/Piutang</li>
<li>Modul Kas</li>
</ol>
</li>
<li>
<p>Implementasi</p>
<ol>
<li>Modul Ledger</li>
<li>Modul Hutang/Piutang</li>
<li>Modul Kas</li>
</ol>
</li>
</ol>
<p>Lifecycle waterfall memang memiliki banyak kelemahan, apalagi kalau projectnya besar. Karena rentang waktu yang jauh antara fase requirement dan fase UAT, maka biasanya pada saat UAT user merasa kaget karena merasa aplikasi yang dibuat tidak sesuai ekspektasi. Entah karena user sudah lupa requirement yang dia bikin sendiri, ataupun karena proses bisnisnya memang sudah berubah.</p>
<p>Lifecycle yang dijalankan ArtiVisi adalah Staged Delivery, seperti yang dijelaskan di bukunya Steve McConnell berjudul Rapid Development. Berikut urutannya.</p>
<ol>
<li>
<p>Global / High Level</p>
<ol>
<li>Project Planning</li>
<li>Requirement Development</li>
</ol>
</li>
<li>
<p>Modul Ledger</p>
<ol>
<li>Project Planning</li>
<li>Requirement Development</li>
<li>Coding</li>
<li>UAT</li>
<li>Implementasi</li>
</ol>
</li>
<li>
<p>Modul Hutang/Piutang</p>
<ol>
<li>Project Planning</li>
<li>Requirement Development</li>
<li>Coding</li>
<li>UAT</li>
<li>Implementasi</li>
</ol>
</li>
<li>
<p>Modul Kas</p>
<ol>
<li>Project Planning</li>
<li>Requirement Development</li>
<li>Coding</li>
<li>UAT</li>
<li>Implementasi</li>
</ol>
</li>
</ol>
<p>Pendekatan ini bukan waterfall, karena delivery-nya iteratif dan kecil-kecil. Satu modul biasanya berkisar 1 - 2 bulan saja, kira-kira sesuai dengan panjang sprint yang dianjurkan di metodologi Scrum. Tapi pendekatan ini juga bukan murni agile, karena ada global project planning dan requirement di awal. Untuk apa ada kegiatan itu? Tidak lain dan tidak bukan adalah untuk mengantisipasi interkoneksi antar modul.</p>
<p>Bila kita langsung mengambil salah satu modul untuk diiterasi, maka ada resiko modul tersebut tidak nyambung dengan modul lainnya. Memang bisa disambungkan, tapi nanti akan berkesan tambal sulam. Istilahnya Fred Brooks dalam Mythical Man Month, tidak ada Conceptual Integrity. Oleh karena itu kita perlu melakukan global requirement development untuk melihat interaksi antar modul.</p>
<p>Pembaca yang teliti mungkin juga akan bertanya, mengapa kita menggunakan istilah Requirement Development, bukannya Requirement Gathering? Ini karena untuk mendapatkan requirement, perlu usaha ekstra, tidak sekedar memungut (gathering) informasi di sana-sini. Seringkali kita harus menanyakan hal yang sama dari beberapa sudut pandang untuk mendapatkan apa maunya user. Di saat lain, kita harus bisa menebak fitur yang tersirat. Kalau user bilang, “Harus ada fitur untuk menyimpan data transaksi”, berarti tidak cuma ada screen edit dan list. Tapi juga harus ada fitur search berdasarkan tanggal, produk, nilai transaksi, dan atribut lainnya. Kadangkala ini juga berarti harus ada fitur untuk export/import ke format file lainnya. Jadi, untuk mendapatkan requirement, perlu proses development. Mulai dari sedikit, lalu diakumulasi, dan dikristalisasi sehingga benar-benar akurat dan lengkap. Jangan sampai ada yang ketinggalan.</p>
<p>Kelima fase yang dijelaskan di atas merupakan fase yang sekuensial. Artinya, satu fase dijalankan setelah fase lain selesai. Kita tidak bisa mulai implementasi sebelum UAT selesai. Well, bisa sih, tapi hasilnya <em>tidak akan menyenangkan</em>. Demikian juga, kalau belum selesai coding, ya jangan UAT dulu. Bisa sih maksain UAT, tapi tentu <em>tidak akan menyenangkan</em>. Begitu juga halnya kalau kita coding sebelum jelas requirementnya.</p>
<p>Selain fase yang sekuensial tersebut, ada juga serangkaian kegiatan yang harus kita lakukan secara kontinyu sepanjang project. Yaitu project tracking dan change management.</p>
<h2 id="project-tracking">Project Tracking</h2>
<p>Project tracking tidak sulit. Setiap minggu, harus ada orang yang melihat project plan dan membandingkan dengan kondisi sekarang. Task mana yang sudah selesai, mana yang sedang dikerjakan, mana yang sudah selesai. Kondisi sekarang ini direkap ke dalam satu laporan, 1-2 halaman, dan dilaporkan ke client dan manajemen. Di ArtiVisi, mengadopsi dari BaliCamp, selain rekap task juga disertakan rekap resiko dan masalah yang terjadi dalam proyek. Ini akan menjadi bahan diskusi dengan client, bagaimana cara memecahkan masalah tersebut, dan bagaimana mencegah resiko agar tidak berdampak merugikan.</p>
<h2 id="requirement-development">Requirement Development</h2>
<p>Sebelum membahas change management, kita bahas dulu requirement. Di ArtiVisi, requirement bentuknya adalah daftar screen aplikasi, biasanya dibuat dengan Netbeans. Kami tidak menggunakan format naratif seperti yang dianut beberapa organisasi dan dinamai User Requirement Specification (URS), Business Requirement Specification (BRS), atau Software Requirement Specification (SRS), User Story, atau istilah-istilah lainnya. Berdasarkan pengalaman, dokumen naratif membuat user malas berinteraksi. Untuk aplikasi shopping cart sederhana saja, bila dibuatkan *RS, akan menjadi lebih dari 20 halaman. Apalagi untuk aplikasi yang besar. Client akan malas membaca, dan akibatnya pada saat aplikasi dideliver hasilnya tidak sesuai ekspektasi, walaupun sesuai dengan *RS.</p>
<p>ArtiVisi menggunakan prototype aplikasi untuk requirement. Kita buatkan screen desktop ataupun web, diisi data hardcoded, dan sudah memiliki menu dan flow. Dari screen registrasi klik submit akan masuk screen konfirmasi, dst. Dengan menggunakan prototype yang bisa diisi dan diklik, user akan lebih bersemangat untuk berinteraksi, sehingga hasil requirement akan menjadi berkualitas. Pada fase requirement ini user bebas meminta modifikasi apapun asal masih dalam scope yang disepakati.</p>
<p>Setelah tidak ada lagi perubahan signifikan, semua screen dicapture, dimasukkan ke dokumen, dan diapprove client (sign off). Ini akan menjadi patokan dalam proses development, diantaranya digunakan oleh programmer untuk coding, dan technical writer untuk membuat user manual.</p>
<h2 id="change-management">Change Management</h2>
<p>Perubahan setelah sign off harus melalui prosedur change management. Ini gunanya agar progress project bisa terkendali. Sering sekali banyak project molor dan tidak selesai-selesai karena banyak perubahan ini itu yang tidak dikelola dengan baik sehingga tidak terlihat titik akhirnya.</p>
<p><a href="/images/uploads/2010/02/change-procedure_id-212x300.png"><img src="/images/uploads/2010/02/change-procedure_id-212x300.png" alt="Prosedur Change Management " /></a></p>
<p>Prosedur change management tidak rumit. Hanya terdiri dari tiga langkah saja, yaitu :</p>
<ol>
<li>Requester menjelaskan perubahan yang diminta, apa alasan yang mendasari perubahan</li>
<li>Tim development menganalisa dampak perubahan, apakah ada coding yang berubah, user manual, test scenario, dsb. Output dari analisa ini adalah estimasi berapa tambahan waktu, tenaga, dan biaya untuk mengeksekusi perubahan ini</li>
<li>
<p>Manajemen di sisi client memutuskan berdasarkan estimasi tersebut, apakah perubahan ini akan :</p>
<ul>
<li>Dijalankan segera</li>
<li>Ditunda ke iterasi berikut</li>
<li>Ditolak, artinya tidak jadi dijalankan pada project ini, mungkin saja dijadikan project baru.</li>
</ul>
</li>
</ol>
<p>Dengan adanya proses ini, akan jelas berapa hari project akan mundur, dan berapa biaya tambahannya.</p>
<p>Demikian sharing tentang praktek yang kita gunakan di ArtiVisi. Metodologi ini cukup sederhana, sehingga bisa dijalankan bahkan di project yang one-man-show. Project manager dia, business analyst dia, programmer dia juga, training lagi-lagi dia, terima transferan tidak lain dan tidak bukan adalah dia juga. Di project berskala besar, metodologi ini juga cukup efektif untuk membuat project berjalan secara predictable.</p>
<p>Masih ada penjelasan lebih lanjut yang harus ditulis, diantaranya bagaimana cara menghitung (estimasi) project, dan bagaimana melakukan project planning dan tracking yang efektif. Ini akan dibahas di tulisan yang lain.</p>
<p>Bila ingin berkomentar atau berdiskusi, silahkan bergabung di milis it-project-indonesia@googlegroups.com dengan cara mengirim email kosong ke it-project-indonesia-subscribe@googlegroups.com</p>
Konfigurasi lokasi logfile pada Spring MVC2009-07-15T19:38:18+07:00https://software.endy.muhardin.com/java/log4j-spring-mvc<p>Di mana kita harus menyimpan log output aplikasi kita? Tentunya kita ingin menggunakan lokasi yang dinamis sesuai dengan lokasi deployment. Misalnya, di Windows kita mungkin mendeploy aplikasi kita di</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C:\Program Files\Apache Tomcat\webapps\aplikasi-saya
</code></pre></div></div>
<p>Sedangkan di Linux, kita mendeploy aplikasi di</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/apache-tomcat/webapps/aplikasi-saya
</code></pre></div></div>
<p>Dengan kemungkinan seperti di atas, bagaimana kita harus menulis konfigurasi log4j?
Mudah, bila kita menggunakan Spring MVC.</p>
<p>Kita bisa menggunakan <code class="language-plaintext highlighter-rouge">Log4jConfigListener</code> yang disediakan Spring. Class ini memungkinkan kita menggunakan variabel di konfigurasi log4j kita. Kita mendaftarkan class ini di dalam <code class="language-plaintext highlighter-rouge">web.xml</code>, sebelum <code class="language-plaintext highlighter-rouge">ContextLoaderListener</code>, seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</code></pre></div></div>
<p>Dengan adanya <code class="language-plaintext highlighter-rouge">Log4jConfigListener</code> ini, kita bisa menyebutkan lokasi konfigurasi log4j seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:artivisi-log4j.properties</param-value>
</context-param>
</code></pre></div></div>
<p>Isi <code class="language-plaintext highlighter-rouge">artivisi-log4j.properties</code> terlihat seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Konfigurasi kategori
log4j.rootLogger=INFO,fileout
# File output
log4j.appender.fileout=org.apache.log4j.DailyRollingFileAppender
log4j.appender.fileout.file=${webapp.root.path}/WEB-INF/logs/application.log
log4j.appender.fileout.datePattern='.'yyyy-MM-dd
log4j.appender.fileout.layout=org.apache.log4j.PatternLayout
log4j.appender.fileout.layout.conversionPattern=%d [%t] %p (%F:%L) %m%n
</code></pre></div></div>
<p>Perhatikan konfigurasi <code class="language-plaintext highlighter-rouge">log4j.appender.fileout.file</code>. Kita menggunakan variabel <code class="language-plaintext highlighter-rouge">${webapp.root.path}</code> yang akan diisi dengan nilai lokasi deployment aplikasi web kita. Variabel <code class="language-plaintext highlighter-rouge">${webapp.root.path}</code> ini didefinisikan dalam web.xml sebagai berikut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <context-param>
<param-name>webAppRootKey</param-name>
<param-value>webapp.root.path</param-value>
</context-param>
</code></pre></div></div>
<p>Dengan konfigurasi ini, kita dapat meletakkan log output kita di</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C:\Program Files\Apache Tomcat\webapps\aplikasi-saya\WEB-INF\logs\application.log
</code></pre></div></div>
<p>bila kita mendeploy di Windows, dan di</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/apache-tomcat/webapps/aplikasi-saya/WEB-INF/logs/application.log
</code></pre></div></div>
<p>bila kita deploy di Linux.</p>
<p>Konfigurasi di atas bisa disederhanakan lagi bila kita mengikuti nilai default yang disediakan Spring, yaitu cukup seperti ini dalam <code class="language-plaintext highlighter-rouge">web.xml</code> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</code></pre></div></div>
<p>Kemudian memberi nama file konfigurasi logger kita <code class="language-plaintext highlighter-rouge">log4j.properties</code> yang berada di top level dalam classpath, dan berisi seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Konfigurasi kategori
log4j.rootLogger=INFO,fileout
# File output
log4j.appender.fileout=org.apache.log4j.DailyRollingFileAppender
log4j.appender.fileout.file=${webapp.root}/WEB-INF/logs/application.log
log4j.appender.fileout.datePattern='.'yyyy-MM-dd
log4j.appender.fileout.layout=org.apache.log4j.PatternLayout
log4j.appender.fileout.layout.conversionPattern=%d [%t] %p (%F:%L) %m%n
</code></pre></div></div>
<p>Nilai variabel <code class="language-plaintext highlighter-rouge">${webapp.root}</code> secara default akan diisi dengan lokasi deployment tanpa harus mengkonfigurasi <code class="language-plaintext highlighter-rouge">webAppRootKey</code></p>
Menjalankan Spring HTTP Invoker di Sun JRE 6 HTTP Server2009-07-03T21:41:28+07:00https://software.endy.muhardin.com/java/spring-httpinvoker-sun-jre6-httpserver<p>Dulu, kita sudah mencoba untuk <a href="http://endy.artivisi.com/blog/java/remoting-dengan-spring/">membuat remoting service dengan menggunakan Spring Framework</a>. Salah satu protokol yang digunakan adalah Spring HTTP Invoker. Untuk mempublish service dengan protokol ini, kita harus menggunakan servlet engine, misalnya Tomcat.</p>
<p>Akan tetapi, Sun Microsystem merilis Java versi 6 yang sudah dilengkapi dengan HTTP Server sederhana. Dengan memanfaatkan fitur ini, kita tidak perlu lagi menggunakan Tomcat hanya untuk mempublish service dengan HTTP Invoker. Ini akan sangat bermanfaat untuk aplikasi kecil yang ingin dipanggil oleh aplikasi lain.</p>
<p>Pada artikel ini, kita akan mempublish service dengan protokol HTTP Invoker pada HTTP Server yang disediakan oleh Sun JRE versi 6.</p>
<p>Di Netbeans, kita akan membuat tiga project, yaitu</p>
<ul>
<li>
<p>remoting-shared : project ini menampung interface RemotingService, yang akan digunakan di client dan server</p>
</li>
<li>
<p>remoting-server : project ini yang akan mempublish service. Implementasi RemotingService juga ada di sini</p>
</li>
<li>
<p>remoting-client : project ini yang akan mengakses service yang dipublish remoting-server</p>
</li>
</ul>
<p>Berikut screenshot Netbeans. Project remoting-server dan remoting-client memiliki dependensi terhadap remoting-shared.</p>
<p>Pertama, mari kita lihat dulu service interfacenya. Berikut adalah kode programnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.tutorial.remoting.spring.service.api;
public interface RemotingService {
public String halo(String nama);
}
</code></pre></div></div>
<p>Kode program ini berada di project remoting-shared.</p>
<p>Berikutnya, kita lihat dulu di sisi client. Kita cuma butuh satu class untuk menjalankan aplikasi, yaitu ClientLauncher sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.tutorial.remoting.spring.client;
public class ClientLauncher {
private static final Logger log = Logger.getLogger(ClientLauncher.class.getName());
public static void main(String[] args) {
AbstractApplicationContext ctx =
new ClassPathXmlApplicationContext("client-ctx.xml", ClientLauncher.class);
ctx.registerShutdownHook();
RemotingService service = (RemotingService) ctx.getBean("remotingService");
String msg = service.halo("endy");
log.info("Pesan dari server : "+msg);
}
}
</code></pre></div></div>
<p>ClientLauncher akan membaca client-ctx.xml yang isinya seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- proxy dengan protokol HTTP Invoker -->
<bean id="remotingService"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl"
value="http://localhost:9090/RemotingService"/>
<property name="serviceInterface"
value="com.artivisi.tutorial.remoting.spring.service.api.RemotingService"/>
</bean>
</beans>
</code></pre></div></div>
<p>Seperti kita lihat di atas, client mengakses service yang ada di komputer lokal (localhost) di port 9090, dengan nama service RemotingService.</p>
<p>Selanjutnya, mari kita implement project remoting-server. Di sini ada implementasi RemotingService sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.artivisi.tutorial.remoting.spring.service.impl;
@Service("remotingService")
public class RemotingServiceImpl implements RemotingService {
Logger log = Logger.getLogger(RemotingServiceImpl.class.getName());
public String halo(String nama) {
log.info("Terima dari client : "+nama);
return "Halo, "+nama;
}
}
</code></pre></div></div>
<p>Kemudian ada class untuk menjalankan aplikasi di sisi server. Berikut ServerLauncher.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.tutorial.remoting.spring.server;
public class ServerLauncher {
public static void main(String[] args) {
AbstractApplicationContext ctx =
new ClassPathXmlApplicationContext("server-ctx.xml", ServerLauncher.class);
ctx.registerShutdownHook();
}
}
</code></pre></div></div>
<p>ServerLauncher membaca file konfigurasi server-ctx.xml. Inilah isinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- menginstankan Sun HttpServer dalam JRE 6 -->
<bean class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
<property name="contexts">
<map>
<entry key="/RemotingService" value-ref="remotingServiceHttpInvoker"/>
</map>
</property>
<property name="port" value="9090" />
</bean>
</beans>
</code></pre></div></div>
<p>Pada blok konfigurasi pertama, kita menginstankan Sun HttpServer yang ada di JRE 6. HttpServer ini akan berjalan di port 9090, sesuai dengan yang kita konfigurasi di sisi client. Di sana terlihat bahwa URL /RemotingService akan ditangani oleh remotingServiceHttpInvoker. Berikut konfigurasinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <!-- publish service dengan protokol HttpInvoker -->
<bean id="remotingServiceHttpInvoker"
class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"
p:service-ref="remotingService"
p:serviceInterface="com.artivisi.tutorial.remoting.spring.service.api.RemotingService"
/>
</code></pre></div></div>
<p>Selanjutnya, kita suruh Spring mendeteksi implementasi service kita secara otomatis, yaitu class yang ada anotasi @Service.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <context:component-scan base-package="com.artivisi"/>
</code></pre></div></div>
<p>Berikut adalah dependensi pustaka di project client.</p>
<p><a href="/images/uploads/2009/07/remoting-library-client-300x220.png"><img src="/images/uploads/2009/07/remoting-library-client-300x220.png" alt="Library untuk Project Client " /></a></p>
<p>Dan ini untuk di server.</p>
<p><a href="/images/uploads/2009/07/remoting-library-server-300x234.png"><img src="/images/uploads/2009/07/remoting-library-server-300x234.png" alt="Library untuk Project Server " /></a></p>
<p>Keseluruhan project akan terlihat seperti ini.</p>
<p><a href="/images/uploads/2009/07/remoting-folder-structure-231x300.png"><img src="/images/uploads/2009/07/remoting-folder-structure-231x300.png" alt="Struktur Folder semua Project " /></a></p>
<p>Semua library dapat diambil dari <a href="http://www.springsource.org/download">distribusi Spring Framework</a> dan <a href="http://www.springsource.com/repository/app/">Repository SpringSource</a>.</p>
<p>Coba jalankan ServerLauncher, kita akan melihat log seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Jul 3, 2009 2:57:25 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@be2358: display name [org.springframework.context.support.ClassPathXmlApplicationContext@be2358]; startup date [Fri Jul 03 14:57:25 WIT 2009]; root of context hierarchy
Jul 3, 2009 2:57:26 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [server-ctx.xml]
Jul 3, 2009 2:57:26 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@be2358]: org.springframework.beans.factory.support.DefaultListableBeanFactory@f11404
Jul 3, 2009 2:57:26 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@f11404: defining beans [org.springframework.remoting.support.SimpleHttpServerFactoryBean#0,remotingServiceHttpInvoker,remotingService,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor]; root of factory hierarchy
Jul 3, 2009 2:57:27 PM org.springframework.remoting.support.SimpleHttpServerFactoryBean afterPropertiesSet
INFO: Starting HttpServer at address 0.0.0.0/0.0.0.0:9090
</code></pre></div></div>
<p>Lalu jalankan ClientLauncher, inilah log yang muncul.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Jul 3, 2009 2:58:12 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@be2358: display name [org.springframework.context.support.ClassPathXmlApplicationContext@be2358]; startup date [Fri Jul 03 14:58:12 WIT 2009]; root of context hierarchy
Jul 3, 2009 2:58:12 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [client-ctx.xml]
Jul 3, 2009 2:58:13 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@be2358]: org.springframework.beans.factory.support.DefaultListableBeanFactory@d2906a
Jul 3, 2009 2:58:13 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@d2906a: defining beans [remotingService]; root of factory hierarchy
Jul 3, 2009 2:58:13 PM com.artivisi.tutorial.remoting.spring.client.ClientLauncher main
INFO: Pesan dari server : Halo, endy
</code></pre></div></div>
<p>Setelah ClientLauncher dijalankan, di log server akan muncul informasi sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INFO: Terima dari client : endy
</code></pre></div></div>
<p>Demikianlah cara menggunakan embedded HttpServer. Selamat mencoba.</p>
PostgreSQL Sequence dengan Hibernate2009-06-22T22:08:58+07:00https://software.endy.muhardin.com/java/postgresql-sequence-hibernate<p>Menggunakan PostgreSQL Sequence dengan Hibernate</p>
<p>Pada artikel ini, kita akan membahas tentang bagaimana membuat Hibernate menggunakan sequence yang dibuat PostgreSQL.</p>
<p>Bila kita membuat Hibernate mapping untuk entity class sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Entity @Table(name="example")
public class Example {
@Id @GeneratedValue
private Integer id;
private String name;
// getter dan setter
}
</code></pre></div></div>
<p>kemudian menyuruh Hibernate untuk menggenerate DDL ke PostgreSQL, maka kita akan mendapatkan hasil sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create table example (id int4 not null, name varchar(255), primary key (id))
create sequence example_id_seq
</code></pre></div></div>
<p>Artinya, Hibernate akan membuat sequence bernama example_id_seq dan menggunakannya untuk menghasilkan id.</p>
<p>Skema yang dihasilkan ini berbeda dengan skema yang biasa digunakan DBA PostgreSQL dalam membuat tabel, yaitu seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE TABLE example (id serial, name text)
</code></pre></div></div>
<p>Bila kita menggunakan mapping di atas ke skema tabel dengan id bertipe serial ini, kita akan mendapatkan exception sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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
</code></pre></div></div>
<p>Ini disebabkan karena mapping di atas akan mencari sequence bernama hibernate_sequence yang tidak ada kalau kita membuat tabel dengan id serial.</p>
<p>Solusinya adalah dengan menggunakan Hibernate Annotation Extension, yaitu anotasi @GenericGenerator seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@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;
}
</code></pre></div></div>
<p>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.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\d example
Table "public.example"
Column | Type | Modifiers
--------+---------+-------------------------------------------------------
id | integer | not null default nextval('example_id_seq'::regclass)
name | text |
</code></pre></div></div>
<p>Barulah setelah itu kita bisa menyimpan object ke database dengan mulus.</p>
Integrasi aplikasi kantor pusat dan cabang [Bagian 3]2009-06-16T17:56:28+07:00https://software.endy.muhardin.com/java/integrasi-pusat-cabang-3<p>Pada artikel sebelumnya, kita telah membahas tentang <a href="http://endy.artivisi.com/blog/java/integrasi-pusat-cabang-1/">konsep integrasi aplikasi</a>, dan <a href="http://endy.artivisi.com/blog/java/integrasi-pusat-cabang-2/">implementasinya menggunakan Spring Integration</a>. Contoh implementasi kita kemarin, walaupun mencakup integrasi end-to-end, masih sangat sederhana.</p>
<p>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.</p>
<p>Untuk memecahkan masalah ini, kita menggunakan Router. Router bertugas memilih channel sesuai dengan message yang datang. Berikut skema penggunaan router.</p>
<p><a href="/images/uploads/2009/06/router-demo-300x91.png"><img src="/images/uploads/2009/06/router-demo-300x91.png" alt="Skema Penggunaan Router " /></a></p>
<p>Sebelum membahas tentang router, baiklah kita lihat dulu data penjualan yang akan dikirim. Berikut class penjualan</p>
<h4 id="penjualan">Penjualan</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Penjualan {
private Integer id;
private Date tanggal = new Date();
private List<PenjualanDetail> details = new ArrayList<PenjualanDetail>();
// getter dan setter
}
</code></pre></div></div>
<p>Berikutnya, penjualan detail.</p>
<h4 id="penjualandetail">PenjualanDetail</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class PenjualanDetail {
private Integer id;
private Penjualan penjualan;
private Produk produk;
private Integer jumlah;
// getter dan setter
}
</code></pre></div></div>
<p>Object baru ini tentu saja harus kita buatkan transformernya untuk mengkonversi object menjadi JSON.</p>
<h4 id="cabangservice">CabangService</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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();
}
}
</code></pre></div></div>
<p>Penjualan dan PenjualanDetail akan kita proses melalui method simpan dan hapus yang ada di CabangService.</p>
<h4 id="cabangservice-1">CabangService</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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());
}
}
</code></pre></div></div>
<p>Tentunya nanti method save dan delete ini akan lebih canggih dari ini, misalnya insert dan delete ke database.</p>
<p>Kalau kita lihat skemanya, ada satu titik di mana message akan dilihat dan disalurkan ke channel yang sesuai.</p>
<p><a href="/images/uploads/2009/06/router-only-300x104.png"><img src="/images/uploads/2009/06/router-only-300x104.png" alt="Routing Message " /></a></p>
<p>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.</p>
<p>Karena nantinya property operasi ini akan digunakan tidak saja untuk Penjualan, tapi juga transaksi lainnya, baiklah kita buat saja di superclass DomainObject sebagai berikut.</p>
<h4 id="penjualan-1">Penjualan</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class DomainObject {
private String operasi;
}
</code></pre></div></div>
<p>Berikut class Penjualan yang sudah dimodifikasi.</p>
<h4 id="penjualan-2">Penjualan</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Penjualan extends DomainObject {
private Integer id;
private Date tanggal = new Date();
private List<PenjualanDetail> details = new ArrayList< <PenjualanDetail>();
// getter dan setter
}
</code></pre></div></div>
<p>Property operasi ini akan dilihat oleh router untuk menentukan nama channel. Berikut implementasi Router.</p>
<h4 id="router">Router</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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;
}
}
</code></pre></div></div>
<p>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.</p>
<p>Berikut unit test untuk router di atas.</p>
<h4 id="router-1">Router</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class NamaKelasDanOperasiRouterTest {
@Test
public void testPilihChannel() {
Penjualan p = new Penjualan();
p.setOperasi("simpan");
assertEquals("penjualan-simpan", new NamaKelasDanOperasiRouter().pilihChannel(p));
}
}
</code></pre></div></div>
<p>Berikut aliran message mulai dari gateway sampai menjadi JSON.</p>
<p><a href="/images/uploads/2009/06/gateway-to-json-300x31.png"><img src="/images/uploads/2009/06/gateway-to-json-300x31.png" alt="Dari Gateway sampai menjadi JSON " /></a></p>
<p>Dan ini adalah konfigurasi Spring Integration untuk flow di atas.</p>
<h4 id="gateway-ke-json">Gateway ke JSON</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><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"/>
</code></pre></div></div>
<p>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.</p>
<p><a href="/images/uploads/2009/06/bridge-300x45.png"><img src="/images/uploads/2009/06/bridge-300x45.png" alt="bridge antara incoming dan outgoing " /></a></p>
<p>Berikut konfigurasi bridge untuk menghubungkan outgoingJson dan incomingJson.</p>
<h4 id="bridge">Bridge</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><bridge
input-channel="outgoingJson"
output-channel="incomingJson"
/>
</code></pre></div></div>
<p>Dari incomingJson, kita konversi dulu menjadi object.
Berikut konfigurasinya.</p>
<h4 id="json-ke-object">JSON ke Object</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><channel id="incomingJson"/>
<transformer
input-channel="incomingJson"
output-channel="incomingPenjualan"
ref="jsonTransformer"
method="jsonToPenjualan"
/>
</code></pre></div></div>
<p>Setelah menjadi object, kita masukkan ke router untuk ditentukan channelnya.</p>
<h4 id="routing">Routing</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><router
input-channel="incomingPenjualan"
ref="namaKelasDanOperasiRouter"
method="pilihChannel"
/>
</code></pre></div></div>
<p>Kemudian, dari channel kita sambungkan ke method save dan delete.</p>
<h4 id="method-save">Method Save</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><channel id="penjualan-simpan"/>
<service-activator
input-channel="penjualan-simpan"
ref="cabangService"
method="save"/>
</code></pre></div></div>
<h4 id="method-delete">Method Delete</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><channel id="penjualan-hapus"/>
<service-activator
input-channel="penjualan-hapus"
ref="cabangService"
method="delete"/>
</code></pre></div></div>
<p>Terakhir, kita buat class untuk menjalankan semua rangkaian integrasi ini.</p>
<h4 id="cabangrouterdemo">CabangRouterDemo</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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;
}
}
</code></pre></div></div>
<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.</p>
Simulasi Slow Network2009-06-15T15:21:33+07:00https://software.endy.muhardin.com/linux/simulasi-slow-network<p>Di salah satu proyek ArtiVisi, kita memiliki aplikasi client-server yang terhubung melalui koneksi internet berbandwidth kecil. Client menghubungi server melalui modem GPRS.</p>
<p>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.</p>
<p>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.</p>
<p>Alternatif kedua, menggunakan aplikasi ip_relay. Aplikasi ini bisa diinstal dengan nama paket iprelay.</p>
<p>Berikut adalah referensi pemakaian tc :</p>
<ul>
<li>
<p><a href="http://www.kdedevelopers.org/node/1878">pipita’s blog</a></p>
</li>
<li>
<p><a href="http://henrydu.com/blog/how-to/simulate-a-slow-link-by-linux-bridge-123.html">Henry’s Point</a></p>
</li>
</ul>
<p>Dan ini adalah referensi pemakaian iprelay :</p>
<ul>
<li>
<p><a href="http://ubuntuforums.org/showthread.php?t=670628">Ubuntu Forum</a></p>
</li>
<li>
<p><a href="http://www.stewart.com.au/ip_relay/README">Readme di website ip_relay</a></p>
</li>
</ul>
Integrasi aplikasi kantor pusat dan cabang [Bagian 2]2009-06-12T20:43:16+07:00https://software.endy.muhardin.com/java/integrasi-pusat-cabang-2<p>Pada <a href="http://endy.artivisi.com/blog/java/integrasi-pusat-cabang-1/">artikel sebelumnya</a>, kita sudah mendiskusikan requirement yang diinginkan. Sekarang kita akan melakukan implementasi menggunakan <a href="http://www.springsource.org/spring-integration">Spring Integration</a>.</p>
<p>Spring Integration memiliki beberapa abstraksi utama yang perlu kita ketahui agar bisa membuat implementasi, yaitu :</p>
<ul>
<li>
<p>Message : ini adalah data yang akan kita kirim, proses, dan terima</p>
</li>
<li>
<p>Channel : ini adalah saluran tempat lewatnya message</p>
</li>
<li>
<p>Endpoint : ini adalah ujung dari channel</p>
</li>
<li>
<p>Transport : mekanisme pengiriman message. Spring Integration mendukung messaging (email,JMS), remoting (Web Service, RMI, HttpInvoker, HTTP), file, dan lainnya (stream)</p>
</li>
</ul>
<p>Ilustrasi hubungan antara message, channel, dan endpoint bisa dilihat di gambar berikut :</p>
<p><a href="/images/uploads/2009/06/msg-channel-endpoint-300x59.png"><img src="/images/uploads/2009/06/msg-channel-endpoint-300x59.png" alt="Hubungan Message, Channel, dan Endpoint " /></a></p>
<p>Message terdiri dari dua bagian utama, yaitu header dan payload (isi message).</p>
<h3 id="channel">Channel</h3>
<p>Berdasarkan kemampuan menampung message, channel dibedakan menjadi :</p>
<ul>
<li>
<p>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)</p>
</li>
<li>
<p>Subscribable : tidak memiliki buffer, tiap message yang masuk langsung dikirim ke endpoint. Pengirim harus menunggu sampai message diterima.</p>
</li>
</ul>
<p>Berdasarkan perilaku pengiriman message, channel dibedakan menjadi :</p>
<ul>
<li>
<p>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</p>
</li>
<li>
<p>Point to Multipoint (PTM) : tiap message akan dikirim ke semua penerima yang terdaftar.</p>
</li>
</ul>
<p>Ada beberapa implementasi channel :</p>
<ul>
<li>
<p>Direct Channel : subscribable dan point-to-point. Seluruh proses (kirim, masuk channel, terima) akan dilakukan dalam thread yang sama</p>
</li>
<li>
<p>Queue Channel : pollable dan point to point. Message yang datang duluan akan dikirim duluan juga (FIFO)</p>
</li>
<li>
<p>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</p>
</li>
<li>
<p>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.</p>
</li>
<li>
<p>ThreadLocal Channel : pollable dan point-to-point. Message disimpan di thread local, sehingga tidak disharing dengan thread yang berbeda.</p>
</li>
<li>
<p>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.</p>
</li>
</ul>
<h3 id="endpoint">Endpoint</h3>
<p>Endpoint digunakan untuk menghubungkan channel. Pemrosesan message dilakukan dalam endpoint. Ada beberapa jenis endpoint, yaitu:</p>
<ul>
<li>
<p>Service Activator : kalau kita ingin memanggil business method kita, gunakan endpoint ini.</p>
</li>
<li>
<p>Channel Adapter : ini adalah penghubung channel dengan transport, baik untuk menerima message (inbound) ataupun mengirim message (outbound).</p>
</li>
<li>
<p>Transformer : digunakan untuk mengubah format message</p>
</li>
<li>
<p>Filter : digunakan untuk memutuskan apakah suatu message akan diterima atau dibuang</p>
</li>
<li>
<p>Router : digunakan untuk memilih channel mana yang akan menerima message</p>
</li>
<li>
<p>Splitter : digunakan untuk memecah message menjadi beberapa bagian untuk diproses secara independen</p>
</li>
<li>
<p>Aggregator : digunakan untuk menggabungkan beberapa message menjadi satu message untuk diteruskan ke channel berikutnya</p>
</li>
<li>
<p>Resequencer : digunakan untuk menyusun urutan message</p>
</li>
</ul>
<h3 id="implementasi-kantor-pusat">Implementasi Kantor Pusat</h3>
<p>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.</p>
<p><a href="/images/uploads/2009/06/pusat-json-300x24.png"><img src="/images/uploads/2009/06/pusat-json-300x24.png" alt="Flow konversi data menjadi JSON " /></a></p>
<p>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.</p>
<p><a href="/images/uploads/2009/06/json-file-300x56.png"><img src="/images/uploads/2009/06/json-file-300x56.png" alt="Flow message JSON ke shared folder " /></a></p>
<p>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.</p>
<p><a href="/images/uploads/2009/06/json-email-300x35.png"><img src="/images/uploads/2009/06/json-email-300x35.png" alt="Flow message JSON ke Email " /></a></p>
<p>Kita harus membuat beberapa file sebagai berikut :</p>
<ul>
<li>
<p>Produk.java : domain model dari data yang akan kita kirim</p>
</li>
<li>
<p>JsonTransformer.java : kode program untuk mengubah object Produk menjadi JSON</p>
</li>
<li>
<p>JsonTransformerTest.java : kode program untuk mengetes ProdukTransformer</p>
</li>
<li>
<p>PusatSender.java : kode program untuk mengaktifkan Spring Integration dan mengirim data produk</p>
</li>
<li>
<p>Gateway.java : interface untuk mengirim message. Kita harus membuat ini agar tidak ada dependensi ke library Spring Integration</p>
</li>
<li>
<p>pusat-integration-ctx.xml : konfigurasi Spring Integration</p>
</li>
</ul>
<p>Berikut kode programnya.</p>
<h4 id="produkjava">Produk.java</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Produk implements Serializable {
private Integer id;
private String kode;
private String nama;
// getter dan setter generate dengan IDE
}
</code></pre></div></div>
<h4 id="jsontransformerjava">JsonTransformer.java</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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();
}
}
</code></pre></div></div>
<h4 id="jsontransformertestjava">JsonTransformerTest.java</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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));
}
}
</code></pre></div></div>
<h4 id="pusatsenderjava">PusatSender.java</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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);
}
}
</code></pre></div></div>
<h4 id="gatewayjava">Gateway.java</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface Gateway {
public void send(Produk p);
}
</code></pre></div></div>
<h4 id="pusat-integration-ctxxml">pusat-integration-ctx.xml</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><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>
</code></pre></div></div>
<h3 id="implementasi-kantor-cabang">Implementasi Kantor Cabang</h3>
<p>Di sisi kantor cabang, berikut aliran message dari transport hingga menjadi JSON. Kita mulai dengan transport file.</p>
<p><a href="/images/uploads/2009/06/file-json-300x35.png"><img src="/images/uploads/2009/06/file-json-300x35.png" alt="Flow dari shared folder menjadi JSON " /></a></p>
<p>Bila kita menggunakan email, berikut gambarnya</p>
<p><a href="/images/uploads/2009/06/email-json-300x35.png"><img src="/images/uploads/2009/06/email-json-300x35.png" alt="Flow dari Email menjadi JSON " /></a></p>
<p>Setelah menjadi JSON, kita proses sampai ke CabangService</p>
<p><a href="/images/uploads/2009/06/json-cabang-300x25.png"><img src="/images/uploads/2009/06/json-cabang-300x25.png" alt="Flow pemrosesan JSON menjadi Produk " /></a></p>
<p>Kita harus membuat beberapa file sebagai berikut :</p>
<ul>
<li>
<p>CabangReceiver.java : kode program untuk mengaktifkan Spring Integration</p>
</li>
<li>
<p>CabangService.java : kode program yang akan dipanggil setelah message diterima, dikonversi dari JSON menjadi Produk</p>
</li>
<li>
<p>cabang-integration-ctx.xml : konfigurasi Spring Integration</p>
</li>
</ul>
<p>Kode programnya.</p>
<h4 id="cabangreceiverjava">CabangReceiver.java</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class CabangReceiver {
public static void main(String[] args) {
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext("cabang-integration-ctx.xml", CabangReceiver.class);
ctx.registerShutdownHook();
}
}
</code></pre></div></div>
<h4 id="cabangservicejava">CabangService.java</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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());
}
}
</code></pre></div></div>
<h4 id="cabang-integration-ctxxml">cabang-integration-ctx.xml</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><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>
</code></pre></div></div>
<p>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.</p>
<p>Berikut konfigurasi untuk GMail</p>
<h4 id="debug-output">Debug Output</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <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>
</code></pre></div></div>
<h4 id="pop3">POP3</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <mail:inbound-channel-adapter
channel="incomingMail"
store-uri="pop3s://mygmailaccount:mygmailpassword@pop.gmail.com:995/INBOX"
java-mail-properties="javaMailProperty"/>
</code></pre></div></div>
<h4 id="imap">IMAP</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <mail:inbound-channel-adapter
channel="incomingMail"
store-uri="imaps://mygmailaccount:mygmailpassword@imap.gmail.com:993/INBOX" />
</code></pre></div></div>
<h4 id="imap-idle">IMAP-Idle</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <mail:imap-idle-channel-adapter channel="incomingMail"
store-uri="imaps://mygmailaccount:mygmailpassword@imap.gmail.com:993/INBOX"/>
</code></pre></div></div>
<p>Setelah email masuk, jangan lupa konversi dulu jadi String.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <channel id="incomingMail" />
<mail:mail-to-string-transformer
input-channel="incomingMail"
output-channel="incomingJson"/>
</code></pre></div></div>
<p>Demikianlah tutorial penggunaan Spring Integration. Dengan menggunakan Spring Integration ini, ada beberapa benefit yang kita dapatkan dibandingkan coding sendiri :</p>
<ul>
<li>
<p>Kode program lebih sedikit</p>
</li>
<li>
<p>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.</p>
</li>
<li>
<p>Aliran data lebih mudah dibaca, yaitu dalam konfigurasi Spring Integration</p>
</li>
<li>
<p>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.</p>
</li>
</ul>
<p>Konsumsi effort :</p>
<ul>
<li>
<p>Download : 1 jam</p>
</li>
<li>
<p>Browsing tutorial : 2 jam</p>
</li>
<li>
<p>Mempelajari Spring Integration : 8 jam</p>
</li>
<li>
<p>Membuat sample aplikasi : 4 jam</p>
</li>
<li>
<p>Menulis blog dan membuat gambar : 8 jam</p>
</li>
<li>
<p><strong>Total : 23 jam (3 hari)</strong></p>
</li>
</ul>
<p>Apakah dalam 3 hari kita bisa membuat implementasi kirim-terima data produk via email yang bebas bug ??</p>
Integrasi aplikasi kantor pusat dan cabang [Bagian 1]2009-06-11T21:39:48+07:00https://software.endy.muhardin.com/java/integrasi-pusat-cabang-1<p>Integrasi Aplikasi Pusat Cabang - Bagian I</p>
<p>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.</p>
<p>Pada artikel ini, saya akan mengelaborasi proses problem solving yang dilakukan, solusi yang dipilih, dan tentunya dilengkapi dengan source code.</p>
<h3 id="use-case">Use Case</h3>
<p>Ada beberapa use case yang ingin didukung oleh aplikasi, sebagai berikut :</p>
<ul>
<li>
<p>Pusat menambahkan jenis produk baru, data ini harus segera diimplementasikan juga di cabang</p>
</li>
<li>
<p>Transaksi yang dilakukan di cabang harus dikirim ke pusat agar bisa dibuatkan laporan keseluruhan</p>
</li>
<li>
<p>Data pelanggan di cabang harus juga dikirim ke pusat</p>
</li>
<li>
<p>Perpindahan stok barang dari pusat ke cabang dan sebaliknya</p>
</li>
</ul>
<h3 id="constraint">Constraint</h3>
<p>Bicara requirement, tentu tidak bisa dilepaskan dari constraint. Berikut constraint yang ada :</p>
<ul>
<li>
<p>Pusat dan cabang sama-sama terhubung ke internet menggunakan Speedy</p>
</li>
<li>
<p>Karena pakai Speedy, kita bisa mengekspose satu mesin ke internet menggunakan IP Public</p>
</li>
<li>
<p>IP Public tidak dedicated, dan mungkin berubah sewaktu-waktu</p>
</li>
<li>
<p>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.</p>
</li>
</ul>
<h3 id="alternatif-solusi">Alternatif Solusi</h3>
<p>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.</p>
<ul>
<li>
<p>Remoting</p>
<ul>
<li>
<p>Web Services</p>
</li>
<li>
<p>RMI</p>
</li>
<li>
<p>HttpInvoker</p>
</li>
</ul>
</li>
<li>
<p>Messaging</p>
<ul>
<li>
<p>JMS</p>
</li>
<li>
<p>Email (POP3/IMAP dan SMTP)</p>
</li>
</ul>
</li>
<li>
<p>File</p>
<ul>
<li>
<p>Shared Folder</p>
</li>
<li>
<p>FTP</p>
</li>
<li>
<p>SSH/SCP</p>
</li>
</ul>
</li>
<li>
<p>Database</p>
<ul>
<li>
<p>Shared Database</p>
</li>
<li>
<p>Replikasi Database</p>
</li>
</ul>
</li>
</ul>
<h3 id="pemilihan-solusi">Pemilihan Solusi</h3>
<p>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.</p>
<ul>
<li>
<p>Remoting dengan RMI : port tidak standar</p>
</li>
<li>
<p>Messaging dengan JMS : port tidak standar</p>
</li>
<li>
<p>Replikasi MySQL : protokol terlalu kompleks dan cerewet, port tidak standar</p>
</li>
</ul>
<p>Kita harus mempertimbangkan konsumsi bandwidth. Protokol yang rakus bandwidth bisa kita singkirkan, yaitu Remoting dengan WS.</p>
<p>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.</p>
<p>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.</p>
<p>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 <a href="http://en.wikipedia.org/wiki/IMAP_IDLE">IMAP-Idle</a>. Fitur ini akan sangat berguna kelak di kemudian hari. Kebetulan <a href="http://samj.net/2008/07/proof-gmail-imap-gimap-supports-imap.html">GMail sudah mendukung fitur ini</a>.</p>
<h3 id="desain-implementasi">Desain Implementasi</h3>
<p>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.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Produk {
private Integer id;
private String kode;
private String nama;
// getter dan setter
}
</code></pre></div></div>
<p>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 :</p>
<ul>
<li>
<p>java.io.Serializable, konversi dengan ObjectInputStream dan ObjectOutputStream</p>
</li>
<li>
<p>JSON</p>
</li>
<li>
<p>XML</p>
</li>
</ul>
<p>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.</p>
<p>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.</p>
<p>Sementara saya pilih JSON, karena ada <a href="http://json-lib.sourceforge.net/usage.html">json-lib</a> yang bisa mengkonversi object menjadi JSON dan sebaliknya dengan mudah.</p>
<p>Selanjutnya, bagaimana cara mengirim email?</p>
<p>Cara mengirim email dengan Java sudah ditunjukkan Ifnu <a href="http://ifnu.artivisi.com/?p=80">di sini</a>. 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.</p>
<p>Nah, pertama saya memutuskan <a href="http://static.springframework.org/spring/docs/2.5.x/reference/mail.html">menggunakan Spring untuk mengirim email</a>. Ibaratnya kalau coding sendiri itu berkelahi dengan tangan kosong, pakai Spring berkelahi pakai pistol. Jauh lebih cepat dan mudah.</p>
<p>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.</p>
<p>Saatnya membuka gudang senjata dan mencari senjata yang lebih besar. Saya menemukan rocket launcher, yaitu <a href="http://www.springsource.org/spring-integration">Spring Integration</a>.</p>
<p>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.</p>
<p>Berhubung artikel ini sudah terlalu panjang. Implementasi Spring Integrationnya diteruskan di <a href="http://endy.artivisi.com/blog/java/integrasi-pusat-cabang-2">bagian kedua</a>.</p>
Mengakses EJB secara Remote2009-06-03T20:46:00+07:00https://software.endy.muhardin.com/java/ejb-remote-clien<p>Mengakses EJB secara Remote</p>
<p>Beberapa buku tentang EJB, seperti EJB 3 in Action karangan Reza Rahman maupun Enterprise Java Beans 3.0 karangan Bill Burke, membahas secara detail tentang penggunaan EJB. Akan tetapi, ada satu hal kecil namun penting yang tidak dibahas. Bagaimana cara mengakses EJB secara remote?</p>
<p>Di sisi server, ini mudah. Cukup membuat remote interface saja beres. Tapi bagaimana cara lookupnya dari client? Artikel ini akan membahas cara memanggil EJB dari method main.</p>
<p>Sebelumnya, berikut adalah EJB yang akan kita panggil.</p>
<h3 id="halobeanjava">HaloBean.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.belajar.ejb.session;
@Stateless
public class HaloBean implements HaloRemote {
public String halo(String nama) {
return "Halo "+nama;
}
}
</code></pre></div></div>
<h3 id="haloremotejava">HaloRemote.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.belajar.ejb.session;
@Remote
public interface HaloRemote {
public String halo(String nama);
}
</code></pre></div></div>
<p>EJB ini akan kita deploy ke Glassfish menggunakan Netbeans. Bagaimana caranya? Silahkan buka menu Help Netbeans.</p>
<p>Berikut adalah kode untuk memanggil HaloBean secara remote.</p>
<h3 id="haloclientjava">HaloClient.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.belajar.ejb.client;
public class HaloClient {
public static void main(String[] args){
// 0. Konfigurasi host dan port
String ejbHost = "localhost";
String ejbPort = 12345;
String ejbName = HaloRemote.class.getName();
// 1. JNDI Context
Properties props = new Properties();
props.put("org.omg.CORBA.ORBInitialHost", ejbHost);
props.put("org.omg.CORBA.ORBInitialPort", ejbPort);
InitialContext ctx = new InitialContext(props);
// 2. Lookup EJB
HaloRemote haloBean = (HaloRemote) ctx.lookup(ejbName);
// 3. Panggil methodnya
System.out.println("Halo EJB : "+haloBean.halo("endy"));
}
}
</code></pre></div></div>
<p>Ada beberapa variabel yang perlu diperhatikan di sini, yaitu ejbHost, ejbPort, dan ejbName. Variabel ejbHost tentunya adalah komputer tempat EJB dideploy. Variabel ejbPort adalah port yang digunakan oleh Application Server untuk mempublish EJB. Port ini berbeda-beda tergantung merek application server dan cara deploymentnya. Karena saya menggunakan Netbeans untuk mendeploy ke Glassfish, maka saya harus cari tahu dulu berapa port yang digunakan Netbeans.</p>
<p>Caranya, login ke admin console Glassfish melalui Netbeans. Buka tab Services, klik kanan Glassfish, dan pilih View Admin Console.
(/images/uploads/2009/06/netbeans-view-admin-console.png)
Browser akan terbuka dan menampilkan halaman login Glassfish.
(/images/uploads/2009/06/glassfish-login.png)
Setelah login, klik Application Server di panel kiri. Port yang digunakan akan ditampilkan di panel kanan, yaitu dengan nama IIOP Port(s). Coba-coba saja semuanya. Di komputer saya, port yang benar adalah 10275.
(/images/uploads/2009/06/glassfish-iiop-port.png)
Variabel ejbName juga berbeda tergantung dari application server yang digunakan. Baca dokumentasi server Anda untuk memastikannya.</p>
Backup MySQL2009-06-02T15:23:43+07:00https://software.endy.muhardin.com/lain/backup-mysql<p>Sebelumnya, saya telah membahas script backup untuk <a href="https://software.endy.muhardin.com/linux/backup-trac/">Trac</a> maupun <a href="https://software.endy.muhardin.com/aplikasi/svn-parentpath-backup/">Subversion</a>. Kali ini, kita akan bahas backup script untuk MySQL.</p>
<p>Sama seperti backup script sebelumnya, script ini akan membuat folder sesuai dengan tanggal dan jam backup. Selanjutnya, script akan melakukan backup terhadap database MySQL sesuai dengan nama database yang ditentukan. Backup ini akan disimpan di folder yang kita tentukan.</p>
<p>Backup script ini sebenarnya hanya menjalankan perintah <code class="language-plaintext highlighter-rouge">mysqldump</code> saja. Ada dua kali pemanggilan <code class="language-plaintext highlighter-rouge">mysqldump</code>, yang pertama melakukan backup untuk skema database, yang kedua melakukan backup untuk data dalam tiap tabel.</p>
<p>Perintah untuk melakukan backup skema adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysqldump -d --compact <nama-database> > backup-skema-database-`date +%Y%m%d-%H%M`.sql
</code></pre></div></div>
<p>Sedangkan perintah untuk melakukan backup data adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysqldump -n -c -t --compact --single-transaction <nama-database> > backup-data-database-`date +%Y%m%d-%H%M`.sql
</code></pre></div></div>
<p>Berikut backup scriptnya. Misalnya kita beri nama <code class="language-plaintext highlighter-rouge">mysql-backup.sh</code> dan disimpan di folder <code class="language-plaintext highlighter-rouge">/root/backup-db</code></p>
<p>[Update - 7 Des 2009] Sudah ditambahkan perintah untuk kompresi hasil backupnya.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nb">test</span> <span class="nt">-x</span> /bin/date <span class="o">||</span> <span class="nb">exit</span> <span class="nt">-1</span>
<span class="nb">test</span> <span class="nt">-x</span> /usr/bin/mysqldump <span class="o">||</span> <span class="nb">exit</span> <span class="nt">-1</span>
<span class="nb">test</span> <span class="nt">-x</span> /bin/tar <span class="o">||</span> <span class="nb">exit</span> <span class="nt">-1</span>
<span class="nb">test</span> <span class="nt">-x</span> /bin/bzip2 <span class="o">||</span> <span class="nb">exit</span> <span class="nt">-1</span>
<span class="nv">DBHOST</span><span class="o">=</span><span class="nv">$1</span>
<span class="nv">DBNAME</span><span class="o">=</span><span class="nv">$2</span>
<span class="nv">USERNAME</span><span class="o">=</span><span class="nv">$3</span>
<span class="nv">PASSWORD</span><span class="o">=</span><span class="nv">$4</span>
<span class="nv">BACKUP_FOLDER</span><span class="o">=</span><span class="nv">$5</span>
<span class="nv">CURR_DATE</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>/bin/date +%Y%m%d-%H%M<span class="si">)</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">=</span> <span class="s1">''</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"Usage : </span><span class="nv">$0</span><span class="s2"> <db name> <username> <password> <backup folder>"</span>
<span class="k">return </span>1
<span class="k">fi
if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="o">=</span> <span class="s1">''</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"Usage : </span><span class="nv">$0</span><span class="s2"> <db name> <username> <password> <backup folder>"</span>
<span class="k">return </span>1
<span class="k">fi
if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span> <span class="o">=</span> <span class="s1">''</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"Usage : </span><span class="nv">$0</span><span class="s2"> <db name> <username> <password> <backup folder>"</span>
<span class="k">return </span>1
<span class="k">fi
if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$4</span><span class="s2">"</span> <span class="o">=</span> <span class="s1">''</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"Usage : </span><span class="nv">$0</span><span class="s2"> <db name> <username> <password> <backup folder>"</span>
<span class="k">return </span>1
<span class="k">fi
</span><span class="nb">echo</span> <span class="s2">"Create backup folder </span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"..."</span>
/bin/mkdir <span class="s2">"</span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Backup </span><span class="nv">$DBNAME</span><span class="s2"> schema to </span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">/</span><span class="nv">$DBNAME</span><span class="s2">-schema-</span><span class="nv">$CURR_DATE</span><span class="s2">.sql"</span>
<span class="nb">echo</span> <span class="s2">"..."</span>
/usr/bin/mysqldump <span class="nv">$DBNAME</span> <span class="nt">-u</span> <span class="nv">$USERNAME</span> <span class="nt">-p</span><span class="nv">$PASSWORD</span> <span class="nt">-h</span><span class="nv">$DBHOST</span> <span class="nt">-d</span> <span class="nt">--compact</span> <span class="o">></span> <span class="s2">"</span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">/</span><span class="nv">$DBNAME</span><span class="s2">-schema-</span><span class="nv">$CURR_DATE</span><span class="s2">.sql"</span>
<span class="nb">echo</span> <span class="s2">"Backup </span><span class="nv">$DBNAME</span><span class="s2"> data to </span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">/</span><span class="nv">$DBNAME</span><span class="s2">-data-</span><span class="nv">$CURR_DATE</span><span class="s2">.sql"</span>
<span class="nb">echo</span> <span class="s2">"..."</span>
/usr/bin/mysqldump <span class="nv">$DBNAME</span> <span class="nt">-u</span> <span class="nv">$USERNAME</span> <span class="nt">-p</span><span class="nv">$PASSWORD</span> <span class="nt">-h</span> <span class="nv">$DBHOST</span> <span class="nt">-n</span> <span class="nt">-c</span> <span class="nt">-t</span> <span class="nt">--compact</span> <span class="nt">--single-transaction</span> <span class="o">></span> <span class="s2">"</span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">/</span><span class="nv">$DBNAME</span><span class="s2">-data-</span><span class="nv">$CURR_DATE</span><span class="s2">.sql"</span>
<span class="nb">echo</span> <span class="s2">"Compressing folder </span><span class="nv">$CURR_DATE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"..."</span>
/bin/tar cvf - <span class="s2">"</span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">"</span> | /bin/bzip2 <span class="nt">-c9</span> <span class="o">></span> <span class="s2">"</span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">.tar.bz2"</span>
<span class="nb">echo</span> <span class="s2">"Removing folder </span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"..."</span>
/bin/rm <span class="nt">-rf</span> <span class="s2">"</span><span class="nv">$BACKUP_FOLDER</span><span class="s2">/</span><span class="nv">$CURR_DATE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Completed"</span>
</code></pre></div></div>
<p>Script di atas dapat dijalankan setiap Jumat malam jam 23.00 dengan konfigurasi sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 23 * * 5 /bin/sh /root/backup-db/mysql-backup.sh db_host db_name db_user db_pass /root/backup-db
</code></pre></div></div>
<p>Bila kita ingin membuat backup untuk semua database dalam server, kita bisa buatkan script yang mengambil nama-nama database di server, kemudian looping untuk melakukan backup kepada masing-masing database tersebut. Berikut scriptnya</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nv">MYSQL_USER</span><span class="o">=</span><span class="s2">"root"</span>
<span class="nv">MYSQL</span><span class="o">=</span>/usr/bin/mysql
<span class="nv">MYSQL_PASSWORD</span><span class="o">=</span><span class="s2">"passwordnya root mysql"</span>
<span class="nv">MYSQLDUMP</span><span class="o">=</span>/usr/bin/mysqldump
<span class="nv">BACKUP_DIR</span><span class="o">=</span><span class="s2">"/root/backup-db"</span>
<span class="nv">databases</span><span class="o">=</span><span class="sb">`</span><span class="nv">$MYSQL</span> <span class="nt">--user</span><span class="o">=</span><span class="nv">$MYSQL_USER</span> <span class="nt">-p</span><span class="nv">$MYSQL_PASSWORD</span> <span class="nt">-e</span> <span class="s2">"SHOW DATABASES;"</span> | <span class="nb">grep</span> <span class="nt">-Ev</span> <span class="s2">"(Database|information_schema|performance_schema)"</span><span class="sb">`</span>
<span class="k">for </span>db <span class="k">in</span> <span class="nv">$databases</span><span class="p">;</span> <span class="k">do</span>
/bin/mkdir <span class="nt">-p</span> <span class="nv">$BACKUP_DIR</span>/<span class="nv">$db</span>
/bin/sh /root/backup-db/backup.sh localhost <span class="nv">$db</span> <span class="nv">$MYSQL_USER</span> <span class="nv">$MYSQL_PASSWORD</span> <span class="nv">$BACKUP_DIR</span>/<span class="nv">$db</span>
<span class="k">done</span>
</code></pre></div></div>
<p>Simpan file tersebut dengan nama <code class="language-plaintext highlighter-rouge">mysql-backup-semua.sh</code> dan letakkan di folder <code class="language-plaintext highlighter-rouge">/root/backup-db</code>.</p>
<p>Cara memanggilnya di cron sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 0 * * * /bin/sh /root/backup-db/mysql-backup-semua.sh > /root/backup-db/`date +\%Y\%m\%d\%H\%M\%S`-cron.log 2>&1
</code></pre></div></div>
<p>Semoga bermanfaat</p>
SSH dengan JSch2009-05-03T00:38:00+07:00https://software.endy.muhardin.com/java/ssh-dengan-jsch<p>Mengakses Remote Server dengan Java</p>
<p>Kita sudah biasa mengakses komputer lain menggunakan SSH.
Cukup ketikkan ssh username@namaserver, tekan enter, dan lakukan apa yang kita mau.
Nah, bagaimana kalau kita ingin membuat aplikasi yang butuh mengakses komputer lain?
Misalnya, kita ingin membuat aplikasi sederhana yang menampilkan input IP Address, Username, Password serta tombol Shutdown.
Begitu input field kita isi dan tombol Shutdown ditekan, aplikasi kita akan melakukan ssh ke komputer tersebut dan menjalankan perintah shutdown -h now.</p>
<p>Di Java, hal ini dapat dengan mudah dilakukan menggunakan pustaka <a href="http://www.jcraft.com/jsch/">JSch</a>. Berikut langkah-langkah untuk menggunakan JSch.</p>
<h2 id="mendapatkan-pustaka-jsch">Mendapatkan Pustaka JSch</h2>
<p>Tentunya yang pertama kita lakukan adalah membuka <a href="http://www.jcraft.com/jsch/">website JSch</a> dan mengunduh rilis terbaru. Pilih yang ZIP, jangan yang JAR, karena di dalamnya ada beberapa contoh penggunaan yang akan kita butuhkan untuk mencontek nantinya.</p>
<p>Paket ZIP ini belum dikompilasi, dan kita membutuhkan <a href="http://ant.apache.org">Ant</a> untuk melakukan proses build. Jika Anda belum pernah menggunakan Ant, baca dulu <a href="http://endy.artivisi.com/downloads/writings/Tutorial-Ant.pdf">tutorial ini</a>. Setelah proses build selesai dilakukan, akan muncul file *jar di dalam folder dist.</p>
<h2 id="template-aplikasi-java">Template Aplikasi Java</h2>
<p>Buatlah project Java menggunakan editor yang Anda sukai. Pastikan jar JSch sudah terdaftar di project Java yang barusan dibuat. Setelah itu, buatlah Java Class sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class JschDemo {
public static void main(String[] args) throws Exception {
}
}
</code></pre></div></div>
<p>Untuk selanjutnya, kode program akan ditulis di dalam method main.</p>
<h2 id="parameter-koneksi">Parameter koneksi</h2>
<p>Untuk melakukan koneksi ke komputer lain, kita memerlukan beberapa variabel, ditunjukkan dengan kode sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String host = "localhost";
String user = "endy";
String pass = "java";
String command = "shutdown -h now";
</code></pre></div></div>
<p>Kita akan melakukan koneksi ke komputer kita sendiri (localhost) dengan username endy dan password java. Setelah berhasil terhubung, kita akan menjalankan perintah shutdown -h now.</p>
<h2 id="pre-requisite">Pre Requisite</h2>
<p>Agar kode program kita bisa dijalankan, ada beberapa persyaratan sebagai berikut:</p>
<ul>
<li>
<p>Komputer yang ingin dihubungi harus menjalankan SSH server</p>
</li>
<li>
<p>Komputer yang ingin menghubungi harus melakukan koneksi SSH secara manual terlebih dulu,
agar komputer tujuan terdaftar di file known_hosts</p>
</li>
<li>
<p>User yang digunakan untuk koneksi harus memiliki akses yang cukup untuk melakukan perintah yang diinginkan</p>
</li>
</ul>
<h2 id="menggunakan-jsch">Menggunakan JSch</h2>
<p>Setelah parameter koneksi kita deklarasikan, dan juga prasyarat di komputer tujuan dipenuhi, sekarang kita bisa mulai menggunakan JSch.
Pertama, kita instankan dulu object dari class JSch.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JSch jsch = new JSch();
</code></pre></div></div>
<p>Kemudian, kita berikan database known_hosts, yaitu daftar komputer tujuan beserta public keynya. Ini berguna untuk melakukan verifikasi terhadap komputer tujuan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jsch.setKnownHosts("/home/endy/.ssh/known_hosts");
</code></pre></div></div>
<p>File known_hosts ini akan otomatis dibuat bila kita melakukan koneksi SSH ke komputer lain. Itulah sebabnya kita harus melakukan koneksi manual terlebih dulu, sehingga public key komputer tujuan sudah terdaftar dalam file ini. Alternatif dari menggunakan file ini adalah <a href="http://endy.artivisi.com/blog/linux/login-ssh-dengan-private-key/">mendaftarkan public key di komputer tujuan dan menggunakan private key untuk login</a>.</p>
<p>Selanjutnya, kita membuka koneksi ke komputer tujuan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Session session = jsch.getSession(user, host);
session.setPassword(pass);
session.connect();
</code></pre></div></div>
<p>Lalu, kita membuka channel. Channel menggambarkan bentuk interaksi kita dengan komputer tujuan. JSch menyediakan beberapa jenis channel, misalnya exec dan shell. Kita menggunakan exec untuk mengeksekusi satu perintah saja. Sedangkan shell bisa digunakan untuk membuka terminal interaktif yang bisa kita berikan perintah sebanyak kita mau.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
channel.connect();
</code></pre></div></div>
<p>Kita bisa membaca output dari perintah yang kita jalankan menggunakan I/O Stream seperti biasa.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
String output = reader.readLine();
while (output != null) {
System.out.println(output);
output = reader.readLine();
}
reader.close();
</code></pre></div></div>
<p>Segera setelah perintah selesai dijalankan, kita mengakhiri channel dan session.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>channel.disconnect();
session.disconnect();
</code></pre></div></div>
<p>Berikut adalah keseluruhan kode program.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class JschDemo {
public static void main(String[] args) throws Exception {
String host = "localhost";
String user = "endy";
String pass = "java";
String command = "shutdown -h now";
JSch jsch = new JSch();
jsch.setKnownHosts("/home/endy/.ssh/known_hosts");
Session session = jsch.getSession(user, host);
session.setPassword(pass);
session.connect();
ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
channel.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
String output = reader.readLine();
while (output != null) {
System.out.println(output);
output = reader.readLine();
}
reader.close();
channel.disconnect();
session.disconnect();
}
}
</code></pre></div></div>
<p>Selamat mencoba. Bila percobaan Anda sukses, komputer yang sedang Anda gunakan akan shutdown setelah aplikasi selesai dijalankan.
:D</p>
Upgrade Ubuntu2009-04-22T23:10:37+07:00https://software.endy.muhardin.com/linux/upgrade-ubuntu<p>Sebentar lagi Ubuntu terbaru akan rilis. Seperti biasanya, saya akan menjalankan ritual 6 bulanan yang terdiri dari kegiatan sbb:</p>
<ul>
<li>
<p>Unduh ISO Ubuntu terbaru</p>
</li>
<li>
<p>Bakar ke CD</p>
</li>
<li>
<p>Backup isi home folder</p>
</li>
<li>
<p>Backup mailbox Thunderbird</p>
</li>
<li>
<p>Remove semua hidden file di home folder</p>
</li>
<li>
<p>Reinstall Ubuntu dengan memformat partisi sistem</p>
</li>
</ul>
<p>Saya tidak terlalu suka upgrade, karena banyak menyisakan sampah dan jebakan. Lebih nyaman dan cepat memformat partisi sistem daripada troubleshoot hasil upgrade.</p>
<p>Karena ritual ini akan terus dilakukan di masa depan, ada baiknya kita optimasi workflownya agar lebih efisien.</p>
<p>Berikut adalah requirement saya untuk workflow upgrade ini:</p>
<ul>
<li>
<p>Saya memiliki beberapa aplikasi yang mandatory tapi tidak termasuk dalam paket default Ubuntu, seperti Java SDK, Thunderbird, Inkscape, dan beberapa aplikasi lain. Saya ingin menyimpan daftar aplikasi ini, dan me-reuse-nya setiap kali upgrade.</p>
</li>
<li>
<p>Saya ingin memisahkan proses download *.deb yang memakan waktu lama dengan proses instalasi aplikasi. Tujuannya, agar proses download *.deb ini bisa ‘dititipkan’ di komputer lain yang online 24/7. Setelah selesai, hasilnya tinggal dicopy ke komputer saya, dan langsung diinstal</p>
</li>
</ul>
<p>Setelah browsing <a href="">kesana</a> <a href="http://beans.seartipy.com/2006/05/06/update-or-install-applications-on-debianubuntu-without-an-internet-connection/">kemari</a>, akhirnya saya mendapatkan workflow yang sesuai, sebagai berikut.</p>
<ul>
<li>
<p>Buat dulu daftar paket yang ingin diinstal, dari instalasi Ubuntu yang sekarang</p>
</li>
<li>
<p>Lakukan proses instalasi</p>
</li>
<li>
<p>Buat daftar URL untuk mengunduh *.deb menggunakan Ubuntu yang baru diinstal</p>
</li>
<li>
<p>Jalankan proses unduh di komputer lain yang online 24/7</p>
</li>
<li>
<p>Setelah selesai, pindahkan hasilnya ke komputer ber-Ubuntu baru, dan instal</p>
</li>
</ul>
<h2 id="membuat-daftar-aplikasi">Membuat daftar aplikasi</h2>
<p>Daftar aplikasi yang terinstal di komputer yang kita pakai sekarang dapat didapatkan dengan menjalankan perintah berikut di command prompt.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dpkg --get-selections | sed -n 's/^\(.*\)\tinstall$/\1$/p' | cut -f1 > daftar_aplikasi.txt
</code></pre></div></div>
<p>Buka file <code class="language-plaintext highlighter-rouge">daftar_aplikasi.txt</code> dengan Text Editor dan buang aplikasi yang terinstal secara default. Setelah diedit, file saya berisi sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ttf-inconsolata
ttf-droid
nautilus-open-terminal
mc
chmsee
unrar
comix
keepassx
subversion
git-svn
gimp
network-manager-openvpn
network-manager-openvpn-gnome
network-manager-pptp
network-manager-pptp-gnome
network-manager-vpnc
network-manager-vpnc-gnome
openssh-server
gnome-alsamixer
vlc
rtorrent
hamster-applet
timer-applet
inkscape
mysql-server
mysql-query-browser
mysql-admin
sun-java6-jdk
ubuntu-restricted-extras
build-essential
ant
</code></pre></div></div>
<h2 id="proses-instalasi">Proses Instalasi</h2>
<p>Tidak ada yang istimewa dari proses instalasi. Berikut checklist yang saya gunakan:</p>
<ul>
<li>
<p>Backup isi home folder</p>
</li>
<li>
<p>Backup mailbox Thunderbird, biasanya ada di folder <code class="language-plaintext highlighter-rouge">.mozilla-thunderbird</code></p>
</li>
<li>
<p>Remove semua hidden file dan folder di home. File hidden ini berisi konfigurasi aplikasi yang terinstal. Kita tidak ingin sistem baru kita menggunakan konfigurasi lama. Nanti jadi tidak terlihat barunya.</p>
</li>
<li>
<p>Reinstall Ubuntu dengan memformat partisi sistem</p>
</li>
<li>
<p>Edit <code class="language-plaintext highlighter-rouge">/etc/apt/sources.list</code> agar menggunakan mirror lokal. Saya biasa menggunakan <code class="language-plaintext highlighter-rouge">kambing.ui.ac.id</code></p>
</li>
<li>
<p>Jalankan <code class="language-plaintext highlighter-rouge">apt-get update</code> untuk mengupdate database aplikasi</p>
</li>
</ul>
<p>Berikut isi file <code class="language-plaintext highlighter-rouge">/etc/apt/sources.list</code> saya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deb http://kambing.ui.ac.id/ubuntu/ intrepid main restricted universe multiverse
deb http://kambing.ui.ac.id/ubuntu/ intrepid-updates main restricted universe multiverse
deb http://kambing.ui.ac.id/ubuntu/ intrepid-security main restricted universe multiverse
</code></pre></div></div>
<p>Setelah install ulang, file ini akan menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deb http://kambing.ui.ac.id/ubuntu/ jaunty main restricted universe multiverse
deb http://kambing.ui.ac.id/ubuntu/ jaunty-updates main restricted universe multiverse
deb http://kambing.ui.ac.id/ubuntu/ jaunty-security main restricted universe multiverse
</code></pre></div></div>
<p>Segera setelah database aplikasi kita up-to-date, kita bisa lanjut ke langkah berikutnya.</p>
<h2 id="membuat-daftar-url-untuk-download">Membuat daftar URL untuk download</h2>
<p>Jalankan perintah berikut di command prompt.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat daftar_aplikasi.txt | tr '\n' ' '| xargs apt-get install -qq --print-uris | cut -d\' -f2 > daftar_url.txt
</code></pre></div></div>
<p>Perintah ini akan menghasilkan file <code class="language-plaintext highlighter-rouge">daftar_url.txt</code>, yang berisi daftar URL file *.deb yang dibutuhkan untuk instalasi.</p>
<h2 id="mengunduh-deb">Mengunduh *.deb</h2>
<p>Berbekal <code class="language-plaintext highlighter-rouge">daftar_url.txt</code>, kita bisa segera menjalankan proses pengunduhan di komputer lain yang online 24/7. Komputer ini tidak perlu menjalankan Ubuntu terbaru. Bahkan tidak harus menjalankan Linux.</p>
<p>Jika komputer tersebut juga menjalankan Linux, maka kita bisa menggunakan wget dengan perintah sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -b -c -nc -o donlod-ubuntu.log -i daftar_url.txt
</code></pre></div></div>
<p>Proses pengunduhan dapat dipantau dengan melihat isi file <code class="language-plaintext highlighter-rouge">donlod-ubuntu.log</code>. Gunakan perintah tail seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tail -f donlod-ubuntu.log
</code></pre></div></div>
<p>Tekan Ctrl+C untuk keluar dari tail.</p>
<p>Proses ini akan menghasilkan banyak file *.deb di folder tempat kita menjalankan perintah wget tadi. Setelah semua file didapatkan, copy ke komputer kita yang baru diinstal tersebut.</p>
<h2 id="instalasi-deb">Instalasi *.deb</h2>
<p>Copy semua file *.deb ke folder <code class="language-plaintext highlighter-rouge">/var/cache/apt/archives</code> di komputer berUbuntu baru. Selanjutnya kita lakukan proses instalasi dengan mengetik perintah berikut di command prompt.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat daftar_aplikasi.txt | tr '\n' ' '| xargs apt-get install -y
</code></pre></div></div>
<p>Tunggu sejenak sampai selesai.</p>
<h2 id="replikasi-instalasi">Replikasi Instalasi</h2>
<p>Bagaimana jika kita disuruh melakukan instalasi identik di banyak komputer sekaligus? Gampang, berikut caranya.</p>
<p>Instal salah satu komputer sampai selesai. Lengkapi semua aplikasi yang dibutuhkan. Segera setelah selesai, extract daftar aplikasi yang terinstal menggunakan perintah berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dpkg --get-selections > daftar_instalasi.txt
</code></pre></div></div>
<p>Copy file <code class="language-plaintext highlighter-rouge">daftar_instalasi.txt</code> ini berikut isi folder /var/cache/apt/archives/ ke komputer berikut yang akan diinstal.</p>
<p>Di komputer berikut, copy file *.deb ke folder /var/cache/apt/archives/. Lalu jalankan perintah berikut untuk mengisi daftar instalasi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo dpkg --set-selections < daftar_instalasi.txt
</code></pre></div></div>
<p>Setelah itu, jalankan perintah berikut untuk memulai proses instalasi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get dselect-upgrade
</code></pre></div></div>
<p>Semoga bermanfaat.</p>
Intro jQuery2009-03-17T22:38:56+07:00https://software.endy.muhardin.com/lain/intro-jquery<p>Jaman sekarang, membuat tampilan menarik di aplikasi web sudah tidak sulit lagi.
Banyak pustaka JavaScript yang siap digunakan dengan mudah.
Tidak perlu lagi kita memusingkan keragaman browser dengan keanehannya masing-masing.</p>
<p>Salah satu pustaka JavaScript yang populer adalah <a href="http://www.jquery.com">jQuery</a>.
Berbeda dengan pustaka JavaScript lainnya, jQuery tidak menawarkan komponen UI yang aneh-aneh.
Demikian juga dengan efek visual, hanya tersedia sekenanya saja.
Dengan demikian, jQuery berukuran relatif kecil dan mudah dipelajari.</p>
<p>Akan tetapi, di balik kesederhanaan tersebut tersimpan kecanggihan arsitekturnya.
Dengan arsitektur yang rapi ini, orang mudah membuat plugin untuk menambah kemampuan jQuery.
Hasilnya, ada <a href="http://www.jqueryui.com/">jQuery UI</a> dengan koleksi komponen UI yang kaya.
Ada juga <a href="http://www.trirand.com/blog/">jqGrid</a> dengan komponen tabelnya yang canggih.</p>
<p>Pada tutorial ini, kita akan mempelajari dasar-dasar pemrograman JavaScript menggunakan jQuery.
Setelah menguasai dasar-dasarnya,
kita akan mampu menggunakan berbagai teknik tingkat tinggi seperti AJAX dan efek visual.
Kita juga akan mudah mempelajari dan menggunakan berbagai komponen dan plugin yang tersedia.</p>
<p>Pada intinya, penggunaan jQuery terdiri dari dua langkah sederhana :</p>
<ol>
<li>Mendapatkan elemen HTML yang ingin kita gunakan</li>
<li>Menambahkan behavior pada elemen HTML yang sudah didapatkan.</li>
</ol>
<p>Sebagai contoh kasus, kita akan membuat kalkulator sederhana yang tampilannya seperti ini:
(/images/uploads/2009/03/kalkulator-jquery.png)</p>
<p>Berikut adalah kode HTML untuk membuat kalkulator tersebut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head>
<title>Kalkulator jQuery</title>
</head>
<body>
<h1>Kalkulator jQuery</h1>
<input name="num1"> * <input name="num2">
<input type="button" id="hitung" value="=">
<input name="hasil" disabled="true">
</body>
</html>
</code></pre></div></div>
<p>Dari screenshot di atas kita sudah bisa menebak cara kerja halaman web tersebut.
Bila tombol = ditekan, maka nilai yang ada di input num1 dan num2 akan dibaca dan dikalikan.
Kemudian hasilnya akan ditampilkan di input hasil.</p>
<p>jQuery menganjurkan kita untuk mengikuti prinsip <em>unobtrusive javascript</em>,
yang artinya tidak menambahkan event pada kode HTML.</p>
<p>Bila kita tidak mengikuti prinsip ini, biasanya kita akan langsung menambahkan event di tombol = seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><input type="button" id="hitung" value="=" onclick="alert('Hello World');">
</code></pre></div></div>
<p>Dengan prinsip <em>unobtrusive javascript</em>, kode HTML untuk tampilan dibiarkan apa adanya.
Event onclick akan kita tambahkan di kode JavaScript yang bisa dipasang di blok head secara inline (ditulis langsung),
ataupun dikeluarkan ke file tersendiri.</p>
<p>Pada contoh ini, kita akan menulis inline supaya lebih mudah.</p>
<h2 id="instalasi-jquery">Instalasi jQuery</h2>
<p>Sebelum kita melakukan apa-apa, terlebih dulu halaman kalkulator tersebut harus bisa mengakses jQuery.
Tambahkan baris berikut pada blok head.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script type="text/javascript" src="js/jquery-1.2.6.min.js"></script>
</code></pre></div></div>
<p>Pastikan lokasi file jQuery.js sudah benar.</p>
<p>Setelah itu, biasanya kita akan menambahkan kode program JavaScript yang akan dijalankan <strong>setelah</strong> seluruh halaman selesai diload oleh browser. Tentunya kita tidak ingin menambahkan event pada elemen yang belum selesai diload.</p>
<p>Berikut adalah blok standar untuk meletakkan kode JavaScript kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script type="text/javascript">
$(function(){
});
</script>
</code></pre></div></div>
<h2 id="event-onclick">Event onclick</h2>
<p>Langkah pertama yang kita lakukan adalah menambahkan event ke tombol =.
Agar bisa melakukannya, kita harus mendapatkan tombol tersebut.
Kalau kita perhatikan kode HTML di atas, tombol = memiliki ID yang berisi nilai hitung.
Atribut ID harus unik dalam satu halaman HTML.
Dengan demikian, kita bisa langsung mendapatkan tombol = dengan menggunakan IDnya.
Setelah tombol didapatkan, kita bisa langsung menggunakan function click untuk menambahkan event onclick.
Berikut adalah kode programnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script type="text/javascript">
$(function(){
$("#hitung").click();
});
</script>
</code></pre></div></div>
<p>Dalam JavaScript, kita bisa memasukkan function sebagai parameter dalam function lain.
Bagi mereka yang sudah pernah coding C atau C++, pasti sudah tidak asing dengan teknik ini,
biasa disebut function pointer dalam C atau C++.</p>
<p>Nah, mari kita isikan function sebagai parameter untuk function click, seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script type="text/javascript">
$(function(){
$("#hitung").click(function(){
});
});
</script>
</code></pre></div></div>
<p>Selanjutnya, kita tinggal mengimplementasikan logika kode program kita, yaitu :</p>
<ol>
<li>ambil nilai num1</li>
<li>ambil nilai num2</li>
<li>kalikan num1 dan num2</li>
<li>hasilnya isikan ke input text hasil</li>
</ol>
<h2 id="selector-name">Selector name</h2>
<p>Fitur jQuery untuk menunjuk elemen HTML tertentu disebut selector.
Pada penambahan event onclick di atas, kita sudah menggunakan selector berdasarkan ID.
Sekarang, kita akan menggunakan selector atribut untuk memilih input dengan atribut name berisi num1, num2, dan hasil.
Berikut kode program implementasi dari logika di atas.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script type="text/javascript">
$(function(){
$("#hitung").click(function(){
var num1 = $("input[name=num1]").val();
var num2 = $("input[name=num2]").val();
$("input[name=hasil]").val(num1 * num2);
});
});
</script>
</code></pre></div></div>
<h2 id="kesimpulan">Kesimpulan</h2>
<p>Silahkan buka kalkulator.html di browser, dan coba apakah sudah bekerja dengan benar.</p>
<p>Secara garis besar, pemakaian jQuery dapat dijelaskan dengan satu kalimat,</p>
<blockquote>
<p>pilih elemen menggunakan selector, lalu manipulasi sesuai keinginan</p>
</blockquote>
<p>Setelah memahami artikel ini, kita bisa mengembangkan kemampuan kita dalam menggunakan jQuery, diantaranya:</p>
<ul>
<li>
<p>mempelajari berbagai selector selain id dan atribut. jQuery memiliki banyak sekali selector yang bisa digunakan</p>
</li>
<li>
<p>menggunakan berbagai event lain selain onclick, misalnya onfocus, onchange, dsb</p>
</li>
<li>
<p>operasi elemen selain mengeset value, misalnya menambahkan isi elemen, mengakses server dengan AJAX, dsb</p>
</li>
<li>
<p>menggunakan plugin-plugin jQuery misalnya Date Picker, Tab, jqGrid, dan sebagainya</p>
</li>
<li>
<p>membuat plugin sendiri</p>
</li>
</ul>
Spring Build2009-03-04T20:22:32+07:00https://software.endy.muhardin.com/java/spring-build<p><a href="http://martinus.artivisi.com">Martinus</a> dan <a href="http://ifnu.artivisi.com">Ifnu</a> kalo <a href="http://blog.springsource.com/2009/03/03/building-spring-3/">baca ini</a> pasti merasa familiar dengan struktur folder dan prosesnya.</p>
<p>Saya gak nyontek lho … :p</p>
Null Date di MySQL2009-03-04T17:01:52+07:00https://software.endy.muhardin.com/java/null-date-di-mysql<p>Null Date di MySQL</p>
<p>Bila memiliki tipe data DATE di database MySQL, format standarnya adalah yyyy-MM-dd.
Jadi, bila kita mau mengisi 17 Agustus 1945, kita lakukan seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INSERT INTO hari_besar_nasional (tanggal, keterangan)
VALUES ('1945-08-17', 'Hari Kemerdekaan RI');
</code></pre></div></div>
<p>Bila kita tidak mengisikan tanggal alias NULL, maka MySQL akan mengisi kolom tersebut dengan nilai 0000-00-00. Sayangnya, nilai ini tidak diterima dengan baik oleh driver JDBC MySQL.</p>
<p>Berikut contoh kode program dalam Java</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String sql = "select * from hari_besar_nasional";
ResultSet rs = connection.createStatement().executeQuery(sql);
while(rs.next()){
System.out.println("Tanggal : "+rs.getDate("tanggal");
System.out.println("Keterangan : "+rs.getString("keterangan");
}
rs.close();
</code></pre></div></div>
<p>Jika ada data yang berisi 0000-00-00, maka akan terjadi exception sebagai berikut:</p>
<blockquote>
<p>java.sql.SQLException: Cannot convert value ‘0000-00-00 00:00:00’</p>
</blockquote>
<p>Masalah ini dijelaskan di <a href="http://dev.mysql.com/doc/refman/5.0/en/using-date.html">dokumentasi MySQL ini</a>.</p>
<p>Solusinya adalah mengganti parameter koneksi database, yang tadinya seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jdbc:mysql://localhost/nama_database
</code></pre></div></div>
<p>menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jdbc:mysql://localhost/nama_database?zeroDateTimeBehavior=convertToNull
</code></pre></div></div>
Network Device menghilang di VMWare2009-03-03T15:04:24+07:00https://software.endy.muhardin.com/linux/vmware-eth<p>Saya menggunakan VMWare secara intensif dalam proses development.
Salah satu penggunaannya adalah untuk menginstal aplikasi atau database milik client. Beberapa merek database seperti Oracle relatif invasif terhadap sistem, misalnya mendikte jumlah swap ataupun mengharuskan mengedit kernel parameter. Tentunya ini membuat kotor sistem operasi saya.</p>
<p>Selain itu, kadangkala client menggunakan sistem operasi yang berbeda dengan yang saya gunakan. Ini juga dapat diselesaikan dengan menggunakan VMWare.</p>
<p>Saya menyimpan file-file VM di harddisk eksternal, untuk meningkatkan performance. Menurut <a href="http://www.codinghorror.com">Jeff Atwood</a>, kinerja VM akan meningkat bila dia berada di harddisk yang berbeda dengan hostnya.</p>
<p>Penyimpanan di harddisk eksternal ini ternyata mengundang sedikit masalah. Kadangkala network interface VM saya menghilang. Masalah ini terjadi pada kombinasi host Ubuntu dan guest Ubuntu.</p>
<p>Setelah googling kesana kemari, ternyata ini disebabkan oleh beberapa hal :</p>
<ol>
<li>
<p>Ubuntu me-mount partisi harddisk eksternal secara acak. Kadang partisi 1 dimount ke /media/disk-1, kadang ke /media-disk-2</p>
</li>
<li>
<p>Bila posisi mount berubah, VMWare akan menggenerate UUID (ID unik untuk guest VM) baru</p>
</li>
<li>
<p>Setiap kali UUID berubah, MAC Address network interface guest VM juga berubah.</p>
</li>
<li>
<p>Ubuntu di guest VM meng-cache MAC address, sehingga kalau MAC yang dia cache tidak ada, network interface tersebut tidak akan diload</p>
</li>
</ol>
<p>Solusinya mudah, yaitu menghapus cache MAC address di guest Ubuntu, dan merestartnya. Caranya, login ke guest OS, lalu hapus file /etc/udev/rules.d/70-persistent-net.rules. Setelah itu restart.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo rm /etc/udev/rules.d/70-persistent-net.rules
sudo reboot
</code></pre></div></div>
<p>Setelah restart, guest VM akan kembali memiliki ethernet card.</p>
Enkripsi JDBC Properties2009-01-16T22:24:04+07:00https://software.endy.muhardin.com/java/enkripsi-jdbc-properties<p>Bila kita membuat aplikasi Java yang menggunakan database, pasti kita akan membuat satu file untuk konfigurasi koneksi database, biasanya diberi nama jdbc.properties. Isinya kira-kira sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost/belajar
jdbc.username = belajar
jdbc.password = java
</code></pre></div></div>
<p>Cepat atau lambat, kita akan menemui kebutuhan untuk menyembunyikan nilai yang diisikan ke dalam file tersebut untuk alasan keamanan. Tentunya kita tidak ingin orang yang bisa membaca file tersebut login ke database dan melihat berbagai data rahasia dalam database.</p>
<p>Kita ingin mengenkripsi minimal variabel <code class="language-plaintext highlighter-rouge">jdbc.password</code>, supaya tidak bisa dibaca sembarang orang. Bila kita menggunakan Spring Framework untuk membaca file tersebut, kita bisa menggunakan <a href="http://www.jasypt.org/">Jasypt</a> yang mampu menangani masalah enkripsi file tersebut.</p>
<!--more-->
<p>Bila kita menggunakan Maven, tambahkan dependensi Jasypt sebagai berikut</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.jasypt<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>jasypt-spring31<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.9.0<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>Sesuaikan versinya dengan yang terbaru.</p>
<p>Biasanya, kita membaca file <code class="language-plaintext highlighter-rouge">jdbc.properties</code> di dalam konfigurasi Spring, seperti ini.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><context:property-placeholder</span> <span class="na">location=</span><span class="s">"classpath:jdbc.properties"</span><span class="nt">/></span>
</code></pre></div></div>
<p>Kemudian menggunakannya dalam konfigurasi dataSource seperti ini.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"dataSource"</span> <span class="na">class=</span><span class="s">"org.springframework.jdbc.datasource.DriverManagerDataSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"driverClassName"</span> <span class="na">value=</span><span class="s">"${jdbc.driver}"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"url"</span> <span class="na">value=</span><span class="s">"${jdbc.url}"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">value=</span><span class="s">"${jdbc.username}"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">value=</span><span class="s">"${jdbc.password}"</span><span class="nt">></property></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Dengan menggunakan Jasypt, kita dapat mengenkripsi jdbc.properties sehingga menjadi seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost/belajar
jdbc.username = belajar
jdbc.password = ENC(fSz7P5zYRnhsonKoKvxNmw==)
</code></pre></div></div>
<p>Agar variabel <code class="language-plaintext highlighter-rouge">jdbc.password</code> bisa dibaca dengan baik, kita ganti <code class="language-plaintext highlighter-rouge">PropertyPlaceholderConfigurer</code> dengan miliknya Jasypt, sehingga konfigurasi Spring kita menjadi seperti ini.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><jasypt:encryptor-config</span>
<span class="na">id=</span><span class="s">"encryptorConfig"</span>
<span class="na">password=</span><span class="s">"test123"</span>
<span class="na">algorithm=</span><span class="s">"PBEWithMD5AndTripleDES"</span><span class="nt">/></span>
<span class="nt"><jasypt:string-encryptor</span>
<span class="na">id=</span><span class="s">"stringEncryptor"</span>
<span class="na">config-bean=</span><span class="s">"encryptorConfig"</span><span class="nt">/></span>
<span class="nt"><jasypt:encryptable-property-placeholder</span>
<span class="na">encryptor=</span><span class="s">"stringEncryptor"</span>
<span class="na">location=</span><span class="s">"classpath:jdbc.properties"</span><span class="nt">/></span>
</code></pre></div></div>
<p>Cukup itu saja perubahannya. Konfigurasi <code class="language-plaintext highlighter-rouge">dataSource</code> di atas tidak perlu diubah.</p>
<p>Bagaimana cara kita mengetahui bahwa hasil enkripsi dari <code class="language-plaintext highlighter-rouge">java</code> adalah <code class="language-plaintext highlighter-rouge">fSz7P5zYRnhsonKoKvxNmw==</code> ??
Gampang, buat saja kode Java sederhana seperti ini. Bisa dibuat dalam method <code class="language-plaintext highlighter-rouge">public static void main(String[] xx)</code> ataupun dalam JUnit test.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">StandardPBEStringEncryptor</span> <span class="n">encryptor</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StandardPBEStringEncryptor</span><span class="o">();</span>
<span class="n">encryptor</span><span class="o">.</span><span class="na">setPassword</span><span class="o">(</span><span class="s">"test123"</span><span class="o">);</span>
<span class="n">encryptor</span><span class="o">.</span><span class="na">setAlgorithm</span><span class="o">(</span><span class="s">"PBEWithMD5AndTripleDES"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">encryptedText</span> <span class="o">=</span> <span class="n">encryptor</span><span class="o">.</span><span class="na">encrypt</span><span class="o">(</span><span class="s">"java"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Encrypted Password : ["</span><span class="o">+</span><span class="n">encryptedText</span><span class="o">+</span><span class="s">"]"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">decrypted</span> <span class="o">=</span> <span class="n">encryptor</span><span class="o">.</span><span class="na">decrypt</span><span class="o">(</span><span class="n">encryptedText</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Decrypted Password : ["</span><span class="o">+</span><span class="n">decrypted</span><span class="o">+</span><span class="s">"]"</span><span class="o">);</span>
</code></pre></div></div>
<p>Bila dijalankan, kode program di atas akan menghasilkan output seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Encrypted Password : [fSz7P5zYRnhsonKoKvxNmw==]
Decrypted Password : [java]
</code></pre></div></div>
<p>Nilai itulah yang kita copy-paste ke dalam <code class="language-plaintext highlighter-rouge">jdbc.properties</code>.</p>
<p>Pembaca yang teliti belum puas dengan penjelasan ini, biasanya bertanya</p>
<blockquote>
<p>Lalu kalau passwordnya tetap kita tulis di file konfigurasi apa manfaatnya?
Orang yang membaca file tersebut tinggal menjalankan Jasypt seperti contoh kode di atas, dan dia akan mendapatkan password yang sebenarnya.</p>
</blockquote>
<p>Nah, benar juga.</p>
<p>Untungnya, Jasypt sudah menyediakan fitur lain. Kita bisa menyediakan password melalui environment variable.
Ubah konfigurasi di atas menjadi seperti ini</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><encryption:encryptor-config</span>
<span class="na">id=</span><span class="s">"encryptorConfig"</span>
<span class="na">password-env-name=</span><span class="s">"PASSWORD_APLIKASI"</span>
<span class="na">algorithm=</span><span class="s">"PBEWithMD5AndTripleDES"</span><span class="nt">/></span>
<span class="nt"><jasypt:string-encryptor</span>
<span class="na">id=</span><span class="s">"stringEncryptor"</span>
<span class="na">config-bean=</span><span class="s">"encryptorConfig"</span><span class="nt">/></span>
<span class="nt"><jasypt:encryptable-property-placeholder</span>
<span class="na">encryptor=</span><span class="s">"stringEncryptor"</span>
<span class="na">location=</span><span class="s">"classpath:jdbc.properties"</span><span class="nt">/></span>
</code></pre></div></div>
<p>Dengan konfigurasi di atas, password encryptor (<code class="language-plaintext highlighter-rouge">test123</code> pada contoh sebelumnya) tidak lagi ditulis di file konfigurasi.
Jasypt akan mencari environment variable bernama <code class="language-plaintext highlighter-rouge">PASSWORD_APLIKASI</code>. Jadi, langkah-langkah menjalankan aplikasinya adalah sebagai berikut:</p>
<ol>
<li>Set environment variabel. Di Linux perintahnya <code class="language-plaintext highlighter-rouge">export PASSWORD_APLIKASI=test123</code>. Di Windows <code class="language-plaintext highlighter-rouge">set PASSWORD_APLIKASI=test123</code>.</li>
<li>Jalankan aplikasi di command prompt yang sama</li>
<li>Supaya aman, hapus lagi environment variable. Di Linux perintahnya <code class="language-plaintext highlighter-rouge">unset PASSWORD_APLIKASI</code>. Di Windows, cukup diset dengan nilai kosong saja <code class="language-plaintext highlighter-rouge">set PASSWORD_APLIKASI=""</code>.</li>
</ol>
SVN Externals2008-11-10T21:40:10+07:00https://software.endy.muhardin.com/lain/svn-externals<p>Dalam membuat aplikasi, seringkali kita membutuhkan source-code dari aplikasi lainnya. Misalnya, jika kita sudah membuat cukup banyak aplikasi, maka fitur login dan logout pasti sudah sering kita buat.</p>
<p>Daripada menulis ulang fitur tersebut, alangkah lebih baiknya jika kita reuse kode programnya dalam aplikasi yang akan dibuat. Dengan demikian, setelah melewati beberapa project, kode program yang direuse tersebut akan bertambah kemampuannya dan semakin canggih.</p>
<p>Kita dapat melakukan hal ini dengan menggunakan fitur svn external. Misalnya struktur kode program kita terdiri dari modul berikut:</p>
<ul>
<li>
<p>Master Data</p>
</li>
<li>
<p>Transaksi</p>
</li>
<li>
<p>Report</p>
</li>
<li>
<p>Security</p>
</li>
</ul>
<p>Kita ingin me-reuse kode program security yang ada di aplikasi lain. Dengan demikian, kita perlu menambahkan folder tersebut ke dalam source-tree kita.</p>
<p>Untuk melakukan hal tersebut, kita mengedit property Subversion yang bernama svn:externals. Berikut cara menambah property tersebut :</p>
<ol>
<li>
<p>Checkout dulu seluruh trunk.</p>
<p>svn co http://repo.server.com/svn/nama-project/trunk project-saya</p>
</li>
<li>
<p>Tambahkan property svn:externals ke project yang sudah ada.</p>
<p>svn propset svn:externals “modul-security http://repo.server.com/svn/project-lain/trunk/modul-security” project-saya</p>
</li>
<li>
<p>Commit deh</p>
<p>cd project-saya
svn ci -m “tambahkan property svn:external”</p>
</li>
<li>
<p>Untuk mengambil source code modul-security, lakukan svn update</p>
<p>svn update</p>
</li>
</ol>
<p>Kode program modul-security siap digunakan. Ingat, kalau kita melakukan perubahan di dalamnya dan melakukan commit, maka perubahan akan dikirim ke repository asalnya, yaitu http://repo.server.com/svn/project-lain/trunk/modul-security</p>
<p>Lalu bagaimana kalau kita tidak ingin mengikuti perkembangan modul-security? Bisa saja ada programmer lain yang mengubah modul-security dan menyebabkan kode kita rusak.</p>
<p>Caranya, referensikan modul-security ke tags, jangan ke trunk.</p>
<p>Demikian cara berbagi kode program lintas project. Semoga bermanfaat.</p>
Membuat Mirror Repository Ivy2008-10-05T14:30:53+07:00https://software.endy.muhardin.com/java/ant-ivy-8<p>Pada rangkaian artikel sebelumnya, kita telah membahas tentang:</p>
<ol>
<li>
<p><a href="http://endy.artivisi.com/blog/java/ant-ivy-1">Instalasi Ivy</a></p>
</li>
<li>
<p><a href="http://endy.artivisi.com/blog/java/ant-ivy-2">Build Management dengan Ivy</a></p>
</li>
<li>
<p><a href="http://endy.artivisi.com/blog/java/ant-ivy-3">Otomasi Build Process dengan Ant</a></p>
</li>
<li>
<p><a href="http://endy.artivisi.com/blog/java/ant-ivy-4">Publish Modul ke Repository</a></p>
</li>
<li>
<p><a href="http://endy.artivisi.com/blog/java/ant-ivy-5">Deklarasi internal dependency</a></p>
</li>
<li>
<p><a href="http://endy.artivisi.com/blog/java/ant-ivy-6">Deklarasi external dependency</a></p>
</li>
<li>
<p><a href="http://endy.artivisi.com/blog/java/ant-ivy-7">Ivy Configuration</a></p>
</li>
</ol>
<p>Semua konsep dan pengetahuan di atas memungkinkan kita untuk menggunakan Ivy secara efektif untuk mengelola pembuatan aplikasi. Tetapi ada sedikit hal yang masih mengganjal, yaitu borosnya bandwidth yang digunakan selama build process dilakukan.</p>
<p>Pada artikel kali ini, kita akan membahas cara membuat mirror repository, sehingga pengambilan artifak dapat dilakukan dari server internal maupun harddisk lokal, sehingga tidak memboroskan bandwidth.</p>
<p>Di artikel sebelumnya, kita telah membahas tentang deklarasi resolver. Resolver adalah konfigurasi yang menyatakan lokasi repository. Ada beberapa jenis resolver yang disediakan Ivy:</p>
<ul>
<li>
<p>Filesystem : repository Ivy di harddisk lokal</p>
</li>
<li>
<p>URL : repository Ivy yang diakses melalui protokol http</p>
</li>
<li>
<p>SSH : repository Ivy yang diakses melalui protokol scp/ssh</p>
</li>
<li>
<p>SFTP : repository Ivy, diakses melalui protokol sftp</p>
</li>
<li>
<p>VFS : repository Ivy, kompatibel dengan file system yang didukung oleh <a href="http://jakarta.apache.org/commons/vfs/">Apache Commons VFS</a>, diantaranya WebDAV, FTP, ZIP, <a href="http://jakarta.apache.org/commons/vfs/filesystems.html">dan sebagainya</a></p>
</li>
<li>
<p>Ibiblio : repository Maven 2, diakses melalui protokol http</p>
</li>
</ul>
<p>Sebagai contoh kasus, kita akan membuat mirror dari repository ivy milik SpringSource dan repository Maven2 di Ibiblio.</p>
<p>Pertama, kita deklarasikan dulu resolver untuk kedua repository sumber.
Konfigurasi ini dibuat dalam ivysettings.xml.</p>
<p>SpringSource memiliki dua repo berbeda, untuk artifak yang dihasilkannya sendiri (Spring Portfolio) disebut dengan release, dan untuk pustaka external (Hibernate, JSF, dsb; disebut dengan external. Berikut konfigurasi untuk kedua repo SpringSource, sesuai dengan <a href="http://www.springsource.com/repository/app/faq#q7">dokumentasinya</a>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><url name="springsource.release.repo.resolver">
<ivy pattern="http://repository.springsource.com/ivy/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="http://repository.springsource.com/ivy/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
<url name="springsource.external.repo.resolver">
<ivy pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
</code></pre></div></div>
<p>Dan ini adalah resolver untuk Ibiblio.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ibiblio name="maven2" m2compatible="true" />
</code></pre></div></div>
<p>Repository SpringSource akan kita mirror di <code class="language-plaintext highlighter-rouge">http://repo.artivisi.com/ivy/springsource/</code> dengan struktur folder <code class="language-plaintext highlighter-rouge">nama-organisasi/nama-modul/nomer-revisi/[artifak]-[revisi].[extension]</code>. Folder tersebut berada di mesin <code class="language-plaintext highlighter-rouge">repo.artivisi.com</code> dalam folder <code class="language-plaintext highlighter-rouge">/var/www/repo.artivisi.com/ivy/springsource</code>. Berikut konfigurasi repo mirror-springsource.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ssh name="springsource.mirror.resolver" keyFile="${user.home}/.ssh/id_rsa" host="repo.artivisi.com" user="${mirror.host.user}">
<ivy pattern="/var/www/repo.artivisi.com/ivy/springsource/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="/var/www/repo.artivisi.com/ivy/springsource/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</ssh>
</code></pre></div></div>
<p>Repository Ibiblio akan kita mirror di <code class="language-plaintext highlighter-rouge">http://repo.artivisi.com/ibiblio</code> dengan struktur folder berbeda, untuk metadata sebagai berikut: <code class="language-plaintext highlighter-rouge">[nama-organisasi]/[nama-modul]/ivys/ivy-[nomer-revisi].xml</code>, dan untuk artifak sebagai berikut: <code class="language-plaintext highlighter-rouge">[nama-organisasi]/[nama-modul]/[jenis]s/[artifak]-[nomer-revisi].[extension]</code>. Folder tersebut berada di mesin <code class="language-plaintext highlighter-rouge">repo.artivisi.com</code> dalam folder <code class="language-plaintext highlighter-rouge">/var/www/repo.artivisi.com/ibiblio</code>. Berikut konfigurasinya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ssh name="ibiblio.mirror.resolver" keyFile="${user.home}/.ssh/id_rsa" host="repo.artivisi.com" user="${mirror.host.user}">
<ivy pattern="/var/www/repo.artivisi.com/ibiblio/[organisation]/[module]/ivys/ivy-[revision].xml"/>
<artifact pattern="/var/www/repo.artivisi.com/ibiblio/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]"/>
</ssh>
</code></pre></div></div>
<p>Setelah kita memiliki dua repository sumber dan dua repository tujuan, kita buat target untuk melakukan mirroring. Di Ivy, kegiatan mirroring ini disebut dengan istilah install. Kita memiliki tiga target install, untuk ibiblio, SpringSource release, dan SpringSource external. Berikut deklarasi targetnya, kita masukkan di <code class="language-plaintext highlighter-rouge">ivybuilder.xml</code> bersama dengan target untuk resolve dan publish.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><target name="install-springsource-release" description="--> install dependency from springsource repo">
<ivy:install
organisation="${organisation}"
module="${module}"
revision="${revision}"
from="${springsource.release.repo.resolver}"
to="${springsource.mirror.resolver}"
transitive="true"
overwrite="true"
/>
</target>
<target name="install-springsource-external" description="--> install dependency from springsource repo">
<ivy:install
organisation="${organisation}"
module="${module}"
revision="${revision}"
from="${springsource.external.repo.resolver}"
to="${springsource.mirror.resolver}"
transitive="true"
overwrite="true"
/>
</target>
<target name="install-ibiblio" description="--> install dependency from ibiblio maven2 repo">
<ivy:install
organisation="${organisation}"
module="${module}"
revision="${revision}"
from="${ibiblio.repo.resolver}"
to="${ibiblio.mirror.resolver}"
transitive="true"
overwrite="true"
/>
</target>
</code></pre></div></div>
<p>Seperti kita lihat di atas, deklarasi target install cukup generik, dengan menggunakan variabel yang bisa di-override pada saat runtime. Untuk target <code class="language-plaintext highlighter-rouge">install-springsource-external</code>, kita gunakan repo external SpringSource sebagai sumber, dan mirror-springsource sebagai tujuan. Target <code class="language-plaintext highlighter-rouge">install-springsource-release</code> dan <code class="language-plaintext highlighter-rouge">install-ibiblio</code> juga mirip.</p>
<p>Sekarang saatnya kita coba. Mari kita install Hibernate Annotations versi 3.3.1.GA. Modul ini ada di repository SpringSource external.</p>
<p>Langkah pertama, cari dulu modul yang kita inginkan. Kita bisa gunakan <a href="http://www.springsource.com/repository">halaman search yang telah disediakan</a>. Dari <a href="http://www.springsource.com/repository/app/bundle/version/detail?name=com.springsource.org.hibernate.annotations&version=3.3.1.ga">hasil pencarian</a>, kita menemukan modul yang diinginkan, berikut dengan deklarasi dependensinya. Sekarang kita telah mengetahui nama organisasi, nama modul, dan nomer revisinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant install-springsource-external -Dorganisation="org.hibernate" -Dmodule="com.springsource.org.hibernate.annotations" -Drevision="3.3.1.ga"
</code></pre></div></div>
<p>Ivy akan melakukan resolve dan melihat semua dependensi dari Hibernate Annotations versi 3.3.1.GA. Setelah itu, Ivy akan mendonlodnya ke cache lokal, untuk kemudian diupload melalui scp ke lokasi mirror.</p>
<p>Terakhir, tentunya mirror repository ini akan diakses orang menggunakan http, bukan ssh. Jadi kita harus buatkan resolver untuk mengakses <code class="language-plaintext highlighter-rouge">http://repo.artivisi.com/ivy/springsource</code> dan <code class="language-plaintext highlighter-rouge">http://repo.artivisi.com/ibiblio</code>. Berikut konfigurasinya, kita satukan menggunakan chain-resolver.</p>
<p>Demikianlah rangkaian tutorial tentang penggunaan Ivy. Mungkin banyak pembaca yang membatin,</p>
<blockquote>
<p>Buat apa repot-repot, pakai Netbeans atau Eclipse kan juga bisa bikin jar/war.</p>
</blockquote>
<p>Biar saya kasih bocoran sedikit tentang dapur ArtiVisi. Kami akan mengadopsi SOA (Service Oriented Architecture) dalam semua produk dan project kami. Adopsi ini dilakukan di level mikro dengan menggunakan platform OSGi. Karakteristik utama aplikasi SOA dengan OSGi adalah aplikasi akan terdiri dari banyak modul-modul kecil yang saling berinteraksi. Contohnya bisa dilihat di diagram modul yang disajikan di awal artikel.</p>
<p>Memecah aplikasi besar menjadi modul-modul kecil tidak terlalu sulit. Siapapun yang mengenal keyword <code class="language-plaintext highlighter-rouge">import</code> dan konsep CLASSPATH di Java bisa melakukannya. Yang sulit adalah mengelola kegiatan development dan integrasi antar modul. Oleh karena itu, penggunaan dependency management seperti Ivy atau Maven2 adalah hal yang wajib.</p>
<p>Pembaca bisa memilih apakah akan menggunakan Maven2 ataupun Ivy. Konsep dasarnya mirip, tapi implementasinya berbeda. Jadi, kalau sudah menguasai Ivy, Maven2 tidak sulit. Demikian juga sebaliknya. Silahkan mencari yang sesuai dengan kebutuhan tim Anda.</p>
Ivy Configuration2008-10-03T17:55:34+07:00https://software.endy.muhardin.com/java/ant-ivy-7<p>Rangkaian <a href="http://endy.artivisi.com/blog/java/ant-ivy-6/">artikel sebelumnya</a> telah memberikan kita pemahaman yang memadai untuk menggunakan Ivy. Pada artikel ini, kita akan membahas satu fitur Ivy yang walaupun tidak wajib dikuasai, tapi sangat penting, karena dapat membuat konfigurasi modul kita menjadi fleksibel. Fitur ini dalam dunia Ivy dikenal dengan istilah <code class="language-plaintext highlighter-rouge">configuration</code>.</p>
<p>Sebagai paket standar kualitas di ArtiVisi, kami menggunakan Cobertura untuk melakukan coverage test. Sayangnya Cobertura dan Hibernate tidak kompatibel. Hibernate menggunakan pustaka <code class="language-plaintext highlighter-rouge">asm</code> dengan versi 1.5.3. Sedangkan Cobertura juga menggunakan <code class="language-plaintext highlighter-rouge">asm</code>, dengan versi 2.2.1. Bila kedua versi kita campur, maka akan terjadi error karena Java VM kebingungan menentukan versi mana yang akan digunakan.</p>
<p>Untuk mengatasi masalah ini, kita menggunakan fitur <code class="language-plaintext highlighter-rouge">configuration</code> Ivy. Kita membuat konfigurasi bernama <code class="language-plaintext highlighter-rouge">test</code> yang membawa <code class="language-plaintext highlighter-rouge">asm</code> versi 2.2.1 sesuai kebutuhan Cobertura. Selain itu, kita juga membuat konfigurasi bernama <code class="language-plaintext highlighter-rouge">runtime</code> dengan <code class="language-plaintext highlighter-rouge">asm</code> versi 1.5.3 untuk digunakan Hibernate. Hal ini dimungkinkan karena Cobertura hanya kita gunakan pada saat test.</p>
<p>Contoh lain penggunaan <code class="language-plaintext highlighter-rouge">configuration</code> adalah kombinasi modul untuk merakit aplikasi. Misalnya, kita memiliki modul-modul berikut dalam aplikasi kita:</p>
<ul>
<li>
<p>model</p>
</li>
<li>
<p>dao.api</p>
</li>
<li>
<p>dao.hibernate</p>
</li>
<li>
<p>dao.jdbc</p>
</li>
<li>
<p>ui.web</p>
</li>
<li>
<p>ui.desktop</p>
</li>
</ul>
<p>Kita dapat menggunakan konfigurasi Ivy untuk membentuk 4 kombinasi aplikasi, yaitu:</p>
<ul>
<li>
<p>Desktop dengan JDBC</p>
</li>
<li>
<p>Desktop dengan Hibernate</p>
</li>
<li>
<p>Web-based dengan JDBC</p>
</li>
<li>
<p>Web-based dengan Hibernate</p>
</li>
</ul>
<p>Fitur configuration juga bisa digunakan untuk mengatur rilis artifak, sehingga dari satu paket source-code, kita bisa membuat rilis:</p>
<ul>
<li>
<p>Hanya source-code (source only)</p>
</li>
<li>
<p>Hanya hasil kompilasi (binary only)</p>
</li>
<li>
<p>Paket dokumentasi (javadoc, reference)</p>
</li>
<li>
<p>Paket komplit (source, binary, dokumentasi)</p>
</li>
</ul>
<p>Kombinasi rilis ini umum kita temui dalam pustaka open-source populer seperti Spring Framework atau Hibernate.</p>
<p>Masih banyak lagi skenario penggunaan configuration. Silahkan kembangkan imajinasi Anda untuk penggunaannya. Pada artikel ini, kita akan membahas skenario kombinasi aplikasi seperti ilustrasi di atas.</p>
<p>Kita telah memiliki modul person-model, dengan deklarasi dependensi (ivy.xml) sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-model"/>
<configurations>
<include file="${basedir}/../person-build/ivy/ivyconfigurations.xml" />
</configurations>
<publications>
<artifact name="${ant.project.name}" conf="api"/>
<artifact name="${ant.project.name}-sources" conf="source" type="src" ext="jar"/>
</publications>
</ivy-module>
</code></pre></div></div>
<p>Daftar konfigurasi yang kita miliki didefinisikan dalam file ivyconfigurations.xml dan di-include dari masing-masing ivy.xml. Berikut isi ivyconfigurations.xml.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><configurations>
<conf name="compile" description="dependency for compile time only" />
<conf name="api" description="Domain Model and API only"/>
<conf name="impl" description="implementation of APIs"/>
<conf name="source" description="source code only"/>
</configurations>
</code></pre></div></div>
<p>Kita juga punya modul person-dao, interface yang digunakan sebagai patokan implementasi akses database, dengan dependensi sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-dao-api"/>
<configurations>
<include file="${basedir}/../person-build/ivy/ivyconfigurations.xml" />
</configurations>
<publications>
<artifact name="${ant.project.name}" conf="api"/>
<artifact name="${ant.project.name}-sources" conf="source" type="src" ext="jar"/>
</publications>
<dependencies>
<dependency name="person-model" rev="latest.integration" conf="api"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>Modul person-dao ini memiliki dua jenis implementasi, yaitu dengan JDBC:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-dao-jdbc"/>
<configurations>
<include file="${basedir}/../person-build/ivy/ivyconfigurations.xml" />
</configurations>
<publications>
<artifact name="${ant.project.name}" conf="impl"/>
<artifact name="${ant.project.name}-sources" conf="source" type="src" ext="jar"/>
</publications>
<dependency name="person-dao-api" rev="latest.integration" conf="api"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>dan dengan Hibernate:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-dao-hibernate"/>
<configurations>
<include file="${basedir}/../person-build/ivy/ivyconfigurations.xml" />
</configurations>
<publications>
<artifact name="${ant.project.name}" conf="impl"/>
<artifact name="${ant.project.name}-sources" conf="source" type="src" ext="jar"/>
</publications>
<dependency name="person-dao-api" rev="latest.integration" conf="api"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>Dengan memisahkan interface dan implementasi DAO, kita bisa langsung membuat dua jenis user interface, yaitu yang berbasis web sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-ui-springmvc"/>
<configurations>
<include file="${basedir}/../person-build/ivy/ivyconfigurations.xml" />
</configurations>
<publications>
<artifact name="${ant.project.name}" conf="impl"/>
<artifact name="${ant.project.name}-sources" conf="source" type="src" ext="jar"/>
</publications>
<dependency name="person-dao-api" rev="latest.integration" conf="api"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>dan berbasis desktop sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-ui-swing"/>
<configurations>
<include file="${basedir}/../person-build/ivy/ivyconfigurations.xml" />
</configurations>
<publications>
<artifact name="${ant.project.name}" conf="impl"/>
<artifact name="${ant.project.name}-sources" conf="source" type="src" ext="jar"/>
</publications>
<dependency name="person-dao-api" rev="latest.integration" conf="api"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>Seperti kita lihat, untuk bisa membuat UI, kita hanya membutuhkan modul dao saja.</p>
<p>Untuk merangkai aplikasi ini, kita membuat satu modul yang tidak berisi source-code Java sama sekali, melainkan hanya deklarasi dependensi saja.</p>
<p>Berikut konfigurasi untuk aplikasi desktop dengan JDBC.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-apps"/>
<configurations>
<conf name="person-app-desktop-jdbc" description="Application with desktop UI and JDBC backend"/>
</configurations>
<dependencies>
<dependency name="person-dao-jdbc" rev="latest.integration"
conf="person-app-desktop-jdbc->impl"/>
<dependency name="person-ui-swing" rev="latest.integration"
conf="person-app-desktop-jdbc->impl"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>dan ini adalah konfigurasi untuk aplikasi web dengan Hibernate.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-apps"/>
<configurations>
<conf name="person-app-web-hibernate" description="Application with web-based UI and Hibernate backend"/>
</configurations>
<dependencies>
<dependency name="person-dao-hibernate" rev="latest.integration"
conf="person-app-web-hibernate->impl"/>
<dependency name="person-ui-springmvc" rev="latest.integration"
conf="person-app-web-hibernate->impl"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>Deklarasi lengkap konfigurasi dan dependensi untuk keempat kombinasi aplikasi sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-apps"/>
<configurations>
<conf name="person-app-web-jdbc" description="Application with web-based UI and JDBC backend"/>
<conf name="person-app-desktop-jdbc" description="Application with desktop UI and JDBC backend"/>
<conf name="person-app-web-hibernate" description="Application with web-based UI and Hibernate backend"/>
<conf name="person-app-desktop-hibernate" description="Application with desktop UI and Hibernate backend"/>
</configurations>
<dependencies>
<dependency name="person-model" rev="latest.integration" conf="*->api"/>
<dependency name="person-dao-api" rev="latest.integration" conf="*->api"/>
<dependency name="person-dao-jdbc" rev="latest.integration"
conf="person-app-web-jdbc->impl;person-app-desktop-jdbc->impl"/>
<dependency name="person-dao-hibernate" rev="latest.integration"
conf="person-app-web-hibernate->impl;person-app-desktop-hibernate->impl"/>
<dependency name="person-ui-swing" rev="latest.integration"
conf="person-app-desktop-jdbc->impl;person-app-desktop-hibernate->impl"/>
<dependency name="person-ui-springmvc" rev="latest.integration"
conf="person-app-web-jdbc->impl;person-app-web-hibernate->impl"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>Kita dapat merakit aplikasi dengan ant sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant resolve
</code></pre></div></div>
<p>Nanti pada folder lib akan terbentuk empat folder sesuai konfigurasi. Isi dari masing-masing folder mencerminkan paket aplikasi sesuai kombinasi yang kita inginkan.</p>
<p>Seperti kita lihat, dengan menggunakan fitur configuration, kita dapat merangkai empat kombinasi aplikasi dengan mudah. Tentunya aplikasi kita harus dirancang secara modular supaya mudah dikombinasikan seperti cerita di atas.</p>
<p><a href="http://endy.artivisi.com/blog/java/ant-ivy-8/">Artikel berikutnya</a> akan menutup seri Ivy ini dengan cara membuat repository dalam organisasi kita. Dengan adanya repository lokal, kita dapat menghemat bandwidth internasional karena kebutuhan dependensi dapat ditangani oleh server internal.</p>
<p>Kita akan melihat kemampuan Ivy untuk mengadaptasi repository baik yang memiliki metadata Ivy, maupun Maven.</p>
External Dependency dengan Ivy2008-10-01T17:06:24+07:00https://software.endy.muhardin.com/java/ant-ivy-6<p>Pada <a href="http://endy.artivisi.com/blog/java/ant-ivy-5/">artikel sebelumnya</a>, kita telah menghubungkan dua modul berbeda dengan deklarasi dependensi. Tapi hal itu belum cukup, karena pada aplikasi yang sebenarnya, kita akan banyak menggunakan pustaka yang tidak kita buat sendiri.</p>
<p>Agar kita bisa menggunakan pustaka luar tersebut, kita harus mengetahui alamat repository yang menyimpan pustaka yang kita inginkan, berikut dependensinya. Misalnya, bila kita ingin menggunakan Hibernate, kita harus mengetahui repository yang memuat artifak hibernate.jar dan juga dependensinya seperti asm.jar, cglib.jar, dan segudang *.jar lainnya.</p>
<p>Selanjutnya, untuk menghemat bandwidth perusahaan, kita dapat menaruh pustaka yang sering digunakan tersebut dalam jaringan perusahaan. Dengan demikian semua orang yang akan menggunakan *.jar tersebut tidak perlu mendonlod dari lokasi asalnya, tapi cukup dari jaringan lokal. Pembuatan mirror ini akan kita diskusikan di artikel terakhir dalam seri ini.</p>
<p>Ada dua masalah utama yang kita hadapi dalam menggunakan pustaka luar. Pertama, bagaimana mencari dan memilih repository yang baik. Kedua, bagaimana mendaftarkannya dalam project kita.</p>
<p>Mencari dan memilih repository merupakan hal yang krusial. Para pengguna Maven, tools untuk dependency management seperti Ivy, memiliki repository yang dapat diakses melalui internet. Akan tetapi, ada satu masalah besar, isinya tidak lengkap dan deklarasi metadatanya banyak yang berantakan. Sebagai ilustrasi, dari dokumentasi Hibernate kita mengetahui bahwa hibernate.jar membutuhkan asm.jar, commons-collection.jar, dan cglib.jar. Tapi di repository Maven, kadang hanya tersedia asm.jar. Artifak commons-collection.jar ada di deklarasi dependensi, tapi filenya tidak ada. Bahkan cglib.jar sama sekali tidak dicantumkan dalam deklarasi dependensi. Nah, kira-kira seperti itulah kondisi repository yang tidak dipelihara dengan baik. Metadata dependensinya tidak akurat, dan koleksi artifaknya tidak lengkap.</p>
<p>Untungnya –SpringSource, perusahaan yang membekingi Spring Framework– telah membuatkan <a href="http://www.springsource.com/repository/">repository</a> yang cukup mumpuni. Mereka menjamin bahwa repository tersebut transitively complete. Artinya, bila hibernate.jar membutuhkan commons-collection.jar dan commons-collection.jar membutuhkan commons-lang.jar, kita bisa yakin bahwa ketiga .jar tersebut ada dalam repository. SpringSource menyediakan metadata untuk ivy dan maven2. Jadi selain kita pengguna Ivy, pengguna Maven2 juga bisa menggunakan repository tersebut. Semua *.jar yang ada dalam repo ini sudah dijamin OSGi compliant. Ini akan sangat berguna apabila kita membangun di platform OSGi. Yang paling penting, repository ini memiliki fasilitas pencarian.</p>
<p>Bila dependensi yang kita inginkan tidak tersedia di repository SpringSource, kita masih dapat mencari ke repository Maven2 yang disediakan oleh ibiblio.</p>
<p>Baiklah, masalah pertama sudah terpecahkan. Sekarang mari kita selesaikan masalah kedua, yaitu menggunakan kedua repository tersebut dalam aplikasi kita.</p>
<p>Repository ini, sama dengan internal repository yang sudah kita bahas pada artikel terdahulu, akan dideklarasikan dalam file ivysettings.xml. Berikut adalah entri untuk repository SpringSource.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><url name="springsource.release.repo.resolver">
<ivy pattern="http://repository.springsource.com/ivy/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="http://repository.springsource.com/ivy/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
<url name="springsource.external.repo.resolver">
<ivy pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
</code></pre></div></div>
<p>Cara konfigurasi ini juga bisa dilihat di situs repository SpringSource.
Dan ini adalah konfigurasi untuk repository iBiblio.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ibiblio name="maven2" m2compatible="true" />
</code></pre></div></div>
<p>Kita dapat menyuruh Ivy untuk terlebih dulu mencari di repo SpringSource. Bila ditemukan, hentikan pencarian. Bila tidak ditemukan, lanjutkan ke iBiblio. Untuk mengaktifkan mekanisme ini, gunakan ChainResolver sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><chain name="external-repository">
<chain name="springsource-repository" returnFirst="true">
<url name="springsource.release.repo.resolver">
<ivy pattern="http://repository.springsource.com/ivy/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="http://repository.springsource.com/ivy/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
<url name="springsource.external.repo.resolver">
<ivy pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
</chain>
<chain name="ibiblio.repo.resolver">
<ibiblio name="maven2" m2compatible="true" />
</chain>
</chain>
</code></pre></div></div>
<p>Atribut <code class="language-plaintext highlighter-rouge">returnFirst</code> digunakan untuk menghentikan pencarian bila artifak sudah ditemukan.</p>
<p>Setelah repository kita daftarkan, kita tinggal mengkonfigurasi dependensi di project kita. Modul person-dao-impl akan menggunakan Spring Framework 2.5.5.A dan juga Hibernate 3.2.6.ga. Kita dapat mencari di situs repository SpringSource untuk mendapatkan deklarasi dependensi yang sesuai untuk <a href="http://www.springsource.com/repository/app/search?query=spring">Spring Framework</a> dan <a href="http://www.springsource.com/repository/app/search?query=hibernate">Hibernate</a>.</p>
<p>Berikut adalah file ivy.xml dari modul person-dao-impl.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="com.artivisi.tutorial.ivy.dao.impl"/>
<publications>
<artifact name="${ant.project.name}"/>
<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
</publications>
<dependencies>
<dependency
org="com.artivisi"
name="com.artivisi.tutorial.ivy.dao.api"
rev="latest.integration"
/>
<dependency
org="org.springframework"
name="org.springframework.orm"
rev="2.5.5.A"
/>
<dependency
org="org.hibernate"
name="com.springsource.org.hibernate"
rev="3.2.6.ga"
/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>Terlihat dari ivy.xml bahwa modul ini memiliki dependensi terhadap modul person-dao-api, yang mana memiliki dependensi terhadap person-model. Dengan fitur transitive-dependency yang dimiliki Ivy, kita tidak perlu mendeklarasikan dependensi terhadap person-model, karena sudah secara otomatis didaftarkan melalui person-dao-api.</p>
<p>Modul ini memiliki satu source code, yaitu PersonDaoHibernate, sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.tutorial.ivy.dao.impl;
import java.util.List;
import com.artivisi.tutorial.ivy.model.Person;
import com.artivisi.tutorial.ivy.dao.PersonDao;
@Transactional
public class PersonDaoHibernate implements PersonDao {
private SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sf) {
this.sessionFactory = sf;
}
public void save(Person person) {
sessionFactory.getCurrentSession()
.saveOrUpdate(person);
}
@SuppressWarnings("unchecked")
public List<Person> getAll(){
return sessionFactory.getCurrentSession()
.createCriteria(Person.class)
.list();
}
public Person getById(Long id){
return (Person) sessionFactory.getCurrentSession()
.get(Person.class, id);
}
}
</code></pre></div></div>
<p>Seperti kita lihat, kode program tersebut menggunakan @Transactional dari Spring Framework, dan SessionFactory milik Hibernate. Mari kita lakukan kompilasi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant build
</code></pre></div></div>
<blockquote>
<p>PERHATIAN!!! Perintah build ini akan mengakses internet dan mendonlod semua *.jar yang dibutuhkan. Ini akan memakan banyak bandwidth internasional Anda.</p>
</blockquote>
<p>Dari output kita bisa lihat bahwa Ivy menemukan dependensi yang dibutuhkan di repository SpringSource, dan kemudian mendonlodnya. Hasil donlod akan disimpan dalam cache, sehingga bila kita melakukan build lagi, tidak perlu mendonlod berkali-kali.</p>
<p>Demikianlah penjelasan tentang dependensi eksternal. Pada <a href="http://endy.artivisi.com/blog/java/ant-ivy-7/">artikel selanjutnya</a>, kita akan bahas tentang configuration, fitur yang sangat canggih dari Ivy.</p>
Internal Dependency dengan Ivy2008-09-28T16:51:32+07:00https://software.endy.muhardin.com/java/ant-ivy-5<p>Pada <a href="http://endy.artivisi.com/blog/java/ant-ivy-4/">artikel sebelumny</a>a, kita telah menyimpan artifact modul person-model di shared repository melalui mekanisme scp dengan private key. Pada artikel ini, kita akan menggunakan artifact person-model tersebut dalam modul person-dao-api. Pengambilan artifact dilakukan melalui mekanisme yang sama, yaitu scp dengan private key.</p>
<p>Berikut struktur folder modul person-dao-api.</p>
<p><a href="/images/uploads/2008/09/folder-person-dao-api-before-resolve.png"><img src="/images/uploads/2008/09/folder-person-dao-api-before-resolve.png" alt=" " /></a></p>
<p>Kita memiliki folder src yang berisi source code class <code class="language-plaintext highlighter-rouge">PersonDao.java</code>, yang isinya sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.tutorial.ivy.dao;
import java.util.List;
import com.artivisi.tutorial.ivy.model.Person;
public interface PersonDao {
/**
* saves Person object into database.
* If object is already exists (denoted by not-null ID field),
* the existing record with the corresponding ID is updated.
* If the object is new (denoted by null ID field),
* new record is inserted.
*
* This method also set the ID field for new record.
* */
public void save(Person person);
/**
* fetch all person object in database.
* @return List of all person
* */
public List getAll();
/**
* fetch Person object with the speficied ID.
* @param id identifier for person object
* @return Person object if there is record found for the speficied id, null otherwise
* */
public Person getById(Long id);
}
</code></pre></div></div>
<p>Seperti terlihat dalam deklarasi import, class ini menggunakan class <code class="language-plaintext highlighter-rouge">Person</code>, yang sudah tersedia di shared repository. Kita akan melakukan build dengan menggunakan script <code class="language-plaintext highlighter-rouge">build.xml</code>. Isinya sama dengan yang ada di modul person-model, dengan perkecualian nama projectnya. Berikut isi <code class="language-plaintext highlighter-rouge">build.xml</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project name="person-dao-api" default="build">
<property file="build.properties"/>
<import file="${basedir}/../person-build/default.xml"/>
<import file="${basedir}/../person-build/ivy-builder.xml"/>
</project>
</code></pre></div></div>
<p>Dependensi modul <code class="language-plaintext highlighter-rouge">person-dao-api</code> terhadap <code class="language-plaintext highlighter-rouge">person-model</code> kita deklarasikan dalam <code class="language-plaintext highlighter-rouge">ivy.xml</code> sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="person-dao-api"/>
<publications>
<artifact name="${ant.project.name}" conf="api"/>
<artifact name="${ant.project.name}-sources" conf="source" type="src" ext="jar"/>
</publications>
<dependencies>
<dependency name="person-model" rev="latest.integration" conf="api"/>
</dependencies>
</ivy-module>
</code></pre></div></div>
<p>Setelah <code class="language-plaintext highlighter-rouge">build.xml</code> dan <code class="language-plaintext highlighter-rouge">ivy.xml</code> selesai dibuat, kita bisa langsung melakukan build. Panggil target dari command line sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant build
</code></pre></div></div>
<p>Dari output perintah di atas terlihat bahwa Ivy akan menguraikan dependensi project dengan melakukan query ke semua resolver yang terdaftar. Setelah modul yang dibutuhkan (person-model) ditemukan, Ivy akan langsung mendonlodnya ke dalam folder lib. Karena isi folder lib sudah kita daftarkan ke dalam classpath, maka proses kompilasi akan berjalan dengan mulus. Berikut struktur folder <code class="language-plaintext highlighter-rouge">person-dao-api</code> setelah melakukan proses resolve.</p>
<p><a href="/images/uploads/2008/09/folder-person-dao-api-after-resolve.png"><img src="/images/uploads/2008/09/folder-person-dao-api-after-resolve.png" alt=" " /></a></p>
<p>Pada deklarasi dependensi di atas, terlihat bahwa modul <code class="language-plaintext highlighter-rouge">person-dao-api</code> memiliki dependensi terhadap modul <code class="language-plaintext highlighter-rouge">person-model</code> dengan revision <code class="language-plaintext highlighter-rouge">latest.integration</code>. Ini artinya Ivy akan menggunakan versi terbaru yang ada dalam repository. Untuk melihat kemampuan fitur ini, mari kita publish <code class="language-plaintext highlighter-rouge">person-model</code> dengan revision yang lebih baru.</p>
<p>Edit file build.properties dalam modul person-model, sehingga menjadi seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build.version = 0.0.2
release.type = integration
</code></pre></div></div>
<p>Setelah itu, bersihkan hasil kompilasi terdahulu, dan publish modul <code class="language-plaintext highlighter-rouge">person-model</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant clean publish-local
</code></pre></div></div>
<p>Lalu, pindah ke modul person-dao-api, dan lakukan build lagi.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant clean build
</code></pre></div></div>
<p>Terlihat pada output, bahwa modul <code class="language-plaintext highlighter-rouge">person-model</code> yang terbaru akan didonlod, dan yang versi lama akan dihapus. Sehingga isi folder lib akan terlihat sebagai berikut.</p>
<p>Ivy memiliki beberapa cara untuk menentukan mana artifak yang terbaru (latest), yaitu: latest-time, latest-revision, dan latest-lexico.</p>
<p>Dengan menggunakan latest-time, Ivy akan melihat tanggal dibuatnya suatu artifak. Kalau artifak berada di repository filesystem, maka tanggal sistem operasi akan digunakan. Bila repository diakses melalui http, Ivy akan melakukan query terhadap http server. Metode ini, walaupun efektif, relatif lambat bila akses jaringan ke repository tidak mumpuni.</p>
<p>Latest-revision membandingkan nama versi artifak, dan mengenali beberapa keyword. Dia dapat menentukan bahwa versi 1.0-alpha lebih baru daripada 1.0-dev1, dan 1.0 lebih baru daripada 1.0-rc1.</p>
<p>Sedangkan strategi latest-lexico hanya membandingkan berdasarkan urutan abjad. Jadi, 1.0-m1 akan lebih baru daripada 1.0-build135.</p>
<p>Ketiga strategi tersebut sudah built-in dalam Ivy dan tidak memerlukan konfigurasi lebih lanjut.</p>
<p>Selain revision, Ivy juga mengenal terminologi status. Secara default, Ivy menyediakan tiga status diurutkan dari yang paling stabil/mature, yaitu release, milestone, dan integration. Bila kita menggunakan latest.integration, Ivy akan mengambil artifak terbaru berstatus apapun dari repository. Bila kita gunakan latest.milestone, Ivy akan mengabaikan rilis integration, dan hanya akan membandingkan milestone dan release. Demikian juga bila kita gunakan latest.release, maka versi terbaru hanya akan dicari dari artifak berstatus release.</p>
<p>Cara deklarasi latest.integration di atas disebut dengan dynamic revision, karena nomer revision tidak secara eksplisit disebutkan. Selain menggunakan latest.integration, ada beberapa mekanisme lain, yaitu menggunakan +, dan menggunakan range.</p>
<p>Kita bisa menggunakan + untuk memilih revisi terbaru untuk rilis tertentu. Misalnya bila kita memiliki modul dengan revisi 1.0.1, 1.0.4, 1.0.7, dan 1.1.4 dalam repository, kita dapat menggunakan 1.0.+ untuk memilih versi terbaru dalam lini 1.0, yaitu 1.0.7.</p>
<p>Kita juga bisa menggunakan range, untuk menentukan revisi yang dapat digunakan, misalnya [1.0,2.2] akan memilih semua revisi yang sama atau lebih besar dari 1.0, tapi lebih kecil atau sama dengan 2.2. Lebih lengkap tentang ini bisa dilihat di dokumentasi Ivy tentang dependensi.</p>
<p>Demikianlah deklarasi dependensi dengan Ivy. Pada artikel ini, kita baru mencoba dependensi terhadap modul yang kita buat sendiri. Di <a href="http://endy.artivisi.com/blog/java/ant-ivy-6/">artikel selanjutnya</a>, kita akan lihat bagaimana mendeklarasikan dependensi terhadap modul eksternal seperti Spring Framework atau Hibernate.</p>
Publish Modul ke Repository2008-09-10T16:57:41+07:00https://software.endy.muhardin.com/java/ant-ivy-4<p>Template project sudah dibuat pada <a href="http://endy.artivisi.com/blog/java/ant-ivy-3/">artikel sebelumnya</a>. Pada artikel ini, kita akan menghubungkan kedua modul ini dengan menggunakan Ivy.</p>
<p>Seperti kita ketahui, modul <code class="language-plaintext highlighter-rouge">person-dao-api</code> mempunyai dependensi terhadap modul <code class="language-plaintext highlighter-rouge">person-model</code>. Berikut gambarnya.</p>
<p><a href="/images/uploads/2008/09/dep-after.png"><img src="/images/uploads/2008/09/dep-after.png" alt=" " /></a></p>
<p>Dependensi ini terlihat di source-code <code class="language-plaintext highlighter-rouge">PersonDao.java</code>, yang melakukan import terhadap class Person, sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.tutorial.ivy.dao;
import java.util.List;
import com.artivisi.tutorial.ivy.model.Person;
public interface PersonDao {
/**
* saves Person object into database.
* If object is already exists (denoted by not-null ID field),
* the existing record with the corresponding ID is updated.
* If the object is new (denoted by null ID field),
* new record is inserted.
*
* This method also set the ID field for new record.
* */
public void save(Person person);
/**
* fetch all person object in database.
* @return List of all person
* */
public List getAll();
/**
* fetch Person object with the speficied ID.
* @param id identifier for person object
* @return Person object if there is record found for the speficied id, null otherwise
* */
public Person getById(Long id);
}
</code></pre></div></div>
<p>Agar class diatas bisa dikompilasi dengan lancar, ada tiga hal yang harus dilakukan:</p>
<ol>
<li>
<p>Menyediakan lokasi yang dapat diakses oleh kedua modul</p>
</li>
<li>
<p>Mempublikasikan distribusi modul person-model</p>
</li>
<li>
<p>Mendeklarasikan dependensi person-dao-api terhadap person-model</p>
</li>
</ol>
<h3 id="repository-ivy">Repository Ivy</h3>
<p>Dalam dunia Ivy, file-file distribusi suatu modul, seperti <code class="language-plaintext highlighter-rouge">person-model.jar</code>, <code class="language-plaintext highlighter-rouge">person-model-sources.jar</code>, disebut dengan istilah artifact.
Artifact ini diletakkan di lokasi tertentu, disebut dengan istilah repository. Konfigurasi tentang cara mengakses suatu repository disebut dengan istilah resolver. Ivy menyediakan berbagai resolver untuk berbagai metode/protokol, yaitu:</p>
<ul>
<li>
<p>Filesystem : ini biasanya digunakan untuk folder di komputer lokal atau shared folder (NFS atau Windows Share)</p>
</li>
<li>
<p>URL : digunakan untuk mengakses repository melalui http</p>
</li>
<li>
<p>SSH : digunakan untuk mengakses repository melalui mekanisme scp</p>
</li>
<li>
<p>SFTP : menggunakan protokol FTP yang terenkripsi</p>
</li>
<li>
<p>VFS : menggunakan Apache Commons VFS sebagai backend. Mendukung apapun jenis filesystem yang didukung VFS, salah satunya sftp</p>
</li>
<li>
<p>Ibiblio : untuk mengakses repository maven2</p>
</li>
</ul>
<p>Selain itu, Ivy juga menyediakan composite resolver, yaitu resolver yang dapat menampung resolver lainnya. Composite resolver ada dua:</p>
<ul>
<li>
<p>Chain Resolver : menggabungkan beberapa resolver, sehingga kalau suatu artifak tidak ditemukan di resolver pertama, bisa melanjutkan pencarian ke resolver selanjutnya dalam chain</p>
</li>
<li>
<p>Dual Resolver : memisahkan resolver untuk menghitung dependensi, dan resolver untuk mendownload artifak.</p>
</li>
</ul>
<p>Baiklah, ternyata Ivy bisa menangani macam-macam protokol. Tapi bagaimana cara kita mendesain repository yang baik? Bagaimana best-practicesnya? Mari kita bahas.</p>
<h3 id="desain-repository">Desain Repository</h3>
<p>Biasanya, kita memiliki dua jenis repository, internal dan external. Repository internal digunakan untuk menyimpan artifak yang dihasilkan project dalam organisasi. Sebagian atau semua artifak internal ini mungkin saja bersifat komersil atau proprietary, sehingga tidak dibuka untuk konsumsi publik.</p>
<p>Di internet tersedia repository yang bisa diakses semua orang. Pengguna Maven biasanya menggunakan <a href="http://www.ibiblio.org/maven/">repository ibiblio</a>. Pengguna Spring OSGi biasanya menggunakan <a href="http://www.springsource.com/repository">repository SpringSource</a>, yang sudah berisi library yang OSGi compliant. Repository SpringSource kompatibel baik dengan Ivy maupun Maven. Untuk menghemat bandwidth internasional, organisasi kita bisa membuat mirror dari repository publik ini agar dependensi bisa diunduh dari jaringan lokal.</p>
<p>Pada artikel ini, kita hanya akan membahas tentang internal repository. Pembahasan tentang external repository akan dibahas pada artikel yang akan datang.</p>
<h3 id="internal-repository">Internal Repository</h3>
<p>Internal repository dibagi lagi menjadi dua kategori, yaitu local repository, dan shared repository.</p>
<p>Local repository berada di PC masing-masing programmer. Misalnya satu programmer mengerjakan dua modul yang saling berhubungan, misalnya <code class="language-plaintext highlighter-rouge">person-model</code> dan <code class="language-plaintext highlighter-rouge">person-dao-api</code>. Seringkali dia membuat perubahan di <code class="language-plaintext highlighter-rouge">person-model</code> yang akan digunakan di <code class="language-plaintext highlighter-rouge">person-dao-api</code>. Tapi karena kode programnya belum sempurna, dia tidak ingin merilis artifak tersebut ke anggota tim yang lain. Untuk kebutuhan ini, dia mempublikasikan artifact <code class="language-plaintext highlighter-rouge">person-model</code> ke local repo di PCnya dia sendiri, sehingga bisa diakses oleh modul <code class="language-plaintext highlighter-rouge">person-dao-api</code>.</p>
<p>Setelah <code class="language-plaintext highlighter-rouge">person-model</code> dan <code class="language-plaintext highlighter-rouge">person-dao-api</code> dibuat dan ditest secara menyeluruh, barulah programmer tersebut merilis artifak ke shared repo agar bisa digunakan rekan-rekannya.</p>
<h3 id="konfigurasi-internal-resolver">Konfigurasi Internal Resolver</h3>
<p>Untuk mengimplementasikan skenario di atas, kita pertama akan mendefinisikan repository local. Asumsikan saja repository ini akan disimpan di folder local-repo sejajar dengan modul-modul yang lainnya. Kita akan mengkonfigurasi resolvernya di file bernama <code class="language-plaintext highlighter-rouge">ivysettings.xml</code>, diletakkan di modul <code class="language-plaintext highlighter-rouge">person-build</code> dalam subfolder ivy. Berikut isinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivysettings>
<settings defaultResolver="local" />
<caches defaultCacheDir="${ivy.settings.dir}/../../ivy-cache" />
<resolvers>
<filesystem name="local">
<artifact
pattern="${ivy.settings.dir}/../../local-repo/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<ivy
pattern="${ivy.settings.dir}/../../local-repo/release/[organisation]/[module]/[revision]/[artifact]-[revision].xml" />
</filesystem>
</resolvers>
</ivysettings>
</code></pre></div></div>
<p>Ada beberapa hal yang harus dijelaskan di sini.</p>
<ul>
<li>
<p>defaultResolver : ini adalah resolver yang digunakan oleh Ivy bila kita tidak secara eksplisit memilih resolver. Setting ini akan berguna nantinya bila kita mendeklarasikan dependensi</p>
</li>
<li>
<p>defaultCacheDir : menyebutkan lokasi cache di komputer lokal. Bila tidak dikonfigurasi, Ivy akan menyimpan cache di folder <code class="language-plaintext highlighter-rouge">.ivy2/cache</code> dalam home folder user.</p>
</li>
<li>
<p>Variabel ${ivy.settings.dir} : lokasi folder tempat file <code class="language-plaintext highlighter-rouge">ivysettings.xml</code> berada</p>
</li>
<li>
<p>Artifact Pattern : struktur folder tempat artifak disimpan</p>
</li>
<li>
<p>Ivy Pattern : struktur folder tempat metadata modul disimpan. Kita akan membahas tentang metadata ini nanti.</p>
</li>
</ul>
<p>Selanjutnya, kita butuh repository yang bisa digunakan seluruh tim dan programmer dalam perusahaan. Ini berguna bila modul yang kita buat akan digunakan oleh tim lain. Untuk itu, kita akan mengkonfigurasi repository yang akan kita beri nama company. Agar aman, kita gunakan protokol scp untuk memindahkan file ke server. Otentikasinya menggunakan public/private key supaya semua artifact disimpan dengan nama user yang sama. Berikut konfigurasi resolvernya. Tambahkan di bawah resolver local.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ssh name="company" keyFile="${user.home}/.ssh/id_rsa" host="nama-servernya" user="user-untuk-login-di-server">
<ivy
pattern="/lokasi-folder-di-server/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact
pattern="/lokasi-folder-di-server/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</ssh>
</code></pre></div></div>
<p>Kita telah memiliki dua resolver, satu untuk repository di komputer lokal, dan satu lagi repository yang disharing ke seluruh organisasi.</p>
<h3 id="target-resolve-dan-publish">Target Resolve dan Publish</h3>
<p>Setelah kita melakukan konfigurasi resolver, sekarang kita bisa mempublish artifact dari modul person-model.
Untuk melakukan publishing, kita perlu membuat target di buildfile kita. Agar buildfile tetap rapi, kita buat file baru, yaitu <code class="language-plaintext highlighter-rouge">ivy-builder.xml</code>, diletakkan di modul <code class="language-plaintext highlighter-rouge">person-build</code>. File ini nantinya akan kita import di <code class="language-plaintext highlighter-rouge">build.xml</code> dalam masing-masing modul. Berikut isi file <code class="language-plaintext highlighter-rouge">ivy-builder.xml</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project name="ivy-related-targets" xmlns:ivy="antlib:org.apache.ivy.ant">
<target name="resolve" description="--> resolve and retrieve dependencies with ivy">
<ivy:retrieve
sync="true"
pattern="lib/[conf]/[artifact].[ext]"
/>
</target>
<target name="clean-cache" description="--> clean the ivy cache">
<ivy:cleancache />
</target>
<target name="publish-local" depends="build" description="--> publish this project in the ivy repository">
<property name="revision" value="${build.version}" />
<delete file="${build.dir}/ivy.xml" />
<ivy:publish
artifactspattern="${dist.dir}/[artifact].[ext]"
resolver="local"
pubrevision="${revision}"
status="${release.type}"
update="true"
overwrite="true"
/>
<echo message="project ${ant.project.name} released locally with version ${revision} and status ${release.type}" />
</target>
<target name="publish-company" depends="build" description="--> publish this project to company repository">
<property name="revision" value="${build.version}" />
<delete file="${build.dir}/ivy.xml" />
<ivy:publish
artifactspattern="${dist.dir}/[artifact].[ext]"
resolver="company"
pubrevision="${revision}"
status="${release.type}"
update="true"
overwrite="true"
/>
<echo message="project ${ant.project.name} released to company repo with version ${revision} and status ${release.type}" />
</target>
<target name="ivy-report" depends="resolve" description="--> generate dependency report">
<ivy:report todir="build/ivy-report"/>
</target>
</project>
</code></pre></div></div>
<p>Terlihat dari target publish di atas bahwa kita membutuhkan dua variabel untuk melakukan publish, yaitu <code class="language-plaintext highlighter-rouge">${revision}</code> dan <code class="language-plaintext highlighter-rouge">${release.type}</code>. Kedua variabel ini akan kita sediakan pada masing-masing modul.</p>
<p>Bila kita ingin mempublish artifact, terlebih dulu kita harus melakukan build. Kita juga memiliki target resolve untuk menentukan dependency. Target resolve ini dijalankan sebelum melakukan kompilasi. Dengan demikian, kita harus menyesuaikan target compile dalam <code class="language-plaintext highlighter-rouge">default.xml</code> agar menjalankan resolve sebelum compile. Ubah baris berikut dalam <code class="language-plaintext highlighter-rouge">default.xml</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><target name="compile" depends="prepare">
<javac srcdir="${src.java.dir}" destdir="${compile.dir}" classpathref="compile.classpath" />
</target>
</code></pre></div></div>
<p>menjadi seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><target name="compile" depends="resolve, prepare">
<javac srcdir="${src.java.dir}" destdir="${compile.dir}" classpathref="compile.classpath" />
</target>
</code></pre></div></div>
<h3 id="mempublish-artifact">Mempublish Artifact</h3>
<p>Sekarang kita ingin mempublish artifact yang dihasilkan modul <code class="language-plaintext highlighter-rouge">person-model</code>. Seperti kita ketahui pada artikel sebelumnya, bila kita menjalankan target build, akan dihasilkan dua jar dalam folder <code class="language-plaintext highlighter-rouge">dist</code>, yaitu <code class="language-plaintext highlighter-rouge">person-model.jar</code> dan <code class="language-plaintext highlighter-rouge">person-model-sources.jar</code>. Kita akan mempublish kedua artifak ini ke repository.</p>
<p>Pastikan file <code class="language-plaintext highlighter-rouge">ivy-builder.xml</code> sudah diimport dalam <code class="language-plaintext highlighter-rouge">build.xml</code>. Isi <code class="language-plaintext highlighter-rouge">build.xml</code> harusnya terlihat seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project name="person-model" default="build">
<property file="build.properties"/>
<import file="${basedir}/../person-build/default.xml"/>
<import file="${basedir}/../person-build/ivy-builder.xml"/>
</project>
</code></pre></div></div>
<p>File tersebut mengacu pada file <code class="language-plaintext highlighter-rouge">build.properties</code>. Berikut isi file <code class="language-plaintext highlighter-rouge">build.properties</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build.version = 0.0.1
release.type = integration
</code></pre></div></div>
<p>Kedua variabel di atas digunakan untuk mengisi variabel <code class="language-plaintext highlighter-rouge">${build.version}</code> dan <code class="language-plaintext highlighter-rouge">${release.type}</code> yang dibutuhkan target publish di atas. Setiap kali kita melakukan publish, kita harus menentukan versi dan jenis artifak tersebut.</p>
<p>Nomer versi (build.version) tidak sulit dipahami. Untuk menentukan mana yang lebih baru, tinggal dibandingkan versi major, minor, dan micronya. Release type membutuhkan penjelasan lebih lanjut.</p>
<p>Secara default, Ivy memiliki tiga jenis release, diurutkan dari yang paling experimental sampai yang paling stabil: integration, milestone, dan release. Kita juga bisa mendefinisikan jenis release sendiri, dengan menggunakan tag status dalam <code class="language-plaintext highlighter-rouge">ivysettings.xml</code>. Untuk kebutuhan kita, tiga status yang disediakan Ivy sudah memadai.</p>
<p>Selanjutnya, kita mendefinisikan beberapa metadata yang berkaitan dengan artifact yang ingin dipublish, yaitu:</p>
<ul>
<li>
<p>Nama organisasi kita. Ini akan digunakan Ivy untuk mengatur struktur folder dalam repository</p>
</li>
<li>
<p>Nama modul yang akan dipublish</p>
</li>
<li>
<p>Daftar artifak yang akan dipublish. Satu modul bisa mempublish banyak artifak, misalnya: *.jar yang berisi hasil compile, javadoc, source-code, dsb</p>
</li>
</ul>
<p>Metadata tersebut ditulis dalam file yang bernama <code class="language-plaintext highlighter-rouge">ivy.xml</code>. Diletakkan di sebelah <code class="language-plaintext highlighter-rouge">build.xml</code>. Berikut isinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ivy-module version="1.0">
<info organisation="com.artivisi" module="com.artivisi.tutorial.ivy.model"/>
<publications>
<artifact name="${ant.project.name}"/>
<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
</publications>
</ivy-module>
</code></pre></div></div>
<p>Setelah semuanya lengkap, kita tinggal memanggil</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant publish-local
</code></pre></div></div>
<p>untuk mempublish ke repository local, atau</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant publish-company
</code></pre></div></div>
<p>untuk mempublish ke repository shared. Bila dalam proses pembuatan private key kita menggunakan password, akan muncul dialog box yang menanyakan password. Isikan nama user yang digunakan untuk login ke server dan password dari private key kita. Sekali lagi, username yang dimasukkan adalah username di server, sedangkan password yang dimasukkan adalah password untuk private key kita sendiri. Jadi, kita tidak memasukkan password dari username di server.</p>
<p>Setelah dijalankan, kita akan melihat banyak file dalam repository. Ivy akan merilis: <code class="language-plaintext highlighter-rouge">person-model-0.0.1.jar</code>, <code class="language-plaintext highlighter-rouge">person-sources-0.0.1.jar</code>, dan <code class="language-plaintext highlighter-rouge">ivy-0.0.1.xml</code> yang berisi metadata dari rilis tersebut. Setiap file disertai signature md5 dan sha1 untuk kebutuhan verifikasi keabsahan file.</p>
<p>Berikut struktur folder keseluruhan.
<a href="/images/uploads/2008/09/struktur-folder-4-all.png"><img src="/images/uploads/2008/09/struktur-folder-4-all.png" alt=" " /></a></p>
<p>Isi folder local repo sebagai berikut.</p>
<p><a href="/images/uploads/2008/09/struktur-folder-4-local-repo.png"><img src="/images/uploads/2008/09/struktur-folder-4-local-repo.png" alt=" " /></a></p>
<p>Isi folder person-build sebagai berikut.</p>
<p><a href="/images/uploads/2008/09/struktur-folder-4-person-build.png"><img src="/images/uploads/2008/09/struktur-folder-4-person-build.png" alt=" " /></a></p>
<p>Isi folder person-model sebagai berikut.
<a href="/images/uploads/2008/09/struktur-folder-4-person-model.png"><img src="/images/uploads/2008/09/struktur-folder-4-person-model.png" alt=" " /></a></p>
<p>Pada <a href="http://endy.artivisi.com/blog/java/ant-ivy-5">artikel selanjutnya</a>, kita akan membahas bagaimana cara mengambil artifact yang sudah dipublish tersebut.</p>
Otomasi Build Process dengan Ant2008-09-09T16:48:30+07:00https://software.endy.muhardin.com/java/ant-ivy-3<p>Setelah pada artikel sebelumnya kita memahami <a href="http://endy.artivisi.com/blog/java/ant-ivy-2">mengapa dan kapan kita butuh Ivy</a>, mulai dari artikel ini, kita akan menjalani langkah demi langkah supaya kegiatan build kita benar-benar terintegrasi dan efisien.</p>
<p>Pada artikel ini, kita akan membuat satu modul dulu, yang sama sekali tidak memiliki dependensi, yaitu modul <code class="language-plaintext highlighter-rouge">person-model</code>.
Kita akan mengotomasi proses build untuk modul ini, kemudian menyiapkan infrastruktur lainnya sehingga siap untuk digunakan oleh modul lainnya.</p>
<p>Tiap modul yang kita buat, setara dengan satu project dalam IDE. Dalam IDE kita dapat mendaftarkan (membuat referensi ke) project lain dalam build-path sehingga tidak muncul peringatan compile error.</p>
<p>Berikut struktur folder modul <code class="language-plaintext highlighter-rouge">person-model</code>.</p>
<p><a href="/images/uploads/2008/09/01-project-person-model.png"><img src="/images/uploads/2008/09/01-project-person-model.png" alt=" " /></a></p>
<p>Tidak ada yang istimewa, kita memiliki build.xml disertai dengan folder <code class="language-plaintext highlighter-rouge">src</code> yang berisi source code java. Kita ingin agar struktur folder kita seragam di semua modul, jadi untuk seluruh modul nantinya strukturnya akan sama.</p>
<p>Kita hanya memiliki satu file source code sederhana, <code class="language-plaintext highlighter-rouge">Person.java</code> sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi.tutorial.ivy.model;
import java.util.Date;
public class Person {
private Integer id;
private String name;
private Date birthdate;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
}
</code></pre></div></div>
<p>Sebagai rangkaian build, kita akan melakukan kegiatan sebagai berikut:</p>
<ul>
<li>
<p>Bersihkan hasil kompilasi sebelumnya (clean)</p>
</li>
<li>
<p>Kompilasi (compile)</p>
</li>
<li>
<p>Distribusi, membuat file *.jar dari hasil kompilasi (build)</p>
</li>
</ul>
<p>Rangkaian kegiatan tersebut kita tuliskan dalam file <code class="language-plaintext highlighter-rouge">build.xml</code>, sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project name="person-model">
<property name="src.java.dir" value="src/java" />
<property name="compile.dir" value="build/bin" />
<property name="dist.dir" value="build/dist" />
<path id="compile.classpath">
<pathelement location="${src.java.dir}" />
<fileset dir="lib" includes="**/*.jar" />
</path>
<target name="prepare">
<mkdir dir="lib" />
<mkdir dir="build" />
<mkdir dir="${compile.dir}" />
<mkdir dir="${dist.dir}" />
</target>
<target name="clean">
<delete dir="build" />
<delete dir="lib" />
</target>
<target name="compile" depends="prepare">
<javac srcdir="${src.java.dir}" destdir="${compile.dir}" classpathref="compile.classpath" />
</target>
<target name="build" depends="compile">
<!-- binary distribution -->
<jar destfile="${dist.dir}/${ant.project.name}.jar">
<fileset dir="${compile.dir}">
<exclude name="**/*Test.class"/>
</fileset>
</jar>
<!-- source distribution -->
<jar destfile="${dist.dir}/${ant.project.name}-sources.jar">
<fileset dir="${src.java.dir}">
<exclude name="**/*Test.java"/>
</fileset>
</jar>
</target>
</project>
</code></pre></div></div>
<p>Selanjutnya, kita bisa coba melakukan build.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant build
</code></pre></div></div>
<p>Nanti Ant akan menghasilkan file <code class="language-plaintext highlighter-rouge">person-model.jar</code> dan <code class="language-plaintext highlighter-rouge">person-model-sources.jar</code> dalam folder dist.</p>
<p>File <code class="language-plaintext highlighter-rouge">build.xml</code> ini nantinya akan kita gunakan di seluruh modul. Seluruh modul tentunya butuh kegiatan kompilasi dan membuat jar. Untuk menghindari perulangan dan copy-paste, kita simpan <code class="language-plaintext highlighter-rouge">build.xml</code> ini di folder bersama, kita beri nama saja person-build.</p>
<p>Karena digunakan di banyak modul, kita perlu melakukan penyesuaian sedikit, yaitu namanya kita ganti menjadi <code class="language-plaintext highlighter-rouge">default.xml</code>. Deklarasi nama project juga diganti, dari seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project name="person-model">
</code></pre></div></div>
<p>menjadi seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project name="default-build">
</code></pre></div></div>
<p>Struktur foldernya menjadi seperti ini</p>
<p><a href="/images/uploads/2008/09/02-project-person-build.png"><img src="/images/uploads/2008/09/02-project-person-build.png" alt=" " /></a></p>
<p>Selanjutnya, dalam build.xml di project person-model, kita hanya perlu melakukan import saja. Isi <code class="language-plaintext highlighter-rouge">build.xml</code> menjadi seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project name="person-model" default="build">
<import file="${basedir}/../person-build/build.xml"/>
</project>
</code></pre></div></div>
<p>Silahkan coba lakukan build di dalam folder <code class="language-plaintext highlighter-rouge">person-model</code>. Seharusnya modul ini bisa dibuild tanpa error, sama seperti sebelumnya.</p>
<p>Bila modul person-model sudah lancar, kita bisa mulai mereplikasi modul-modul yang lainnya. Dengan semua modul sudah dibuatkan sesuai template, berikut struktur foldernya.</p>
<p><a href="/images/uploads/2008/09/03-project-all.png"><img src="/images/uploads/2008/09/03-project-all.png" alt=" " /></a></p>
<p>File <code class="language-plaintext highlighter-rouge">build.xml</code> di masing-masing project mirip dengan person-model, hanya dibedakan di baris pertama saja.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project name="person-model">
</code></pre></div></div>
<p>Sesuaikan dengan nama modul masing-masing.</p>
<p>Persiapan template project kita sudah selesai. Sampai tahap ini, kita hanya memiliki satu source-code java, yaitu Person.java. Source-code ini akan dipaket menjadi file distribusi <code class="language-plaintext highlighter-rouge">person-model.jar</code> (binary) dan <code class="language-plaintext highlighter-rouge">person-model-sources.jar</code> (source-code). Pada <a href="http://endy.artivisi.com/blog/java/ant-ivy-4/">artikel selanjutnya</a>, kita akan mempublikasikan distribusi ini agar bisa digunakan oleh modul lain.</p>
Build Management dengan Ivy2008-09-08T16:48:29+07:00https://software.endy.muhardin.com/java/ant-ivy-2<p>Pada <a href="http://endy.artivisi.com/blog/java/ant-ivy-1">posting sebelumnya</a>, saya telah membahas tentang <a href="http://endy.artivisi.com/blog/java/ant-ivy-1">cara instalasi Ivy</a>, dan juga sedikit pengantar tentang apa itu Ivy.</p>
<p>Ivy adalah dependency management tools. Dia mampu menangani dependensi antar modul dalam aplikasi. Tentunya penjelasan ini sangat abstrak. Baiklah mari kita lihat problem apa yang kita hadapi dalam membuat aplikasi, dan bagaimana Ivy menyelesaikan problem tersebut.</p>
<blockquote>
<p>Peringatan : Bukan untuk pemula !!!</p>
</blockquote>
<p>Rangkaian artikel ini diperuntukkan untuk Senior Developer, Team Leader, atau Architect.</p>
<p>Saya asumsikan pembaca sudah mahir menggunakan Ant, Linux, dan memiliki bandwidth yang besar.</p>
<h3 id="studi-kasus">Studi Kasus</h3>
<p>Untuk contoh kasus, mari kita buat aplikasi sederhana dengan Spring MVC 2.5. Aplikasi ini bisa didonlod <a href="http://code.google.com/p/hello-spring-25">di sini</a>.</p>
<p>Aplikasi sederhana ini terdiri dari 3 bagian utama, yaitu:</p>
<ul>
<li>
<p>Domain Model</p>
</li>
<li>
<p>Kode Akses Database (DAO)</p>
</li>
<li>
<p>Tampilan (UI)</p>
</li>
</ul>
<p>Hubungan dependensi antara ketiga bagian ini dapat digambarkan sebagai berikut:</p>
<p><a href="/images/uploads/2008/09/dep-before.png"><img src="/images/uploads/2008/09/dep-before.png" alt=" " /></a></p>
<p>Tanda panah dibaca sebagai “tergantung kepada”. Contohnya, modul DAO tergantung kepada modul Domain Model, sehingga untuk mengkompilasi modul DAO, kita harus punya modul Domain Model. Sebaliknya, untuk mengkompilasi modul Domain Model, kita tidak butuh modul DAO.</p>
<h3 id="dependensi-pembagian-tim-dan-penjadwalan">Dependensi, Pembagian Tim, dan Penjadwalan</h3>
<p>Ketergantungan antar modul ini perlu dipertimbangkan dengan seksama, karena dari desain ketergantungan ini, kita dapat menentukan pembagian tim yang efisien. Idealnya masing-masing tim development dapat bekerja secara paralel dan tidak saling menunggu tim lain selesai.</p>
<p>Dengan skema dependensi seperti di atas, pembagian tugas antar tim kita tidak efisien, karena tim DAO harus menunggu tim DM selesai, baru dia dapat mulai. Demikian juga, tim UI harus menunggu tim DM dan juga tim DAO selesai, baru dia dapat mulai. Ini dapat dilihat di project schedule berikut.</p>
<p><a href="/images/uploads/2008/09/sch-before.png"><img src="/images/uploads/2008/09/sch-before.png" alt=" " /></a></p>
<p>Dengan schedule seperti ini, kita membutuhkan 11 minggu untuk development, karena modul UI dan DAO yang membutuhkan waktu lama harus dikerjakan secara serial.</p>
<p>Agar kita dapat bekerja secara paralel, kita dapat mengatur ulang dependensi sebagai berikut.</p>
<p><a href="/images/uploads/2008/09/dep-after.png"><img src="/images/uploads/2008/09/dep-after.png" alt=" " /></a></p>
<p>Kita menambahkan modul baru, yaitu DAO-API dan DAO-Impl. Modul DAO-API ini berisi interface dari modul DAO, tanpa implementasi. Implementasinya berada di modul DAO-Impl.</p>
<p>Pembagian yang baru ini didasarkan pada waktu pengembangan dari masing-masing modul. Modul DM dan DAO-API bisa dikembangkan dengan cepat, karena hanya berisi struktur data dan deklarasi method saja. Modul UI dan DAO-Impl butuh waktu lama, karena relatif kompleks dan membutuhkan banyak test.</p>
<p>Dengan skema baru, project schedule menjadi seperti ini.</p>
<p><a href="/images/uploads/2008/09/sch-after.png"><img src="/images/uploads/2008/09/sch-after.png" alt=" " /></a></p>
<p>Dengan skema di atas, kita dapat mengalokasikan agar tim DAO dan tim UI bersama-sama mengerjakan modul Domain Model dan DAO-API. Setelah selesai, tim UI dapat mengerjakan modul UI secara paralel dengan tim DAO yang mengerjakan modul DAO-Impl.</p>
<p>Durasi development dapat dikurangi menjadi 7 minggu saja.</p>
<h3 id="masalah-dalam-implementasi">Masalah dalam implementasi</h3>
<p>Ok, kita sudah mendesain dependensi sedemikian rupa, sehingga bisa meminimasi idle time. Berarti kita sudah menjadi Development Team Leader yang canggih … benar??</p>
<p>Belum, yang kita lakukan ini baru setengah jalan. Mengelola tim yang bekerja paralel itu bukan pekerjaan yang mudah. Desain dependensi yang baik memungkinkan tim bekerja paralel. Tapi butuh perangkat tambahan agar mereka bisa berkoordinasi secara efisien.</p>
<p>Masalah terbesar dengan project multi-modul ini adalah bagaimana mengelola perubahan (Change Management). Developer yang berpengalaman pasti sudah tahu bahwa keinginan end-user selalu berubah. Perubahan ini menjadi masalah bila terjadi di modul yang digantungi banyak modul lain.</p>
<p>Contohnya, pada assessment awal, kita sudah mendefinisikan bahwa class Person memiliki tiga property, yaitu id, nama, dan tanggalLahir. Class Person ini kita tempatkan di modul Domain Model, yang digunakan oleh semua modul lain. Katakan saja misalnya kita rilis dengan versi 1.0.</p>
<p>Ternyata peta persaingan bisnis aplikasi contact berubah. Perusahaan pesaing menyediakan aplikasi yang tidak hanya menyimpan tanggal lahir, tapi juga nomer handphone. Tentunya kita harus buru-buru mengupgrade aplikasi (yang belum selesai dikerjakan) agar juga memuat data nomer handphone.</p>
<p>Nah, bagaimana mengelola perubahan ini agar kedua tim yang sedang bekerja (DAO-Impl dan UI) dapat menyesuaikan diri dengan mudah?</p>
<p>Implementasi yang paling sederhana bisa dilakukan dengan USB Flashdisk. Compile saja modul DM, kemudian copy ke flashdisk. Edarkan flashdisk tersebut ke seluruh tim … masalah selesai.</p>
<p>Cara flashdisk, walaupun bisa dilakukan, tapi tidak scalable. Jika dependensinya rumit (misalnya membuat aplikasi ERP), kita harus membuat satu departemen khusus untuk mengedarkan flashdisk.</p>
<p>Nah, inilah gunanya Ivy. Dengan Ivy, kita bisa membuat perubahan di class Person, kemudian menyuruh Ivy untuk mempublikasikannya ke lokasi tertentu dengan versi 1.1. Begitu tim lain melakukan kompilasi, Ivy secara otomatis akan mendeteksi bahwa ada update terbaru di modul Domain Model, mendownload versi terbaru, menghapus versi yang lama, baru melakukan kompilasi.</p>
<p>Ivy dapat mengelola dependensi antar modul dalam internal perusahaan, maupun dependensi dengan pustaka open-source. Contoh aplikasi kita di atas menggunakan pustaka dari <a href="http://www.springframework.org">Spring Framework</a>, <a href="http://www.mysql.org">MySQL</a>, <a href="http://velocity.apache.org">Velocity</a>, dan SiteMesh. Masing-masing pustaka tersebut memiliki dependensi lagi terhadap pustaka lain, misalnya <a href="http://commons.apache.org">Apache Commons</a> dan <a href="http://logging.apache.org/log4j">Log4J</a>.</p>
<p>Dengan menggunakan Ivy, kita hanya perlu mendeklarasikan dependensi langsung, yaitu Spring Framework, MySQL, Velocity, dan SiteMesh. Selanjutnya Ivy akan mencari tahu semua dependensi level kedua terhadap Jakarta Commons dan Log4J. Begitu kita melakukan kompilasi, Ivy akan terlebih dulu mengunduh semua dependensi dari internet, melakukan setting CLASSPATH, baru melakukan kompilasi.</p>
<p>Ivy juga memiliki fitur configuration. Dengan fitur ini, kita bisa membedakan dependensi untuk kompilasi, melakukan test, atau mendeploy aplikasi ke production.</p>
<p>Contohnya, bila kita menggunakan database, kita tidak perlu mendownload *.jar apapun untuk melakukan kompilasi. Pada saat kita test di IDE sendiri, kita gunakan database HSQLDB supaya ringan dan cepat. Untuk test oleh tim tester, kita gunakan database MySQL. Akhirnya, untuk UAT dan production, kita gunakan database Oracle.</p>
<p>Contoh lain, kita bisa mendefinisikan konfigurasi deployment dan delivery. Untuk deployment, kita menginstal aplikasi di tempat client. Tentunya kita hanya butuh *.jar saja. Lain halnya dengan delivery. Selain *.jar, kita juga harus memuat source code, javadoc, manual penggunaan, dan lainnya ke dalam DVD untuk diserah-terimakan dengan client.</p>
<p>Dengan menggunakan fitur configuration Ivy, kita dapat mendefinisikan berbagai kombinasi artifak yang dibutuhkan untuk berbagai situasi.</p>
<p>Pada <a href="http://endy.artivisi.com/blog/java/ant-ivy-3/">artikel selanjutnya</a>, kita akan mulai membuat modul Domain Model. Seluruh modul yang dibuat dalam rangkaian artikel ini bisa dibuat dengan Text Editor biasa. Tidak perlu IDE canggih semacam Netbeans, Eclipse, atau IDEA.</p>
<p>Stay tuned.</p>
Login ssh dengan private key2008-09-05T22:45:03+07:00https://software.endy.muhardin.com/linux/login-ssh-dengan-private-key<p>Masih dalam rangka mengotomasi workflow di ArtiVisi, artikel ini akan membahas tentang cara memindahkan file antar komputer secara aman tapi otomatis.</p>
<p>Ada berbagai cara kita memindahkan file dari satu komputer ke komputer lain, diantaranya:</p>
<ul>
<li>
<p>Menggunakan USB Flashdisk</p>
</li>
<li>
<p>Windows File Sharing</p>
</li>
<li>
<p>NFS</p>
</li>
<li>
<p>FTP atau SFTP</p>
</li>
<li>
<p>SCP</p>
</li>
<li>
<p>dan sebagainya</p>
</li>
</ul>
<p>Cara pertama jelas tidak bisa diotomasi.</p>
<p>Cara kedua sampai keempat mengharuskan kita membuka write access tanpa password di komputer tujuan supaya bisa otomatis. Kalau ada passwordnya kan harus ada seseorang yang mengetik password tersebut. Jadi agak sulit mengotomasinya.</p>
<p>Oleh karena itu, kita akan membahas cara kelima, yaitu SCP. SCP –atau Secure Copy– adalah mekanisme copy file melalui protokol SSH. File yang dikirim terenkripsi. Untuk dapat masuk ke komputer tujuan, kita harus melakukan otentikasi. Jadi ini adalah cara yang relatif aman untuk mengirim file.</p>
<p>Tunggu dulu, katanya mau otomatis, tapi kok harus otentikasi?? Berarti harus ada yang mengetik password dong ….</p>
<p>Tidak juga, makanya saya menggunakan istilah otentikasi, bukan password. SSH dapat melakukan otentikasi dengan password maupun private key. Pada artikel ini, kita akan membahas tentang otentikasi private key.</p>
<p>Artikel ini dibuat menggunakan Ubuntu Hardy dengan openssh sudah terinstal.</p>
<p>Ada beberapa langkah yang harus kita lakukan, yaitu:</p>
<ol>
<li>
<p>Membuat private key di komputer pengirim</p>
</li>
<li>
<p>Mengirim public key ke komputer tujuan</p>
</li>
<li>
<p>Registrasi public key di komputer tujuan</p>
</li>
</ol>
<p>Sebelum mulai, kita tentukan dulu studi kasusnya.</p>
<blockquote>
<p>Di tengah pertarungan dengan Pain, guru genit Jiraiya tiba-tiba berhasil menemukan kelemahan musuhnya. Dia ingin mengirim file berisi informasi tersebut ke sang Hokage seksi, yaitu Tsunade. Rencananya, dia akan login ke server di markas dengan username Tsunade dan menyimpan file tersebut di folder <code class="language-plaintext highlighter-rouge">/home/tsunade/important</code>.</p>
</blockquote>
<p>Tentunya Tsunade tidak akan memberitahukan passwordnya kepada Jiraiya, mengingat tabiatnya yang genit. Sebagai gantinya, dia mendaftarkan public key Jiraiya ke user accountnya di server, sehingga Jiraiya bisa login sebagai Tsunade, tanpa Tsunade harus memberitahukan passwordnya kepada Jiraiya.</p>
<p>Pada cerita di atas, kita punya dua komputer, yaitu laptopnya Jiraiya yang dibawanya kemanapun pergi, dan server di markas. Kita juga punya dua user, yaitu Jiraiya dan Tsunade.</p>
<p>Agar bisa melakukan public key authentication, tentunya hal pertama yang harus dilakukan adalah membuat public key.</p>
<h3 id="membuat-public-key">Membuat public key</h3>
<p>Jiraiya harus membuat public key di laptopnya sendiri. Public key selalu berpasangan dengan private key. Untuk membuat public key, Jiraiya mengetik perintah berikut di terminal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen
</code></pre></div></div>
<p>Perintah tersebut akan menanyakan password untuk membuka private key. Jiraiya memilih tidak memberikan password. Sebetulnya ini kurang aman, karena kalau ada orang lain yang berhasil mendapatkan file private key, dia bisa login ke server di markas tanpa hambatan.</p>
<p>Tapi mau bagaimana lagi, hidup sebagai ninja sangat berbahaya. Kita tidak tahu apakah setelah pertarungan kita masih punya tangan untuk mengetik password.</p>
<p>Tekan enter beberapa kali sampai selesai. Berikut output yang dihasilkan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Generating public/private rsa key pair.
Enter file in which to save the key (/home/jiraiya/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/jiraiya/.ssh/id_rsa.
Your public key has been saved in /home/jiraiya/.ssh/id_rsa.pub.
The key fingerprint is:
6f:7c:e2:52:f1:14:5d:2c:7f:3a:53:5e:fe:7f:98:9c jiraiya@laptop
</code></pre></div></div>
<p>Selanjutnya, kita harus kirimkan file public key ke Tsunade agar didaftarkan di accountnya. File public key ini bisa ditemukan di folder <code class="language-plaintext highlighter-rouge">/home/jiraiya/.ssh/</code> dengan nama <code class="language-plaintext highlighter-rouge">id_rsa.pub</code>. Public key bisa dikirim melalui media apapun, misalnya email, usb flashdisk, burung hantu, maupun burung merpati.</p>
<h3 id="registrasi-public-key">Registrasi Public Key</h3>
<p>Singkat kata, Tsunade telah menerima public key dari Jiraiya. Dia harus mendaftarkannya di accountnya agar Jiraiya bisa login.</p>
<p>Tsunade harus login dulu ke server dan menaruh file public key tersebut di sana. Misalnya dia letakkan di folder <code class="language-plaintext highlighter-rouge">/home/tsunade/</code> dan nama filenya diubah menjadi <code class="language-plaintext highlighter-rouge">jiraiya_id_rsa.pub</code>.</p>
<p>Untuk meregistrasinya, Tsunade menggunakan perintah berikut di terminal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat /home/tsunade/jiraiya_id_rsa.pub >> /home/tsunade/.ssh/authorized_keys
</code></pre></div></div>
<p>Setelah itu, public key tidak lagi diperlukan, sehingga bisa dihapus.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm /home/tsunade/jiraiya_id_pub
</code></pre></div></div>
<p>Selesai sudah konfigurasi di server, sekarang kita kembali ke pertarungan Jiraiya vs Pain.</p>
<h3 id="login-dengan-public-key">Login dengan public key</h3>
<blockquote>
<p>Sesaat sebelum Jiraiya menekan tombol Send di Mozilla Thunderbird, Pain mendaratkan serangan mematikan ke Jiraiya, sehingga ybs tidak dapat menggerakkan tangannya. Jiraiya membatin, “Untung saja saya sudah setup public key di server dengan account Tsunade. Baiklah, mari kita scp saja.”</p>
</blockquote>
<p>Untuk mengirim file tersebut melalui scp dengan public key, berikut adalah perintahnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp -i /path/menuju/private/key username@komputer-tujuan:folder-tujuan
</code></pre></div></div>
<p>Jadi, untuk mengirim file <code class="language-plaintext highlighter-rouge">pain-secret.txt</code> ke server dengan username tsunade, Jiraiya menggunakan perintah berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp -i /home/jiraiya/.ssh/id_rsa pain-secret.txt tsunade@server:/home/tsunade/important/
</code></pre></div></div>
<p>File akan segera terkirim tanpa harus mengetik password.</p>
<p>Lebih lanjut tentang ssh dengan public/private key bisa dibaca <a href="http://www.debuntu.org/ssh-key-based-authentication">di sini</a>.</p>
<p>Lebih lanjut tentang nasib Jiraiya bisa dibaca <a href="http://narutochaos.com/download/10/429">di sini</a>.</p>
<p>Selamat mencoba</p>
Backup Trac2008-09-04T19:41:14+07:00https://software.endy.muhardin.com/linux/backup-trac<p><a href="http://endy.artivisi.com/blog/linux/instalasi-trac">Trac sudah terinstal</a>, sekarang harus diamankan melalui prosedur backup.</p>
<p>Seperti biasa, semua prosedur backup harus otomatis dan terjadwal. Kalau tidak begitu, pasti tidak akan dijalankan. Jadi, selain kegiatan memburn CD, kegiatan backup lainnya harus diotomasi.</p>
<p>Di website Trac sudah dijelaskan tentang <a href="http://trac.edgewall.org/wiki/TracBackup">cara melakukan backup terhadap instalasi Trac</a>. Kita hanya perlu membuat script sedikit supaya bisa membackup seluruh project dalam folder parent trac, dan mengkompresnya.</p>
<p>Berikut scriptnya, seperti biasa, menggunakan Ruby.</p>
<p>Script ini diadaptasi dari <a href="http://endy.artivisi.com/blog/aplikasi/svn-parentpath-backup/">script sebelumnya untuk backup Subversion repo</a>.</p>
<p>Berikut scriptnya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require 'zlib'
require 'fileutils'
if ARGV.length < 2
puts "Usage : ruby trac-backup.rb <Trac Parent folder> <backupfolder>"
exit
end
# some configuration
trac_parent_path = ARGV[0]
backup_folder = ARGV[1]
# variable initialization
current_date = Time.now.strftime("%Y%m%d")
Dir.foreach(trac_parent_path) { |trac|
next if('.' == trac || '..' == trac)
puts "Start to process folder : "+trac
puts "Performing trac hotcopy"
project_name = trac_parent_path + File::SEPARATOR + trac
dumpfile_folder = trac + '-' +current_date
dumpfile = backup_folder + File::SEPARATOR + dumpfile_folder
`trac-admin #{project_name} hotcopy #{dumpfile}`
puts "Compressing dumpfile"
`tar czf #{backup_folder}/#{dumpfile_folder}.tar.gz -C #{backup_folder} #{dumpfile_folder}`
puts "Deleting uncompressed backup"
FileUtils.rm_r dumpfile
}
</code></pre></div></div>
<p>Save dengan nama <code class="language-plaintext highlighter-rouge">trac-backup.rb</code>.</p>
<p>Warning, kode di atas tidak bisa dijalankan di Windows, karena menggunakan command line tar untuk mengkompres file. Jika Anda ingin menggunakan di Windows, silahkan gunakan TugZIP seperti <a href="http://endy.artivisi.com/blog/aplikasi/subversion-backup-script-untuk-windows/">contoh ini</a>.</p>
<p>Untuk menjalankannya, cukup panggil dari command prompt.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby trac-backup.rb /var/lib/trac /folder/tempat/backup
</code></pre></div></div>
<p>Supaya fully-automated, daftarkan di cron. Buka editor crontab.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>crontab -e
</code></pre></div></div>
<p>Kemudian ketikkan entri sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 23 * * 5 /usr/bin/ruby /full/path/ke/trac-backup.rb /var/lib/trac /full/path/ke/folder/backup
</code></pre></div></div>
<p>Backup akan dijalankan setiap jam 23.00 pada hari Jumat. Lebih lanjut tentang cron bisa <a href="http://bandung.linux.or.id/node/24">dibaca di sini</a>. Jika bingung dengan formatnya, bisa gunakan <a href="http://www.htmlbasix.com/crontab.shtml">generator crontab online</a>.</p>
Instalasi Trac2008-09-03T23:54:21+07:00https://software.endy.muhardin.com/linux/instalasi-trac<p>Mulai beberapa minggu ini, ArtiVisi kebanjiran project. Ini tentu harus dikelola dengan baik. Semua orang harus bekerja se-efisien mungkin. Menjadi tugas saya sebagai manager untuk memudahkan orang-orang bekerja.</p>
<p>Opa Abe Lincoln pernah bilang gini, jauh sebelum mas Barry masuk sekolah di SD Besuki, Menteng.</p>
<blockquote>
<p>If I had eight hours to chop down a tree, I’d spend six hours sharpening my axe</p>
</blockquote>
<p>So be it ….</p>
<p>Hal pertama yang harus dilakukan sebelum mulai bekerja adalah mempersiapkan environment. Ada beberapa tools yang dapat digunakan untuk mempercepat kegiatan software development, terutama di Java. Ini pernah saya bahas di <a href="http://endy.artivisi.com/blog/manajemen/starter-kit/">artikel ini</a>.</p>
<p><a href="http://endy.artivisi.com/blog/aplikasi/instalasi-subversion/">Subversion sudah terinstal</a> sejak hari pertama ArtiVisi buka warung. Bahkan sebelum websitenya jadi, repository sudah siap digunakan dan <a href="http://endy.artivisi.com/blog/aplikasi/svn-parentpath-backup/">diamankan melalui prosedur backup</a>. Ant-Ivy juga sudah terinstal dan terkonfigurasi. Tinggal menunggu terisi dengan *.jar external dari <a href="http://www.ibiblio.org/maven/">repository ibiblio</a> dan <a href="http://www.springsource.com/repository/">repository springsource</a>.</p>
<p>Hal berikutnya adalah mempersiapkan bug tracker dan project management tools. Karena saya menggunakan Eclipse, maka kriteria pemilihan bug tracker tentu saja adalah kompatibilitasnya dengan <a href="http://www.eclipse.org/mylyn/">Mylyn</a>. <a href="http://www.bugzilla.org/">Bugzilla</a> cuma bisa untuk bug-tracker, jadi saya pilih saja <a href="http://trac.edgewall.org/">Trac</a>.</p>
<p>Berikut langkah instalasi di Ubuntu Gutsy Server.</p>
<h3 id="instalasi-trac">Instalasi Trac</h3>
<p>Instalasi Trac tidak sulit, cukup satu baris perintah saja.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install trac libapache2-mod-python python-setuptools
</code></pre></div></div>
<p>Selanjutnya, kita konfigurasi VirtualHost Apache. Saya buatkan satu subdomain khusus yang namanya trac.artivisi.com. Jadi, kita buat file <code class="language-plaintext highlighter-rouge">/etc/apache2/sites-available/trac.artivisi.com</code>.</p>
<p>Subdomain ini nantinya akan menampung data Trac untuk banyak project. Jadi, kita harus mengkonfigurasi parent path yang menyatakan folder tempat data trac untuk masing-masing project disimpan.</p>
<p>Berikut isi filenya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <Location /trac>
SetHandler mod_python
PythonHandler trac.web.modpython_frontend
PythonOption TracEnvParentDir /var/lib/trac
PythonOption TracUriRoot /trac
</Location>
</code></pre></div></div>
<p>Selanjutnya, kita konfigurasi otentikasinya supaya mengacu ke LDAP tempat menyimpan username dan password Subversion. Jadi username dan password untuk svn commit sama dengan untuk login ke Trac.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <LocationMatch "/trac/[^/]+/login">
AuthType Basic
AuthName "ArtiVisi Trac Server"
AuthBasicProvider ldap
AuthLDAPURL url-ldap-server
AuthLDAPBindDN dn-untuk-apache-login-di-ldap
AuthLDAPBindPassword password-dn-tersebut
AuthzLDAPAuthoritative off
Require valid-user
</LocationMatch>
</code></pre></div></div>
<p>Sekarang konfigurasi Apache sudah selesai. Jangan lupa me-restart apache.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /etc/init.d/apache2/restart
</code></pre></div></div>
<p>Berikutnya, membuat project baru.</p>
<h3 id="membuat-project">Membuat Project</h3>
<p>Misalnya kita ingin membuat project bernama hello-world. Sebagai root, masuk ke folder /var/lib/trac. Kemudian buat projectnya. Jangan lupa tambahkan user administratornya sekaligus.Terakhir, ganti kepemilikan folder sesuai dengan user yang digunakan proses webserver.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo -i
cd /var/lib/trac
trac-admin hello-world initenv
trac-admin hello-world permission add endy TRAC_ADMIN
chown -R www-data.www-data hello-world
</code></pre></div></div>
<p>Langkah di atas akan menginisialisasi Trac dengan satu username –yaitu endy– sebagai administrator. Selanjutnya, kita edit konfigurasinya agar setiap perubahan bug/task akan mengirim notifikasi ke email. Edit file /var/lib/trac/hello-world/conf/trac.ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtp_enabled = true
smtp_default_domain = artivisi.com
smtp_from_name = ArtiVisi Trac Server
smtp_from = artivisi.dev@gmail.com
smtp_replyto = artivisi.dev@gmail.com
smtp_server = smtp.gmail.com
smtp_port = 587
smtp_user = artivisi.dev
smtp_password = rahasia_dong
use_tls = true
</code></pre></div></div>
<p>Pada konfigurasi di atas, kita menggunakan Gmail yang gratis dan mudah. Kita buat account di Gmail bernama artivisi.dev dan kita berikan password rahasia_dong.</p>
<p>Selesai sudah. Sekarang silahkan browse ke URLnya.</p>
<h3 id="plugin-webadmin">Plugin WebAdmin</h3>
<p>Sebagai tambahan, kita bisa menambahkan plugin webadmin supaya kita bisa menambah user dan mengatur permission melalui interface web.</p>
<p>Ubuntu secara default menginstal Trac versi 0.10, jadi kita harus menginstal plugin WebAdmin secara manual. Di versi 0.11 Trac sudah menyertakan WebAdmin secara default.</p>
<p>Instalasi dijalankan langsung dari subversion repository.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo easy_install http://svn.edgewall.org/repos/trac/sandbox/webadmin/
</code></pre></div></div>
<p>Untuk menjalankan plugin, kita perlu membuat file <code class="language-plaintext highlighter-rouge">/etc/trac/trac.ini</code>. Berikut isinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[components]
webadmin.* = enabled
</code></pre></div></div>
<p>Voila, nanti akan muncul tombol admin setelah kita login.</p>
Instalasi Ant Ivy2008-09-03T18:54:15+07:00https://software.endy.muhardin.com/java/ant-ivy-1<p><a href="http://ant.apache.org">Ant</a>, adalah tools untuk mengotomasi kegiatan build di Java. Seperti kita tahu, dari mulai source code diketik sampai bisa digunakan user, ada beberapa kegiatan yang harus dilakukan, misalnya:</p>
<ol>
<li>
<p>Bersihkan sisa-sisa kompilasi terdahulu</p>
</li>
<li>
<p>Setting CLASSPATH</p>
</li>
<li>
<p>Kompilasi</p>
</li>
<li>
<p>Masukkan artifak lain ke CLASSPATH sesuai kebutuhan (*.properties, *.xml)</p>
</li>
<li>
<p>Jalankan automated test</p>
</li>
<li>
<p>Kompilasi Javadoc</p>
</li>
<li>
<p>Paket menjadi *jar atau *war</p>
</li>
<li>
<p>Deploy ke server (bila perlu)</p>
</li>
</ol>
<p>Rangkaian kegiatan ini disebut dengan proses build.</p>
<p>Jika kegiatan ini dilakukan secara manual, tentu saja masa muda kita akan habis untuk melakukan build dan tidak akan sempat menulis kode program.</p>
<p>Kita bisa memudahkan proses build dengan bantuan IDE. Tinggal klik Clean and Build, langsung beres. Tapi kita tahu bahwa masing-masing orang memiliki IDE favorit masing-masing. Jadi langkah-langkah build bisa jadi berbeda di masing-masing IDE. Selain itu, build dengan IDE tidak otomatis. Harus ada seseorang yang menekan tombol Clean and Build. Tentu saja hal ini mencegah kita untuk melakukan otomasi penuh semacam Continuous Integration.</p>
<p>Ada dua tools yang biasa digunakan untuk build, yaitu Ant dan <a href="http://maven.apache.org">Maven</a>. Maven memiliki kelebihan dibanding Ant, yaitu dia:</p>
<ul>
<li>
<p>mampu mengelola dependensi</p>
</li>
<li>
<p>membuatkan website</p>
</li>
<li>
<p>melakukan kegiatan lain yang fancy, seperti menjalankan webserver (mvn:jetty), unit test, dan lain sebagainya</p>
</li>
</ul>
<p>dan fitur-fitur lain yang bisa dilihat <a href="http://maven.apache.org/what-is-maven.html">di sini</a> dan <a href="http://maven.apache.org/maven-features.html">di sini</a>.</p>
<p>Maven sangat powerful, tapi seperti sudah sering saya katakan,</p>
<blockquote>
<p>with great power, comes great complexity</p>
</blockquote>
<p>Maven relatif sulit dipelajari dan <a href="http://bluxte.net/blog/2006-04/27-06-50.html">banyak mengandung black-magic</a> (baca: banyak mengandung undocumented behavior). Selain itu, Maven juga <a href="http://www.bearaway.org/wp/?p=518">tidak reliable</a>, dibuktikan dengan <a href="http://svn.apache.org/repos/asf/cocoon/trunk/README.txt">dokumentasi cara melakukan build untuk salah satu project open source terkenal Cocoon</a>.</p>
<p>Dengan berbagai plus-minus di atas, akhirnya saya memutuskan untuk menggunakan Ant ditambah dengan <a href="http://ant.apache.org/ivy">Ivy</a> saja. Toh sebenarnya kami di ArtiVisi cuma butuh dependency management saja. Ivy lebih mudah dipelajari, dan hei … <a href="http://www.springsource.com/beta/applicationplatform">contoh aplikasi SpringSource Application Platform (SSAP)</a> di-build menggunakan Ivy.</p>
<p>Sekedar informasi, contoh aplikasi Petclinic SSAP cukup kompleks. Aplikasi tersebut memiliki banyak konfigurasi yang dapat dipilih, antara lain:</p>
<ul>
<li>
<p>Framework akses database : JDBC, JPA, Hibernate, atau Eclipselink</p>
</li>
<li>
<p>Database provider : HSQLDB atau MySQL</p>
</li>
</ul>
<p>Dengan banyak kombinasi tersebut, pengelolaan dependency dan proses build menjadi rumit. Domain modelnya saja digunakan oleh banyak modul lain. Belum lagi dependency terhadap pustaka external seperti Hibernate dan Eclipselink. Akan ada banyak konfigurasi untuk compile, test, dan deployment.</p>
<p>Aplikasi contoh tersebut sudah mencerminkan kemampuan Ivy untuk mengelola project dengan banyak relasi ke project lainnya. Dan faktor yang paling penting, <strong>Ivy mudah dipelajari</strong>. Kita akan buktikan dalam beberapa posting berikutnya.</p>
<p>Lebih jauh tentang dependency management akan dibahas pada posting selanjutnya tentang Ivy. Untuk sekarang, kita akan bahas cara instalasinya.</p>
<h3 id="instalasi-ant">Instalasi Ant</h3>
<p>Pertama, kita harus menginstal Ant dulu. Karena saya menggunakan Ubuntu, instalasi tidak terlalu sulit. Cukup ketikkan perintah berikut di command prompt.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install ant ant-optional
</code></pre></div></div>
<p>Atau jika Anda alergi dengan command prompt, bisa menggunakan System > Administration > Synaptic Package Manager.</p>
<p>Selain cara otomatis seperti di atas, kita juga bisa menginstal secara manual. Caranya, <a href="http://ant.apache.org/bindownload.cgi">donlod Ant</a>, kemudian extract. Masukkan path menuju folder bin di dalam hasil extract ke dalam environment variable PATH.</p>
<p>Kemudian, test instalasi dengan mengetik perintah <code class="language-plaintext highlighter-rouge">ant -v</code> di command prompt. Berikut hasilnya di komputer saya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>endy@kintoun:~$ ant -v
Apache Ant version 1.7.0 compiled on August 29 2007
Buildfile: build.xml does not exist!
Build failed
</code></pre></div></div>
<p>Baiklah, kita sudah mendapatkan Ant versi 1.7.0. Selanjutnya, instalasi Ivy.</p>
<h3 id="instalasi-ivy">Instalasi Ivy</h3>
<p>Pertama, tentunya <a href="http://ant.apache.org/ivy/download.html">download dulu Ivy-nya</a>. Pada saat artikel ini ditulis, versi terbaru adalah 2.0.0-beta2. Jangan khawatir dengan status beta, karena menurut pengalaman saya, versi ini cukup stabil.</p>
<p>Setelah donlod, kemudian extract. Kita membutuhkan file berikut :</p>
<ul>
<li>
<p>ivy-2.0.0-beta2.jar</p>
</li>
<li>
<p>ivy-core-2.0.0-beta2</p>
</li>
</ul>
<p>dan satu file lagi bila kita ingin mengakses repository melalui ssh.</p>
<ul>
<li>lib/jsch-0.1.25.jar</li>
</ul>
<p>Masukkan file tersebut ke lokasi instalasi Ant, dalam folder lib. Bila Anda menggunakan Ubuntu seperti saya, dan menginstal Ant menggunakan Synaptic, apt-get, atau aptitude, maka lokasi instalasi Ant ada di</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/share/ant
</code></pre></div></div>
<p>Instalasi Ivy selesai.</p>
<p>Dengan menggunakan Ivy, kita dapat mengelola proyek raksasa (terdiri dari banyak modul, dikerjakan oleh puluhan tim) dengan lebih mudah.</p>
<p>Bagaimana cara menggunakannya? Tunggu posting selanjutnya :D
*[IDE]: Integrated Development Environment</p>
KlikBCA Bisnis di Ubuntu2008-08-27T20:12:48+07:00https://software.endy.muhardin.com/lain/klikbca-bisnis-di-ubuntu<p>Setelah menjadi pengusaha, KlikBCA saya berganti, dari Individual menjadi Bisnis. Hmm … tentunya KlikBCA ini juga harus bisa diakses via Linux.</p>
<p>Untuk mengakses KlikBCA Bisnis, kita harus dial up VPN dulu. BCA menyediakan installer untuk VPN dialer versi Windows.</p>
<p>Google punya google, dapat link <a href="http://blogindra.sanjaya.org/2007/01/menggunakan-klikbca-bisnis-di-ubuntu.html">ini</a> dan <a href="http://linux.or.id/node/1418">ini</a>. Tapi keduanya ternyata tidak menyelesaikan masalah. Ada beberapa hal yang kurang jelas. Mudah-mudahan posting ini bisa menjelaskan semuanya sehingga kita semua bisa berbisnis dengan BCA menggunakan Ubuntu Linux.</p>
<p>Pertama, kita tetap butuh komputer Windows. Di komputer Windows ini, kita instal aplikasi KlikBCA Dialer. Tujuan utamanya adalah mendapatkan informasi koneksi VPN yang sebagian ada dalam file <code class="language-plaintext highlighter-rouge">KlikBCA Bisnis.pcf</code>. File ini bisa kita dapatkan di CD Installer, tapi ternyata formatnya berbeda dengan yang sudah terinstal. So, instal dulu dialernya, kemudian buka file <code class="language-plaintext highlighter-rouge">KlikBCA Bisnis.pcf</code> dengan text editor.</p>
<p>Kita butuh beberapa informasi dari file ini, yaitu:</p>
<ol>
<li>
<p>Host : Ini adalah nama komputer VPN Server</p>
</li>
<li>
<p>GroupName</p>
</li>
</ol>
<p>Kedua, kita butuh aplikasi kecil untuk mendekripsi field <code class="language-plaintext highlighter-rouge">enc_GroupPwd</code> dalam file <code class="language-plaintext highlighter-rouge">KlikBCA Bisnis.pcf</code>. Aplikasi tersebut bisa <a href="http://newgre.net/passwordrevealer">diunduh di sini</a>. Kopikan ke komputer Windows yang sudah terinstal dialer, dan jalankan. Nanti kita akan ditanya Profile mana yang mau didekripsi. Pilih saja <code class="language-plaintext highlighter-rouge">KlikBCA Bisnis.pcf</code>. Nanti dia akan menampilkan Group Password yang sudah terdekripsi.</p>
<p>Selesai dengan Windows, silahkan diformat menjadi Ubuntu.</p>
<p>Ketiga, instal dulu VPN Client dan interface Network Managernya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install vpnc network-manager-vpnc
</code></pre></div></div>
<p>Keempat, kita konfigurasi Network Manager Applet. Network Manager Applet adalah icon kecil di taskbar atas, sebelah kanan. Berikut contohnya.</p>
<p><a href="/images/uploads/2008/08/netman-applet.png"><img src="/images/uploads/2008/08/netman-applet.png" alt=" " /></a>
<em>Gambar diambil dari <a href="http://www.gnome.org/projects/NetworkManager/">sini</a></em>.</p>
<p>Pilih VPN Connections - Configure VPN. Nanti akan tampil daftar jaringan VPN yang sudah ada. Karena kita belum mengkonfigurasi, tentunya ini masih kosong.</p>
<p><a href="/images/uploads/2008/08/add-vpn.png"><img src="/images/uploads/2008/08/add-vpn.png" alt=" " /></a></p>
<p>Selanjutnya, klik Add untuk menambah jaringan VPN baru. Wizardnya akan muncul.</p>
<p><a href="/images/uploads/2008/08/01-create-vpn.png"><img src="/images/uploads/2008/08/01-create-vpn.png" alt=" " /></a></p>
<p>Kita akan disajikan pilihan VPN client yang tersedia. Pilih Cisco compatible.</p>
<p><a href="/images/uploads/2008/08/02-choose-protocol.png"><img src="/images/uploads/2008/08/02-choose-protocol.png" alt=" " /></a></p>
<p>Masukkan data-data sesuai dengan isi file <code class="language-plaintext highlighter-rouge">KlikBCA Bisnis.pcf</code>.</p>
<p><a href="/images/uploads/2008/08/02a-gateway.png"><img src="/images/uploads/2008/08/02a-gateway.png" alt=" " /></a></p>
<p>Dalam tab pertama, isikan:</p>
<ul>
<li>
<p>Gateway : Sesuai isi field Host</p>
</li>
<li>
<p>Group Name : Sesuai isi field GroupName</p>
</li>
</ul>
<p>Klik tab Optional, lalu centang Override user name.
<a href="/images/uploads/2008/08/02b-username.png"><img src="/images/uploads/2008/08/02b-username.png" alt=" " /></a></p>
<p>Isikan Corporate Id + User Id yang diberikan BCA pada Anda. Kedua ID digabungkan tanpa spasi dan diisikan di textfield Override user name.</p>
<p>Selesai konfigurasi, sekarang jika ingin connect, klik pada network manager applet : VPN Connections - KlikBCA Bisnis. Nanti akan muncul pertanyaan password dan group password.</p>
<p><a href="/images/uploads/2008/08/04-authenticate-vpn.png"><img src="/images/uploads/2008/08/04-authenticate-vpn.png" alt=" " /></a></p>
<p>Password diisi dengan respon dari KeyBCA APPLI 1. Group password diisi dengan hasil dekripsi Group Password. Group password boleh disimpan di keyring, karena nilainya tidak berubah-ubah. Tapi password tidak perlu disimpan, karena nilainya berubah sesuai KeyBCA APPLI 1.</p>
<p>Kalau semua nilai yang diisikan benar, maka setelah menunggu sepeminuman teh, Anda akan segera terhubung ke VPN BCA. Ini ditandai dengan adanya tanda gembok pada network manager applet.</p>
<p>Untuk menyudahi koneksi VPN, bila Anda sudah selesai menggunakan KlikBCA Bisnis, klik lagi network manager applet : VPN Connections - Disconnect VPN.</p>
<p>Beberapa linux user belum merasa macho kalau tidak mengedit text file. No problem, jika ingin connect via command line, begini caranya.</p>
<p>Buat file <code class="language-plaintext highlighter-rouge">/etc/vpnc/default.conf</code>. Isinya sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IPSec gateway <masukkan isi field Host>
IPSec ID <masukkan isi field GroupName>
IPSec secret <masukkan hasil dekripsi Group Password>
Xauth username <masukkan CorporateID digabung dengan UserID>
</code></pre></div></div>
<p>Selesai konfigurasi. Sekarang dial dengan cara menjalankan:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo vpnc-connect
</code></pre></div></div>
<p>Nanti dia akan minta password. Masukkan respon APPLI 1 KeyBCA.</p>
<p>Untuk mengakhiri koneksi VPN, jalankan:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo vpnc-disconnect
</code></pre></div></div>
<p>Selamat mencoba.</p>
Menggunakan Log4J2008-07-19T14:21:26+07:00https://software.endy.muhardin.com/java/menggunakan-log4j<p>Menggunakan log4j</p>
<p>Pada waktu SMU dulu, saya dan teman-teman saya membentuk grup band. Sebagai band amatiran, kami hanya tampil kalau ada pentas seni di sekolah, atau kalau ada acara tujuh-belasan di lapangan dekat rumah. Yang ingin tampil di event tersebut tidak hanya band kami, tapi juga puluhan band amatir lainnya. Karena demand jauh melebihi supply, seringkali kami justru harus membayar uang pendaftaran agar bisa tampil.</p>
<p>Sama dengan band remaja pada umumnya, kami punya impian menjadi rock-star. Tidak usah mengeluarkan album dulu lah … setidaknya kami dibayar setiap kali tampil. Bukannya malah membayar.</p>
<p>Suatu ketika, pemain bas kami mengajak kami berkenalan dengan salah satu sepupunya yang juga punya band. Tidak seperti kami yang amatiran, band sepupu ini lebih profesional. Mereka sudah punya slot tampil rutin di beberapa kafe, yang tentu saja … dibayar :D</p>
<p>Setelah ngobrol kesana-sini, akhirnya kami diajak untuk datang ke kafe dan menonton penampilan mereka.</p>
<p>Sesampainya di lokasi, terlihat jelas perbedaan level antara kami dan mereka. Band kami, sebelum tampil, semua anggotanya deg-degan dan gugup. Topik pembicaraan adalah tentang lagu yang dimainkan. Kami saling mengingatkan berapa kali harus mengulang refrain, berapa bar sesi gitar solo, pada ketukan berapa vokalis harus masuk, dan hal-hal sejenisnya. Dan tentunya, orang gemetaran tidak mungkin bercanda.</p>
<p>Berbeda keadaannya dengan band sepupu tersebut. Begitu datang, mereka berhaha-hihi dengan gembira. Pada waktu kami mendekat, terdengar jelas bahwa topik pembicaraan bukanlah tentang teknis musik yang akan dimainkan, melainkan urusan ….. kostum !!!</p>
<p>Mereka sudah sedemikian terbiasa tampil di panggung, sehingga bermusik sudah seperti bernafas. Tidak perlu berpikir lagi. Oleh karena itu mereka bisa dengan tenang memikirkan kostum.</p>
<p>Lalu apa hubungannya band kafe dengan <a href="http://logging.apache.org/log4j">Log4J</a>?</p>
<p>Programmer pemula akan sangat concern dengan urusan algoritma, design pattern, normalisasi database, dan hal-hal teknis pemrograman. Programmer kawakan, sudah tidak lagi mengkhawatirkan hal-hal tersebut. Bukan karena tidak penting, tapi karena hal teknis tersebut sudah menjadi bagian dari dirinya. Coding sudah seperti bernafas. No mind .. demikian menurut Tom Cruise dalam film The Last Samurai.</p>
<p>Lalu apa yang dipikirkan programmer kawakan? Dia memikirkan pengguna aplikasinya. Bukan end-user atau operator, karena itu juga sudah refleks terpikirkan. Melainkan system administrator, first level technical support, dan maintainer programmer (programmer generasi kedua yang akan menambahkan fitur atau melakukan troubleshoot).</p>
<p>Untuk kepentingan tersebut, maka yang harus dipikirkan adalah log message dan exception handling. Log message sangat dibutuhkan oleh sysdamin untuk mendeteksi masalah sedini mungkin. First level technical support juga harus membuat keputusan apakah masalah bisa diselesaikan melalui restart, atau butuh coding tambahan?</p>
<p>Nah, untuk itu kita harus mempertimbangkan :</p>
<ol>
<li>
<p>Di titik mana harus menaruh log</p>
</li>
<li>
<p>Informasi apa yang harus ditulis di log</p>
</li>
<li>
<p>Di mana harus menulis log: konsol, file, database, syslog, Event Viewer?</p>
</li>
<li>
<p>Bagaimana harus memformat log message agar mudah ditelusuri?</p>
</li>
<li>
<p>Bagaimana memfilter log agar menampilkan hanya severity tertentu?</p>
</li>
<li>
<p>Bagaimana memfilter log agar hanya meliput modul tertentu?</p>
</li>
</ol>
<p>Beruntung bagi programmer Java, kita hanya perlu memikirkan nomer 1 dan 2. Sisanya sudah disediakan oleh logging framework seperti <a href="http://logging.apache.org/log4j">log4j</a>.</p>
<p>Lalu bagaimana cara menggunakan log4j? Pelajari di <a href="http://endy.muhardin.googlepages.com/minibook-log4j.pdf">Minibook Log4J</a>.</p>
Road to Java SE2008-07-13T17:56:34+07:00https://software.endy.muhardin.com/java/road-to-java-se<p>Belajar bahasa pemrograman mirip dengan belajar bahasa manusia. Bahasa pertama adalah yang paling sulit. Ini disebabkan karena kita belum paham istilah-istilah yang digunakan, pola umum menyusun kata, dan ungkapan-ungkapan khusus yang biasa digunakan dalam bahasa tertentu.</p>
<p>Mari kita ingat waktu pertama kali belajar bahasa Inggris (bahasa Indonesia tidak dihitung, karena kita sudah bisa dari kecil). Pertama kali, kita sama sekali tidak tahu vocabulary. Kemudian kita belajar tentang susunan kata, Subjek-Predikat-Objek. Belajar kalimat aktif dan pasif.</p>
<p>Demikian juga dengan bahasa pemrograman. Bahasa pertama selalu paling sulit. Setelah kita menguasai satu bahasa, belajar bahasa berikutnya tidak sesulit yang pertama. Misalnya kita butuh waktu enam bulan untuk menguasai Java, maka untuk belajar PHP mungkin cuma butuh 2 bulan.</p>
<p>Dengan asumsi kita sama sekali belum pernah mengenal bahasa pemrograman, berikut adalah peta jalan untuk belajar bahasa pemrograman Java.</p>
<p>Pertama, kita kumpulkan dulu perlengkapan belajarnya. Kita membutuhkan:</p>
<ul>
<li>
<p>Java SDK</p>
</li>
<li>
<p>Java Tutorial dari Sun</p>
</li>
<li>
<p>Javadoc API</p>
</li>
<li>
<p>Text Editor yang bisa memberi warna (syntax highlighting)</p>
</li>
</ul>
<p>Pada tahap ini, jangan menggunakan editor canggih –biasa disebut Integrated Development Environment (IDE)– macam Eclipse, Netbeans, dan lainnya. IDE memiliki fitur yang canggih dan juga kompleks. Di awal masa belajar, kita ingin belajar tentang bahasa Java, bukan penggunaan tombol dan wizard.</p>
<p>Setelah itu, instal Java SDK. Tergantung metode instalasi Anda, mungkin Anda perlu mengatur environment variabel JAVA_HOME dan PATH. JAVA_HOME mengarah ke folder tempat instalasi Java SDK, sedangkan PATH mengarah pada folder bin di dalam lokasi JAVA_HOME.</p>
<p>Untuk mengetes apakah instalasi Anda sudah berhasil, coba ketik perintah berikut di command prompt:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>javac -version
</code></pre></div></div>
<p>Bila keluar pesan error “perintah javac tidak ditemukan” berarti environment variabel masih belum diatur dengan benar.</p>
<p>Perhatikan bahwa setelah mengatur environment variabel, kita harus merestart command prompt. Command prompt baik di Windows maupun Linux tidak dapat mendeteksi perubahan environment variabel secara instan.</p>
<p>Selanjutnya, buat kode program sederhana. Dalam semua bahasa pemrograman, kode ini disebut HelloWorld. Kode program ini hanya mencetak tulisan <strong>Hello World</strong> ke layar komputer.</p>
<p>Coba kompilasi dan jalankan program HelloWorld tersebut. Dari sini Anda akan mengetahui beberapa konsep, yaitu:</p>
<ul>
<li>
<p>Cara kompilasi kode program Java</p>
</li>
<li>
<p>Cara menjalankan aplikasi Java</p>
</li>
<li>
<p>Konsep CLASSPATH</p>
</li>
</ul>
<p>Begitu kita sudah bisa mengkompilasi dan menjalankan kode program Java, kita bisa mulai belajar prinsip umum pemrograman. Prinsip ini sama di semua bahasa pemrograman, walaupun cara penulisannya berbeda. Berikut prinsip-prinsip yang perlu dipelajari:</p>
<ol>
<li>
<p>Anatomi aplikasi. Di Java, aplikasi terdiri dari banyak package, satu package berisi banyak class, satu class berisi banyak property dan method, satu method terdiri dari banyak statement.</p>
</li>
<li>
<p>Menggunakan variabel</p>
</li>
<li>
<p>Macam-macam tipe data</p>
</li>
<li>
<p>Operator</p>
</li>
<li>
<p>Statement, Comment, Expression</p>
</li>
<li>
<p>Method, function, atau procedure. Istilah mana yang digunakan tergantung bahasa yang dipelajari</p>
</li>
<li>
<p>Penggunaan variabel sebagai argumen dan return value. Pelajari juga pass-by-value vs pass-by-reference dan scope variabel</p>
</li>
<li>
<p>Control Flow, meliputi percabangan menggunakan if-else dan switch, dan perulangan dengan while atau for.Ketahui juga keyword control flow seperti break, return, dan exit.</p>
</li>
<li>
<p>Setelah mahir menggunakan variabel skalar, pelajari variabel majemuk. Variabel majemuk ini ada di semua bahasa pemrograman. Biasanya ada dua kategori dasar, yaitu yang menggunakan indeks numerik dan indeks non-numerik. Di PHP variabel majemuk berindeks numerik disebut dengan numeric-array, sedangkan yang berindeks non-numerik disebut associative-array. Di Java pilihan lebih banyak. Untuk indeks numerik ada List dan array. Sedangkan indeks non-numerik diwakili oleh Map. Java juga punya variabel majemuk yang dinamakan Set, yang tidak memiliki indeks berurut, tapi bisa digunakan untuk mencegah elemen yang duplikat. Pelajari konsep, implementasi, dan penggunaannya.</p>
</li>
</ol>
<p>Beberapa latihan yang dapat digunakan untuk memahami prinsip dasar ini antara lain:</p>
<ul>
<li>
<p>Hitung jumlah hari dalam bulan dan tahun tertentu</p>
</li>
<li>
<p>Tampilkan nama sendiri secara berulang sebanyak jumlah hurufnya</p>
</li>
<li>
<p>Hitung faktorial</p>
</li>
<li>
<p>Deret Fibonacci</p>
</li>
<li>
<p>dan sebagainya</p>
</li>
</ul>
<p>Salah satu keunggulan Java adalah dukungannya terhadap Object Oriented Programming. Sekarang, setelah menguasai cara kerja bahasa pemrograman melalui prinsip-prinsip di atas, kita belajar tentang bagaimana mendesain aplikasi dengan menggunakan prinsip orientasi objek.</p>
<p>Langkah-langkah belajar OOP adalah:</p>
<ol>
<li>
<p>Belajar apa itu class dan apa itu object. Kalau di Java, ketahui perbedaan antara class dan instance. Perbedaan antara static variable dan instance variable.</p>
</li>
<li>
<p>Encapsulation</p>
</li>
<li>
<p>Inheritance</p>
</li>
<li>
<p>Polymorphism</p>
</li>
<li>
<p>Banyak orang mengalami kesulitan dalam menjelaskan, apalagi mempelajari encapsulation, inheritance, dan polymorphism. Berdasarkan pengalaman saya, design pattern dapat digunakan untuk membantu pemahaman ketiga konsep OO tersebut. Gunakan Strategy pattern untuk memahami polymorphism, Factory pattern untuk memahami encapsulation, dan Template Method untuk memahami Inheritance. Cukup dengan tiga pattern ini, seharusnya konsep OO sudah bisa dipahami dengan baik</p>
</li>
</ol>
<p>Beberapa bahasa pemrograman seperti Java, Ruby, C# memiliki fitur Exception Handling. Pelajari fitur ini. Khusus Java, pelajari bedanya Error, Checked Exception, dan Unchecked Exception. Yang lebih penting, kenali karakteristik masing-masing dan kapan harus menggunakannya.</p>
<p>Semua bahasa pemrograman modern sudah memiliki infrastruktur Input/Output Stream. I/O Stream ini digunakan untuk membaca dan menulis data ke berbagai tujuan, misalnya file, jaringan, object, dan sebagainya. Dengan menggunakan I/O Stream, teknik baca/tulis file mirip dengan terima/kirim data dari jaringan.</p>
<p>Sebagai latihan, coba buat aplikasi chatting sederhana.</p>
<p>Terakhir, pelajari fitur-fitur Java yang advanced, seperti:</p>
<ul>
<li>
<p>Multithreading</p>
</li>
<li>
<p>Reflections API</p>
</li>
<li>
<p>Annotations</p>
</li>
<li>
<p>Generics</p>
</li>
<li>
<p>Enum</p>
</li>
</ul>
<p>Kalau sudah sampai di sini, pengetahuan Anda sudah cukup untuk meneruskan sendiri. Bila ingin membuat aplikasi bisnis, pelajari JDBC, kemudian ikuti <a href="http://endy.artivisi.com/blog/java/road-to-java-ee/">Road to Java EE</a>. Bila ingin membuat aplikasi daemon seperti web server atau sms gateway, dalami multithreading, classloading, socket programming, dan lainnya. Bila ingin konsentrasi di mobile, pelajari Java ME.</p>
<p>Demikian panduan singkat untuk belajar bahasa pemrograman Java. Tidak ada yang instan di dunia ini, termasuk belajar bahasa pemrograman. Bila Anda sama sekali belum pernah coding, Anda butuh waktu paling tidak 6 bulan untuk menguasai Java. Bila sudah pernah menggunakan bahasa non-OOP, butuh waktu sekitar 2-3 bulan. Perkiraan waktu ini sekedar gambaran saja, mungkin juga ada yang 2 minggu sudah bisa, atau 2 tahun masih belum bisa. Tergantung kondisi.</p>
<p>Setelah menguasai bahasanya, selanjutnya menguasai (Application Programming Interface) API yang dibutuhkan untuk menyelesaikan pekerjaan. Kemudian masih ada framework untuk membantu pembuatan aplikasi. Jangan lupa, kita masih harus belajar menggunakan tools seperti IDE, Ant, Maven, JUnit, Subversion, agar kita bisa bekerja secara efektif dan efisien.</p>
<p>Selamat belajar Java.</p>
Wirausaha2008-07-04T18:01:08+07:00https://software.endy.muhardin.com/life/wirausaha<p>Tiga bulan yang lalu, tepatnya Februari 2008, saya membuat keputusan signifikan sepanjang kehidupan saya.
Yaitu berpindah sisi meja, dari menerima gaji, menjadi memberi gaji alias mendirikan perusahaan sendiri. Padahal kantor tempat saya bekerja merupakan perusahaan terkenal, posisi saya strategis dan menantang, dan lingkungan kerjanya menyenangkan.</p>
<p>Perubahan ini, terutama bagi mereka yang sudah cukup lama bekerja di perusahaan dan sudah berkeluarga, merupakan hal yang menakutkan.
Pada waktu kita masih single, kalau ada kesulitan keuangan, yang menderita cukup diri sendiri saja. Sedangkan bila kita sudah berkeluarga, bokek bisa berakibat fatal bagi istri dan anak. Saya pernah mengalami masa melarat dulu di Surabaya sekitar tahun 2001-2002. Saking bokeknya, kami (saya dan teman-teman serumah) tidak punya uang untuk membeli lauk. Hanya ada nasi. Akhirnya, kami memetik daun kelor yang tumbuh di pekarangan dan merebusnya sebagai teman nasi. Jika ada yang memiliki ilmu kebatinan, pasti akan luntur seketika :p. Setelah berkeluarga, tentunya kita ingin memastikan bahwa jangan sampai anggota keluarga kita terpaksa makan sayur daun kelor.</p>
<p>Tidak sedikit teman-teman yang bertanya bagaimana cara melakukan peralihan dari karyawan menjadi wirausahawan dengan mulus. Yah, saat ini saya juga masih belum menjadi pengusaha sukses. Masih berjuang. Tapi ada sedikit pengalaman yang bisa saya ceritakan pada mereka-mereka yang ingin mengikuti jejak saya.</p>
<p>Di jaman serba sulit seperti ini, akan lebih baik jika kita bisa mandiri menciptakan lapangan pekerjaan sendiri. Jadi, semakin banyak pengusaha, semakin cepat Indonesia akan bangkit dari keterpurukan nasional.</p>
<p>Kerja kantoran dulu kemudian buka usaha sendiri berbeda dengan lulus sekolah dan langsung buka usaha. Masing-masing ada plus-minusnya.</p>
<p>Jika kita langsung buka usaha, kita biasanya akan:</p>
<ul>
<li>
<p>terbiasa dengan kondisi finansial yang tidak menentu. Kadang kaya raya, kadang melarat.</p>
</li>
<li>
<p>memiliki jiwa sales (ini adalah karakteristik penting yang akan saya bahas lebih lanjut nanti)</p>
</li>
<li>
<p>berorientasi hasil, bukan jam kerja</p>
</li>
<li>
<p>miskin pengalaman, sehingga usaha sulit maju</p>
</li>
</ul>
<p>Sebaliknya, bila kita jadi karyawan dulu, biasanya kita:</p>
<ul>
<li>
<p>terbiasa gajian di akhir bulan dengan jumlah fixed.</p>
</li>
<li>
<p>naluri pemburu kurang terasah, kecuali yang bekerja di lini penjualan</p>
</li>
<li>
<p>berorientasi jam kerja, lewat jam kantor masih kerja, hitung lembur</p>
</li>
<li>
<p>sudah mengenal sistem birokrasi kantoran dengan hirarki kekuasaan dan wewenang</p>
</li>
<li>
<p>terbiasa dengan prosedur yang baku (bila bekerja di perusahaan yang rapi)</p>
</li>
</ul>
<p>Masing-masing starting-position memiliki plus minusnya. Yang akan kita bahas sekarang adalah start dari posisi karyawan. Mungkin lain waktu kita akan bahas tentang start langsung jadi pengusaha.</p>
<p>Untuk bisa beralih dengan mulus, ada beberapa persiapan terutama dari aspek pola pikir dan gaya hidup.</p>
<p>Pola pikir yang dibutuhkan adalah:</p>
<ul>
<li>
<p>Orientasi terhadap hasil</p>
</li>
<li>
<p>Sense of urgency</p>
</li>
<li>
<p>Kepekaan terhadap peluang</p>
</li>
</ul>
<p>Sedangkan gaya hidup yang dibutuhkan adalah:</p>
<ul>
<li>
<p>Multiple Stream of Income (MSI).</p>
</li>
<li>
<p>Aktif di komunitas, baik fisik maupun maya.</p>
</li>
</ul>
<p>Mari kita bahas satu persatu.</p>
<h3 id="orientasi-hasil">Orientasi Hasil</h3>
<p>Bila kita menjadi karyawan swasta –apalagi di industri IT–, kita sulit untuk santai. Selalu ada atasan yang memantau kinerja kita. Terlihat chatting atau browsing, pasti akan langsung diajak diskusi mengenai pemanfaatan jam kerja yang efisien. Dengan demikian, performa kita akan selalu kinclong, karena dimonitor dan diingatkan sepanjang waktu.</p>
<p>Lain halnya bila kita menjadi wirausahawan. Tidak ada lagi atasan yang memantau kinerja kita dan mengingatkan bila kita mulai tidak fokus. Mau berpola hidup PNS, monggo. Mau sampai di kantor jam 11 pulang jam 14, silahkan.</p>
<p>Ini merupakan tantangan bagi kita. Kita harus memacu diri sendiri untuk menghasilkan sesuatu. Bila kita programmer, harus menghasilkan kode program. Bila kita instruktur pelatihan, harus menghasilkan modul pelatihan dan slide presentasi. Tanpa orientasi terhadap hasil, dapur bisa berhenti ngebul.</p>
<h3 id="sense-of-urgency">Sense of Urgency</h3>
<p>Mirip dengan orientasi hasil, di lingkungan karyawan kita memiliki atasan yang rajin menagih hasil kerja kita. Setelah hasil kerja kita serahkan, biasanya akan diperiksa dulu sebelum kita berikan pada client.</p>
<p>Berbeda halnya bila kita berwirausaha. Bila kita tidak memiliki sense of urgency, semua delivery ke client akan terlambat dan berkualitas rendah, karena tidak ada yang menagih dan memeriksa pekerjaan kita.</p>
<p>Kedua hal ini, orientasi hasil dan sense of urgency sepintas nampak seperti hal sepele dan sudah menjadi kondisi yang umum. Tapi walaupun ini terkesan common sense, tapi ternyata sangat berat dilakukan tanpa adanya atasan yang mengawasi. Penguasaan kita terhadap dua hal ini akan menentukan apakah kita bermental bos atau karyawan. Tanpa kedua hal ini, walaupun kita menjadi karyawan, sulit untuk meningkatkan karir di kantor.</p>
<h3 id="kepekaan-terhadap-peluang">Kepekaan terhadap peluang</h3>
<p>Sebagai karyawan, bila tidak berada di departemen business development, marketing, atau sales, biasanya kita tidak mampu mengenali peluang bisnis. Kepekaan terhadap peluang adalah suatu kemampuan yang harus dilatih terus menerus, mirip seperti kemampuan mendesain aplikasi.</p>
<p>Saat belum terlatih, kita tidak bisa melihat peluang bisnis, bahkan walaupun sudah disodorkan di depan mata. Berikutnya, kita bisa mengenali peluang, tapi belum bisa membedakan mana yang angin surga dan mana peluang betulan. Bila sudah mahir, kita bisa mengendus dari sekian banyak peluang, mana yang akan menghasilkan imbal hasil yang paling menguntungkan.</p>
<p>Inilah yang saya sebut di atas “memiliki jiwa sales”. Sebagai pendiri perusahaan, tugas utama kita adalah mendatangkan bisnis ke dalam perusahaan. Menjadi seorang deal-maker. Bila perusahaan kita tidak mendapat project, karyawan tidak ada kerjaan, ini adalah tanggung jawab founder.</p>
<p>Tanpa kemampuan ini, perusahaan tidak akan jalan, tidak peduli sepintar apapun programmer yang dimiliki. No sales, no company.</p>
<h3 id="multiple-stream-of-income">Multiple Stream of Income</h3>
<p>Sebagai karyawan yang loyal, biasanya kita hanya memiliki satu sumber penghasilan, yaitu gaji. Kondisi ini menyulitkan kita bila tiba-tiba ingin banting setir menjadi pengusaha. Seperti kita tahu, menjadi pengusaha penuh dengan ketidakpastian income. Kadang panen raya, kadang paceklik. Tidak ada bisnis yang terus menerus sukses. Bila Anda percaya ada bisnis yang tidak bisa gagal, hati-hati, bisa jadi Anda akan masuk koran sebagai investor blue energy yang ternyata palsu.</p>
<p>Untuk mengatasi perbedaan suasana ini, saya anjurkan untuk membiasakan diri mencari penghasilan tambahan di luar gaji. Setidaknya selama dua sampai tiga tahun, cobalah untuk mencari penghasilan diluar gaji. Tentunya dilakukan dengan <a href="http://endy.artivisi.com/blog/life/nyambi-bolehkah/">cara yang profesional dan etis</a>. Bila sumber penghasilan kita sudah lebih dari satu, maka kita sudah memiliki Multiple Stream of Income (MSI).</p>
<p>Ada beberapa keuntungan yang didapat dari MSI ini. Pertama, terutama bagi eks-karyawan, ini akan menghilangkan paradigma kita bahwa yang namanya penghasilan hanyalah dari gaji bulanan. Kedua, ini akan mengasah kepekaan kita terhadap peluang bisnis. Ketiga, ini akan melatih kita berorientasi hasil dan memiliki sense of urgency. Keempat, ini akan menambah keyakinan diri bahwa tanpa gaji rutin kita tetap bisa survive.</p>
<p>Saya sendiri sudah memiliki MSI sejak lulus kuliah. Dua tahun terakhir sebelum saya benar-benar berhenti jadi karyawan, saya mulai mencatat income saya, baik dari gaji maupun dari yang lainnya. Di akhir tahun pertama, proporsi pendapatan gaji dengan non-gaji berbanding 85:15. Di tahun kedua, proporsinya naik menjadi 65:35. Artinya, hanya dengan menggunakan 10-15% waktu, saya mampu menghasilkan 35% penghasilan tahunan saya.</p>
<p>Kemudian hal ini saya bicarakan dengan keluarga. Siapkah mereka hidup dengan hanya 35% saja dari income biasa? Tentunya dengan imbalan waktu yang lebih fleksibel untuk keluarga dan potensi penghasilan yang tidak terbatas.</p>
<p>Alhamdulillah keluarga saya menyatakan siap berjuang bersama. Saat ini, setelah lima bulan berjuang, rata-rata income saya sudah lebih dari gaji semasa kerja dulu. Tidak ada yang instan. Pada masa awal dulu, kami hidup prihatin dan mengencangkan ikat pinggang.</p>
<p>Paradigma MSI ini akan menjadi lebih penting setelah kita memiliki usaha sendiri. Bila kita mengerjakan project software development, harus selalu ada lebih dari satu project yang berjalan bersamaan. Bila kita mengadakan pelatihan, harus ada pemasukan dari sesi training, lisensi materi pelatihan, dan penjualan buku atau sampel kode program.</p>
<h3 id="aktif-di-komunitas">Aktif di komunitas</h3>
<p>Rasulullah bersabda bahwa silaturahmi akan membuka pintu rejeki. Brian Tracy dan Robert Kiyosaki mengatakan bahwa aliran kas masuk berbanding lurus dengan komunikasi keluar. Hasilnya mungkin tidak terlihat langsung, tapi bisa terasakan dampaknya.</p>
<p>Di ArtiVisi, kita mengikuti filosofi tersebut. Saya dan Ifnu aktif mengisi blog dan berkontribusi di milis Java. Motif utamanya tentu saja sedekah ilmu dan mencari pahala. Kalau kemudian ada project yang datang dari komunitas, kami anggap itu sebagai bonus dan juga konsekuensi logis dari aktifitas tersebut.</p>
<p>Demikianlah sedikit sharing pengalaman mendirikan perusahaan baru. Masih panjang jalan yang harus ditempuh untuk membesarkan perusahaan yang baru seumur jagung ini. Di atas semua usaha, tentunya doa memiliki peranan yang paling penting.</p>
<p>Harapan saya, dengan artikel ini akan banyak pengusaha baru yang bisa membuka banyak lapangan kerja. Di satu sisi, banyak training centre dan software development company memang akan menambah saingan ArtiVisi. Tapi di sisi lain, kami jadi bisa berjualan lisensi materi pelatihan, project management training, dan consulting kepada para kompetitor.</p>
<p>Ayo jadi pengusaha !!</p>
Apa itu CMMI?2008-06-18T01:17:17+07:00https://software.endy.muhardin.com/manajemen/apa-itu-cmmi<p>Di <a href="http://tech.groups.yahoo.com/group/appli-member">milis Asosiasi Pengembang Perangkat Lunak Indonesia (APPLI)</a> ramai dibahas mengenai standarisasi dalam pengembangan perangkat lunak. Salah satu standar yang populer digunakan adalah CMMI (Capability Maturity Model Integration) yang dikembangkan oleh Carnegie-Mellon University, untuk lebih tepatnya dalam departemen Software Engineering Institute. Selain itu, ada juga blog <a href="http://arian75.wordpress.com/2008/06/16/sei-cmmi-maturity-apakah-ada-perusahaan-it-indonesia-yang-mendapatkannya/">ini</a> dan <a href="http://santus.wordpress.com/2008/02/04/cmmi-what-is-it-for/">ini</a> yang membahas tentang CMMI.</p>
<p>Dengan adanya standar, organisasi dapat berkembang dengan lebih terarah. Semua anggota organisasi mulai dari programmer, analis, tester, manajer, dan direktur menjadi tahu apa ruang lingkup pekerjaannya. Apa yang harus disediakan bagi pihak lain, dan juga apa yang bisa diharapkan dari departemen lain. Dengan demikian, tidak banyak effort yang terbuang karena miskomunikasi atau kurang koordinasi.</p>
<p>Sayangnya, dunia enterpreneur IT di Indonesia masih jarang yang hirau terhadap masalah standarisasi ini. Berbagai alasan dikemukakan, antara lain:</p>
<ul>
<li>
<p>Tidak mengerti bahasa Inggris</p>
</li>
<li>
<p>Standar luar negeri tidak cocok untuk kondisi lokal</p>
</li>
<li>
<p>Standar membuat organisasi monoton dan membosankan</p>
</li>
<li>
<p>dan segudang alasan lainnya</p>
</li>
</ul>
<p>Menurut saya, segala alasan itu cuma pembenaran saja untuk sifat malas belajar. Sebagai praktisi IT, tentunya kita sadar bahwa dunia tempat kita hidup sekarang (internet) dibangun di atas standar. Untuk bisa browsing, kita menggunakan protokol HTTP. Memeriksa email, menggunakan protokol POP3, IMAP, dan SMTP. Chatting, menggunakan protokol IRC, XMPP. Voice chat, menggunakan protokol SIP.</p>
<p>Protokol adalah nama lain dari standar. So, standar membuat hidup kita menjadi lebih baik. Setidaknya, standar apapun lebih baik daripada tanpa standar. Melalui artikel ini, mudah-mudahan para praktisi tergerak untuk setidaknya mempelajari dulu standar sebelum mengeluarkan vonis “tidak perlu” atau “tidak sesuai”.</p>
<p>Sekarang, mari kita lihat standarisasi dalam pengembangan perangkat lunak. Standar yang populer dan cukup saya kuasai adalah CMMI, jadi mari kita bahas tentang CMMI. Kebetulan saya pernah ikutan mengantarkan BaliCamp meraih CMMI Maturity Level 3.</p>
<h3 id="apa-itu-cmmi">Apa itu CMMI</h3>
<p>CMMI adalah singkatan dari Capability Maturity Model Integration. Ini adalah kerangka kerja (framework) yang bisa digunakan untuk mengembangkan proses di dalam perusahaan.</p>
<p>Apa itu proses? Proses adalah cara kita melakukan suatu tugas. Misalnya, membuat proposal, menganalisa kebutuhan client, membuat kode program, dan kegiatan lainnya. Semua tata laksana kegiatan tersebut dikenal dengan nama proses atau prosedur.</p>
<p>CMMI membantu kita untuk memperbaiki proses di perusahaan/organisasi kita. Dengan membaiknya proses, diharapkan produk yang dihasilkan akan ikut menjadi baik.</p>
<p>CMMI dirumuskan oleh Software Engineering Institute di Carnegie Mellon University. Para peneliti di SEI telah mengamati proyek pembangunan perangkat lunak di seluruh dunia, mulai dari proyek kecil sampai proyek raksasa. Organisasi yang diteliti meliputi NASA, IBM, dan kontraktor Departemen Pertahanan Amerika Serikat. Pengalaman yang dimiliki organisasi tersebut dirangkum dalam seperangkat aturan yang disebut CMMI. Nah, apakah perusahaan kita sudah lebih canggih daripada organisasi di atas, dalam hal mengelola proyek software? Kalau belum, mari kita belajar dari mereka.</p>
<h3 id="apa-sih-isinya-cmmi-">Apa sih isinya CMMI ??</h3>
<p>CMMI terdiri dari rangkaian practices. Dalam rangkaian practices ini ada rambu-rambu atau rekomendasi yang dapat diikuti. Practices dalam CMMI dibagi menjadi dua, yaitu Generic Practices (GP) dan Specific Practices (SP).</p>
<p>Bila kita sudah mengimplementasikan practices dengan sempurna, kita dianggap sudah memenuhi Goals. Sama seperti practices, ada Generic Goals (GG) dan Specific Goals (SG).</p>
<p>SG dan SP dikelompokkan menjadi Process Area (PA). Total ada 22 Process Area dalam CMMI for Development versi 1.2.</p>
<p>Daftar PA, GG, SG, GP, dan SP dapat dilihat di spesifikasi CMMI yang bisa didonlod gratis <a href="http://www.sei.cmu.edu/publications/documents/06.reports/06tr008.html">di sini</a>.</p>
<h3 id="apa-hubungannya-cmmi-dengan-standarisasi-iso">Apa hubungannya CMMI dengan standarisasi ISO</h3>
<p>CMMI dan ISO sama-sama standar yang digunakan untuk menilai proses suatu organisasi. Kalau kita programmer Java, kita punya sertifikasi SCJP, SCWD, SCBCD, dan sebagainya. Nah, anggap saja CMMI atau ISO ini adalah sertifikasinya perusahaan.</p>
<p>Kalau di SCJP yang dinilai adalah penguasaan kita terhadap bahasa pemrograman Java, maka di ISO/CMMI yang dinilai adalah penguasaan perusahaan terhadap prosesnya sendiri.</p>
<p>Perbedaan CMMI dan ISO terletak pada ketelitiannya. Bila kita ingin perusahaan kita mendapat sertifikasi ISO, perusahaan kita harus memiliki Standard Operating Procedure (SOP) yang tertulis. Kemudian kita harus membuktikan pada badan sertifikasi bahwa SOP tersebut kita jalankan dengan baik. Apa saja yang kita tulis dalam SOP bebas terserah kita. ISO tidak mengatur sampai ke tingkat itu.</p>
<p>Berbeda dengan CMMI, selain kita punya SOP, dia punya aturan khusus tentang isi SOP. Misalnya, kalau kita melakukan analisa kebutuhan (requirement gathering), ada beberapa aturan yang harus diikuti, misalnya:</p>
<ul>
<li>
<p>Pernyataan kebutuhan user harus dicatat</p>
</li>
<li>
<p>Pernyataan kebutuhan harus dikonfirmasi ke user</p>
</li>
<li>
<p>Pernyataan kebutuhan harus disetujui kedua pihak</p>
</li>
<li>
<p>Kalau ada perubahan, harus dicatat</p>
</li>
<li>
<p>Antara kebutuhan dan software yang dideliver, harus bisa dilacak bolak-balik</p>
</li>
</ul>
<p>Singkatnya, kalau kita lulus ISO, belum tentu lulus CMMI. Sebaliknya, kalau kita lulus CMMI, besar kemungkinan kita akan langsung lulus ISO bila ikut sertifikasinya.</p>
<h3 id="apa-yang-dimaksud-maturity-level-">Apa yang dimaksud Maturity Level ??</h3>
<p>Tujuan awal dirumuskannya CMMI sebenarnya adalah untuk mendukung proses tender di lingkungan Departemen Pertahanan Amerika Serikat (US-DoD). Mereka ingin memiliki sistem penilaian terhadap semua vendor yang mengajukan proposal. Untuk itu dirumuskanlah sistem penilaian vendor berupa Maturity Level (ML).</p>
<p>Maturity Level di CMMI ada 5, mulai dari yang terendah ML 1, sampai yang paling canggih ML 5. Bila perusahaan kita sudah ML-5, maka kita bisa ikut dalam tender proyek software rudal Patriot. Begitu kira-kira.</p>
<p>Setiap ML memiliki seperangkat PA yang harus dipenuhi agar kita berhak menggunakan titel ML tersebut. Sebagai contoh, bila kita ingin lulus ML-2, maka kita harus mengimplementasikan 7 PA. Untuk mencapai ML-3, kita harus mengimplementasikan 7 PA dari ML-2 ditambah dengan 11 PA dari ML-3. Demikian seterusnya, sehingga ML-5 yang sudah mengimplementasikan 22 PA.</p>
<p>Bila kita sama sekali belum mengimplementasikan apa-apa, perusahaan kita dikategorikan sebagai ML-1. Level ini diadakan sebagai hiburan bagi perusahaan yang sudah ikut SCAMPI Class A, tapi tidak lulus bahkan di ML-2. Well, ML-1 kedengarannya lebih baik daripada No-ML atau ML-0 :p</p>
<p>Daftar lengkap PA per ML bisa dilihat di <a href="http://www.sei.cmu.edu/publications/documents/06.reports/06tr008.html">spesifikasi CMMI</a>.</p>
<h3 id="perusahaan-saya-ingin-mendapat-ml-5-bagaimana-caranya">Perusahaan saya ingin mendapat ML-5, bagaimana caranya?</h3>
<p>Pertama, tentunya perusahaan kita harus memenuhi semua persyaratan mulai dari ML-2 sampai ML-5. Perusahaan kita harus sudah punya SOP yang mengatur semua proses sesuai aturan CMMI. Bila ada aturan yang tidak kita pahami, kita bisa datangkan konsultan untuk menjelaskan.</p>
<p>Setelah ada SOP tersebut, setiap orang dalam perusahaan harus memahami dan menjalankannya dengan benar. Setelah kita yakin bahwa perusahaan kita mampu, kita mendatangkan appraiser atau auditor untuk memeriksa proses kita.</p>
<p>Kegiatan appraisal ini disebut dengan SCAMPI. Ada macam-macam SCAMPI, tapi yang berhak mengeluarkan peringkat ML hanyalah SCAMPI Class A.</p>
<p>Dalam SCAMPI, Lead Appraiser(LA) akan merekrut beberapa orang dari perusahaan kita untuk membantunya mengaudit. Tim auditor ini disebut Appraisal Team Member (ATM). Perusahaan kita juga juga harus menyediakan tim yang akan diwawancarai oleh ATM, yang disebut dengan Functional Area Representative (FAR).</p>
<p>FAR merupakan perwakilan dari berbagai departemen dalam organisasi. Mungkin nantinya akan ada kelompok FAR dari procurement, tim project, network administrator, programmer, tester, dan lainnya.</p>
<p>ATM dibutuhkan untuk menterjemahkan aturan CMMI ke dalam SOP perusahaan. Misalnya, dalam CMMI ada aturan mengenai analisa kebutuhan, yaitu process area Requirement Management (REQM) dan Requirement Development (RD). Process area ini di perusahaan A mungkin diimplementasikan dengan dokumen Software Requirement Specification (SRS), tapi di perusahaan B mungkin namanya User Requirement Specification (URS), dan di perusahaan C berupa Use Case Diagram dan User Stories. Nah, tugas ATM adalah menjembatani antara istilah CMMI dan istilah internal perusahaan.</p>
<p>Wawancara ATM tidak aneh-aneh. Untuk setiap proses area, mereka akan tanya apakah FAR sudah mengimplementasikan. Bila sudah, mana buktinya. Bukti ini bisa berupa hard-copy, bisa juga soft-copy. Kita bisa mengajukan email sebagai evidence. Bahkan kita juga bisa menunjukkan log Subversion atau item bug dalam aplikasi bug tracker.</p>
<p>Dalam sintaks Java 5, proses appraisal dalam SCAMPI Class A bisa digambarkan sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int level = 1;
appraisal:
for(int i=1; i<=5; i++) {
List allProcessAreas = maturityLevel[i].getProcessAreas();
for(ProcessArea pa : allProcessAreas) {
for(GenericPractice gp : allGenericPractices) {
if(!far.isImplement(pa, gp) { break appraisal; }
}
for(SpecificPractice sp : pa.getSpecificPractices()) {
if(!far.isImplement(sp)) { break appraisal; }
}
level++;
}
}
</code></pre></div></div>
<p>Berdasarkan hasil wawancara FAR oleh ATM, LA akan memutuskan ML berapa yang pantas untuk perusahaan kita.</p>
<p>Semua hasil SCAMPI Class A akan dilaporkan pada SEI dan akan <a href="http://sas.sei.cmu.edu/pars/pars.aspx">dipublikasikan di internet</a>. Sebagai contoh, kita bisa melihat <a href="http://sas.sei.cmu.edu/pars/pars_detail.aspx?a=7546">hasil appraisal BaliCamp pada tahun 2006</a>.</p>
<p>Sayangnya, sampai sekarang belum ada appraiser lokal. Jadi kita harus mendatangkan appraiser dari luar negeri.</p>
<p>Informasi lebih rinci mengenai SCAMPI dapat dilihat <a href="http://www.sei.cmu.edu/publications/documents/06.reports/06hb002.html">di spesifikasi resminya</a>. Di sana ada penjelasan rinci tentang apa saja syarat menjadi ATM, bagaimana proses interview dilakukan, dan bagaimana cara memutuskan apakah suatu evidence sudah memenuhi syarat atau belum.</p>
<h3 id="apa-yang-dimaksud-continuous-representation">Apa yang dimaksud Continuous Representation?</h3>
<p>Perusahaan mengadopsi CMMI untuk berbagai tujuan. Ada yang bermotif marketing, yaitu meraih ML tertentu dengan harapan akan mendapat project dari US-DoD, ataupun simply memperkeren Company Profile. Sama saja dengan kita ambil SCJP.</p>
<p>Ada juga yang memang berniat meningkatkan kualitas prosesnya. Mengadopsi CMMI dengan harapan perusahaan akan menjadi lebih baik.</p>
<p>Kita kesampingkan dulu motif marketing. Mari kita lihat motif peningkatan kualitas. Ada beberapa pendekatan untuk mengadopsi CMMI. Kita bisa mengadopsi per ML, misalnya tahun ini ML-3, berikutnya ML-4, dan seterusnya. Atau bisa juga kita hanya memfokuskan perbaikan pada satu process area tertentu saja, misalnya Requirement Management, atau Risk Management.</p>
<p>Bila kita berorientasi ML, maka kita mengambil pendekatan Staged Representation. Sedangkan bila kita berorientasi PA, maka kita mengambil pendekatan Continuous Representation.</p>
<h3 id="bila-perusahaan-saya-sudah-ml-5-apakah-perusahaan-akan-menjadi-monoton-dan-membosankan-apakah-karyawannya-akan-menjadi-seperti-robot-belaka">Bila perusahaan saya sudah ML-5, apakah perusahaan akan menjadi monoton dan membosankan? Apakah karyawannya akan menjadi seperti robot belaka??</h3>
<p>Bagi programmer seperti saya dan Anda, kreativitas dan improvisasi adalah kenikmatan kerja yang utama. Itulah yang membuat kita memilih profesi software developer. Oleh karena itu, wajar bila kita mengkhawatirkan masalah ini.</p>
<p>Well, saya sudah pernah mengantarkan BaliCamp meraih ML-3, dan ikut terlibat dalam persiapan mencapai ML-5. Jadi, yang akan saya ceritakan ini adalah pengalaman nyata, bukan FUD (Fear, Uncertainty, Doubt).</p>
<p>Sebagai programmer yang terlibat dalam project, hal yang paling menyebalkan bagi kita bukanlah kesulitan teknis atau kerumitan algoritma. Semakin sulit, semakin menantang bagi kita. Hal yang paling menyebalkan adalah perubahan requirement di tengah jalan. Fitur yang kita implementasi dengan susah payah, bertampilan Web 2.0, menggunakan teknologi AJAX, tiba-tiba divonis client, “Ini bukan yang saya mau, GANTI !!!”.</p>
<p>Atau mungkin tidak se-ekstrim itu. End-user hanya minta geser tombol sedikit, tambah fitur sedikit, dan sedikit-sedikit lainnya, yang lama-lama tentunya akan menjadi bukit. Tiba-tiba, project sudah telat 2 bulan, dan fitur baru 50% terimplementasi. Bukan karena kita codingnya lama, tapi karena user minta perubahan melulu.</p>
<p>Nah, urusan perubahan requirement ini wajib hukumnya untuk dikelola dengan baik. Diatur dalam process area Requirement Management (REQM) yang ada di ML-2 dalam SP 1.3. Kalau perusahaan kita mengimplementasi REQM dengan baik, kita sebagai programmer akan lebih tenang hidupnya. Semua perubahan terhadap aplikasi yang sedang dibuat harus melalui rangkaian prosedur untuk memastikan perubahan tersebut benar-benar diinginkan dan sudah dipertimbangkan konsekuensinya. End-user tidak akan semena-mena meminta perubahan, tapi harus melalui persetujuan atasannya dan atasan kita. Dengan demikian, perubahan yang sampai pada programmer sudah pasti adalah perubahan yang penting, bukan hanya menurut end-user, tapi juga menurut sponsor project. Bahkan, adanya prosedur ini saja sudah cukup untuk membatasi <em>liarnya imajinasi</em> end-user.</p>
<p>Ini hanya satu contoh saja bagaimana implementasi CMMI memudahkan hidup kita sebagai programmer. Silahkan baca-baca spesifikasinya untuk mengetahui aturan-aturan lainnya. Secara umum, CMMI sama sekali tidak menyinggung tentang teknologi, IDE, tools, bahasa pemrograman yang digunakan. Bahkan kegiatan coding sendiri cuma dibahas dalam 2 PA dari 22 yang ada, yaitu Technical Solution dan Product Integration.</p>
<p>Technical Solution mengharuskan kita untuk mengidentifikasi alternatif pendekatan yang tersedia. Kemudian dari alternatif tersebut, kita pilih yang paling baik, berdasarkan kriteria yang kita tentukan sendiri. Jadi tidak boleh langsung coding, melainkan harus mikir dulu.</p>
<p>ML-4 dan ML-5 banyak menitikberatkan pada analisa kuantitatif dan continuous improvement. Sukakah anda terlibat dalam project yang selesai tepat waktu, tidak lembur, libur pada hari Sabtu-Minggu? Nah, kalau sudah ML-5, project seperti ini bukan lagi impian, tapi sudah menjadi hal yang biasa. Delay dalam project sudah bisa diketahui sejak dini. Dari mulai telat 1 hari, project manager sudah bisa tahu dan mengambil tindakan antisipasi seperti minta pengunduran jadwal, mengurangi requirement, dan sebagainya.</p>
<p>Sebagai programmer, tidak banyak perubahan yang kita rasakan selain project menjadi lebih tenang dan teratur. Yang paling besar terkena dampak implementasi CMMI adalah Project Manager. Tiba-tiba saja dia akan diharuskan membuat banyak dokumen dan menyediakan data. Pekerjaan administratifnya akan menjadi jauh lebih banyak.</p>
<p>Tentunya hal ini bisa diatasi dengan otomasi proses. Begitu prosesnya sudah rapi, kegiatan administrasi bisa di-online-kan. Masalah versioning dokumen bisa diatasi dengan Subversion. Daftar resiko project, task management, bug report, bisa diotomasi dengan Bug/Issue Tracker. Lagipula, bila perusahaan kita bergerak di bidang IT, tentunya persenjataan seperti itu sudah seharusnya menjadi lifestyle kita.</p>
<p>Demikianlah penjelasan singkat tentang CMMI. Lain waktu kita akan bahas satu persatu Process Area yang ada.</p>
<p>Semoga bermanfaat.</p>
Long time no see2008-06-11T22:44:45+07:00https://software.endy.muhardin.com/lain/long-time-no-see<p>Mohon maaf bagi pembaca setia blog saya, karena sudah lama tidak ada artikel baru. Pada Februari 2008 yang lalu, saya mengambil keputusan yang mengubah arah hidup saya, mudah-mudahan ke arah yang lebih baik.</p>
<p>Saya mengundurkan diri dari posisi empuk dan pekerjaan menantang di <a href="http://www.balicamp.com">Sigma Karya Sempurna (BaliCamp)</a> dan memutuskan untuk menggarap <a href="http://www.artivisi.com">ArtiVisi</a> secara lebih serius bersama <a href="http://ifnu.artivisi.com">Ifnu</a>.</p>
<p>Sebagai perusahaan start-up, banyak hal yang harus dilakukan pada masa awal berdirinya perusahaan. Hal-hal teknis seperti setup repository server dan hal non teknis seperti rumus penggajian pegawai, aturan cuti, dan sebagainya harus dipikirkan dan dibuatkan prosedurnya. Belum lagi pengembangan produk pelatihan dan standarisasi kualitas bahan ajar. Dan yang paling penting, cari proyek supaya dapur tetap ngebul. Sehingga akibatnya aktivitas blogging menjadi kalah prioritas.</p>
<p>Setelah empat bulan berlalu, banyak pengalaman yang didapat, dan juga banyak project yang sudah closing. Urusan non teknis yang penting juga sudah banyak yang bisa didelegasikan. Karena itu, mudah-mudahan saya bisa mengisi blog lagi.</p>
<p>Beberapa pengalaman yang rencananya akan saya tuliskan antara lain:</p>
<ul>
<li>
<p>Acegi Security 2.0</p>
</li>
<li>
<p>Tutorial OSGi</p>
</li>
<li>
<p>SpringSource Application Platform</p>
</li>
<li>
<p>Tutorial <a href="http://www.jpos.org">jPOS</a> (library untuk memproses ISO-8583)</p>
</li>
<li>
<p>dan tulisan non-java seperti “<a href="http://endy.artivisi.com/blog/life/wirausaha">Langkah persiapan berhenti jadi karyawan”</a></p>
</li>
</ul>
<p>Stay tuned … :D</p>
Road to Java EE2008-03-27T18:41:19+07:00https://software.endy.muhardin.com/java/road-to-java-ee<p>Another Frequently Asked Question.</p>
<blockquote>
<p>Saya sudah menguasai Java Standard Edition dan sekarang mau belajar Java Enterprise Edition. Bagaimana learning-path-nya?</p>
</blockquote>
<p>Inilah Road to Java Enterprise versi saya:</p>
<h3 id="tahap-pertama">Tahap Pertama</h3>
<ol>
<li>Belajar HTTP.
<ul>
<li>bedanya GET dan POST</li>
<li>apa itu session</li>
<li>bagaimana cara implement state management</li>
<li>konsep multipart dan mekanisme upload file</li>
</ul>
</li>
<li>Belajar Servlet Fundamental.
<ul>
<li>Servlet</li>
<li>Filter</li>
<li>Listener</li>
<li>Tidak perlu repot2 belajar JSP</li>
</ul>
</li>
<li>Belajar JDBC.
Pastikan Anda tau:
<ul>
<li>Cara connect ke database</li>
<li>Cara eksekusi DML</li>
<li>Cara menjalankan SQL select</li>
</ul>
</li>
<li>Belajar Database Transaction Fundamental.
Pastikan Anda tau:
<ul>
<li>Syarat-syarat untuk mengaktifkan transaction</li>
<li>Local vs Managed Transaction</li>
<li>Programmatic vs Declarative Transaction</li>
<li>Transaction Isolation Level</li>
<li>Transaction Propagation</li>
</ul>
</li>
</ol>
<p>Untuk tahap pertama, itu dulu saja.</p>
<p>Kalau sudah ngerti itu, bisa dengan mudah memahami:</p>
<ul>
<li>Web framework apapun (Spring MVC, Struts 1 dan 2, Java Server Faces)</li>
<li>Database abstraction framework seperti Spring JDBC, iBatis, dan Hibernate.</li>
</ul>
<h3 id="tahap-kedua">Tahap Kedua</h3>
<p>Tahap kedua ini relatif rumit. Karena itu, untuk tiap materi, pastikan:</p>
<ul>
<li>Anda tau masalah yang mendasari munculnya teknologi ini.</li>
<li>Anda tau cara memecahkan masalah tersebut dengan teknologi ybs.</li>
<li>Anda tau keterbatasan dari teknologi ybs.</li>
<li>Anda tau alternatif solusi selain menggunakan teknologi ybs</li>
</ul>
<ol>
<li>Remote Method Invocation
<ul>
<li>Mekanisme remote invocation</li>
<li>Mekanisme rmiregistry</li>
<li>Cara membuat remote object</li>
<li>Cara mempublish remote object</li>
<li>Cara membuat client yang mengakses remote object</li>
</ul>
</li>
<li>Java Messaging Service (JMS)
<ul>
<li>Arsitektur Messaging</li>
<li>Point to Point vs Publisher - Subscriber</li>
<li>Bedanya Durable dan Non-Durable Subscriber</li>
<li>Cara mengirim message</li>
<li>Cara menerima message</li>
</ul>
</li>
<li>Enterprise Java Bean
<ul>
<li>Stateless Session Beans</li>
<li>Stateful Session Beans</li>
<li>Message Driven Beans</li>
<li>Entity Beans dan evolusinya dari versi 2 sampai versi 3.</li>
</ul>
</li>
</ol>
<p>Kalau sudah menyelesaikan tahap 2 ini, seharusnya Anda akan mudah memahami Seam Framework dan bisa menggunakan sebagian besar fitur dari application server Java (seperti Glassfish, Geronimo, JBoss AS, IBM Websphere, Oracle iAS, BEA Weblogic, dsb).</p>
<p>Selain itu, masih ada tahap ketiga, yaitu urusan lain-lain seperti JMX, dan teman-temannya. Tapi saya yakin kalau sudah lulus tahap dua, sudah tidak bingung lagi mau belajar apa.</p>
<p>Daftar di atas memang cukup menggetarkan hati. Sebagai gambaran, saya sendiri butuh waktu satu tahun lebih untuk memahami itu semua.</p>
<p>Tapi jangan khawatir, kalau Anda mulai hari ini, berarti tahun depan sudah menguasai. Kalau menunda belajar, bukan saja akan lebih lama selesainya, tapi juga materinya akan lebih banyak. Framework integrasi ala OSGi dan fitur baru Java 7 seperti Closure sudah di ambang pintu.</p>
<p><strong>Mulailah dari sekarang !!!</strong></p>
Kutu Loncat2008-03-17T23:53:14+07:00https://software.endy.muhardin.com/manajemen/kutu-loncat<p>Di milis Java sedang ribut urusan gaji programmer. Topik ini adalah topik abadi. Sepanjang hidup saya di milis ini, paling tidak urusan gaji dibahas setahun dua kali. Kadang-kadang lebih sering.</p>
<p>Kita tidak akan membahas tentang urusan gaji … mungkin nanti di posting berikutnya. Kita akan membahas tentang fenomena pindah kerja terlalu sering, a.k.a kutu loncat. Menurut salah seorang komentator, programmer Java cenderung kutu loncat.</p>
<p>Sedikit informasi latar belakang, sejak lulus di tahun 2001 sampai 2008 ini, saya sudah kerja di 7 perusahaan berbeda. Kalau dirata-rata, berarti tiap tahun pindah kerja. Saat ini saya membangun perusahaan sendiri. Jadi saya akan bahas dari sudut pandang karyawan, dan juga pemilik perusahaan.</p>
<p><strong>Sudut Pandang Karyawan</strong></p>
<p>Sebagai karyawan, pindah kerja itu hal yang biasa. Yang penting jangan terlalu sering, dan menggunakan sopan-santun yang benar.</p>
<p>Kalau kita terlalu sering pindah kerja, misalnya setahun 3 kali (berarti rata-rata 4 bulan), kita akan mengalami beberapa kesulitan.</p>
<p>Kesulitan pertama adalah dilema dalam mengupdate CV. Apakah 3 perusahaan tersebut akan kita tulis atau tidak? Kalau ditulis, HRD akan bertanya-tanya, ada apa dengan kandidat ini? Kok dalam satu tahun sudah pindah 3 kali. Terlalu menuntut, rewel, atau apa? Kalau tidak ditulis, HRD akan bertanya kenapa kandidat ini pengangguran beberapa bulan?</p>
<p>Kesulitan kedua, waktunya akan habis untuk masa transisi. Untuk bisa efektif, seorang karyawan butuh 1-2 bulan penyesuaian. Kalau kita resign, butuh waktu 2-4 minggu untuk transfer knowledge ke penerus kita. Kerugian di kita juga, kita tidak bisa mengakumulasi pengalaman.</p>
<p>Bagaimana sopan santunnya? Mudah saja:</p>
<ol>
<li>
<p>Beritahukan sedini mungkin. Idealnya 1 bulan, kalau tidak bisa ya minimal 2 minggu sebelumnya</p>
</li>
<li>
<p>Selesaikan semua tanggung jawab dan hutang (kalau ada)</p>
</li>
<li>
<p>Lakukan proses handover dengan benar</p>
</li>
</ol>
<p>Kapan kita pindah kerja?</p>
<p>Menurut saya, bila:</p>
<ul>
<li>
<p>Pekerjaan kita sudah tidak lagi membuat kita tambah pintar</p>
</li>
<li>
<p>Kompensasi yang kita terima tidak sebanding dengan kontribusi yang kita berikan</p>
</li>
<li>
<p>Ada tawaran yang minimal 50% lebih besar (annually, bukan monthly). Kalau lebih sedikit dari itu, tidak sebanding dengan kerepotan pindah kerja.</p>
</li>
</ul>
<p><strong>Sudut Pandang Perusahaan</strong></p>
<p>Kalau karyawannya bagus, perusahaan akan rugi kalau dia resign, karena:</p>
<ul>
<li>
<p>butuh waktu untuk rekrutasi</p>
</li>
<li>
<p>butuh waktu untuk training</p>
</li>
<li>
<p>butuh waktu untuk handover/transisi</p>
</li>
</ul>
<p>Oleh karena itu, perusahaan harus berusaha mempertahankan karyawannya yang bagus. Apalagi untuk software development company. Komputer rusak gampang diganti, tapi top developer resign, urusannya jauh lebih kompleks. Keseluruhan daya saing perusahaan terletak pada otak karyawannya.</p>
<p>Untuk sudut pandang perusahaan, saya akan lebih fokus tentang bagaimana sudut pandang yang benar dalam memandang karyawannya.</p>
<p>Selalu berikan training. Training ini adalah untuk memastikan karyawan tersebut melakukan tugasnya dengan benar. Saya pernah dengar kutipan berikut, “Train your employee and risk they leave, or not train your employee and risk they stay”. Arti kutipan di atas, resiko kita bila memberikan training adalah bila karyawan resign, kita rugi biaya training. Sebaliknya, bila kita tidak memberikan training, kita menanggung resiko bila karyawan tersebut tidak perform dan tak kunjung resign.</p>
<p>Kesalahan besar yang lainnya, berkaitan dengan training, menganggap karyawan yang otodidak tidak butuh training. Ini pandangan yang sempit, menganggap bahwa training hanyalah sarana menambah knowledge belaka.</p>
<p>Training, apalagi external training, memiliki benefit lain disamping menambah pengetahuan:</p>
<ol>
<li>
<p>Memperluas wawasan</p>
</li>
<li>
<p>Memperluas pergaulan</p>
</li>
<li>
<p>Menimbulkan sense-of-significance dalam karyawan. Perasaan bahwa dia tidak semata diperah, tapi juga dibesarkan. Perasaan bahwa perusahaan peduli dengan peningkatan kemampuannya. Urusan bahwa materi tersebut bisa dipelajarinya sendiri tidaklah penting.</p>
</li>
</ol>
<p>Evaluasi kompensasi vs kontribusi secara periodik. Karyawan yang baik kemampuannya akan cepat meningkat. Hari ini baru bisa Java Fundamental, enam bulan kemudian sudah mengerti Spring Framework. Peningkatan kemampuan jelas mengimplikasikan peningkatan market-value si karyawan.</p>
<p>Perusahaan harus memastikan bahwa dialah yang pertama mengetahui peningkatan kemampuan tersebut, dan kemudian mengapresiasinya. Di kantor tempat saya bekerja sebelumnya, banyak orang-orang hebat, yang kehebatannya baru disadari perusahaan setelah orang tersebut resign. Sungguh suatu kesia-siaan yang besar. Saya tidak tahu apakah perusahaan tidak bisa mengenali orang hebat, tidak mau mengapresiasi orang hebat, atau tidak mampu mengapresiasi dengan layak. Yang saya tahu hanya satu, orang hebat satu persatu meninggalkan perusahaan.</p>
<p>Jadi, perusahaan harus selalu mengamati perkembangan karyawannya. Sadari bahwa ulat sudah menjadi kupu-kupu. Kemudian berikan kompensasi yang sesuai.</p>
<p>Kompensasi tidak selalu berimplikasi naik gaji. Banyak kompensasi non-gaji yang bisa digunakan, diantaranya:</p>
<ul>
<li>
<p>menambah jatah cuti</p>
</li>
<li>
<p>mengirim karyawan ikut event</p>
</li>
<li>
<p>mengikutkan karyawan pelatihan</p>
</li>
<li>
<p>mendorong karyawan ikut dalam kegiatan komunitas</p>
</li>
<li>
<p>mengupgrade komputernya</p>
</li>
<li>
<p>dan masih banyak teknik lainnya, asal Anda kreatif saja.</p>
</li>
</ul>
<p>Kesalahan berikutnya, perusahaan, terutama startup, sering menganggap karyawan ber-etos kerja seperti founder. Karena foundernya kerja 10 jam sehari tanpa lembur, maka dianggap karyawannya juga akan rela melakukan hal serupa. Kalau foundernya Sabtu Minggu ngantor dengan gembira, dianggapnya karyawan juga harus ngantor di masa weekend sambil tersenyum. Bila client telat bayar, karyawan diharapkan akan mengerti.</p>
<p>Ini pemahaman yang salah. Karyawan tidak memiliki keterkaitan emosi terhadap perusahaan sekuat foundernya. Mereka punya kehidupan sendiri dan cita-cita sendiri. Perusahaan harus adil, kalau kerja lembur, berikanlah apresiasi yang sesuai. Berikan uang lembur, atau tambahkan jatah cutinya.</p>
<p>Sebagai penutup, saya menganggap perusahaan IT sama seperti klub sepakbola profesional, seperti Inter Milan atau Arsenal. Posisinya di klasemen, kekayaannya, jumlah fansnya, semua terutama ditentukan oleh siapa pelatihnya, dan siapa pemainnya. Tidak memperhatikan karyawan adalah langkah pertama menuju kegagalan.</p>
Kandidat Java vs PHP2008-02-07T00:04:44+07:00https://software.endy.muhardin.com/life/kandidat-java-vs-php<p>Disclaimer : kondisi dan pengalaman Anda SANGAT MUNGKIN BERBEDA.
Jadi jangan bilang saya salah … ini pengalaman pribadi.
Pengalaman Anda boleh saja berbeda, dan sangat dianjurkan untuk sharing.</p>
<blockquote>
<p>Belakangan ini banyak yang nyari Programmer PHP yah :-d</p>
</blockquote>
<p>Demikian tanggapan moderator milis JUG-Indonesia.</p>
<p>Saya mau sharing pengalaman sedikit tentang rekrutasi ArtiVisi beberapa hari yang lalu.
Rate salary di lowongan kemarin itu 2-3 juta rupiah, looking for PHP Programmer.</p>
<p>Ternyata, dengan rate salary segitu, para kandidat sudah mampu ‘melaju ke babak playoff’.
Begini maksudnya.</p>
<p>Kalau interview, saya selalu mengajukan pertanyaan yang makin lama makin sulit.
Job seeker, perhatikan ini, Endy’s interview style.</p>
<ul>
<li>Urusan coding standar. Percabangan dan perulangan.Misalnya:
<ul>
<li>tampilkan nama anda sebanyak jumlah hurufnya. Kalau namanya Endy, tampilkan endy endy endy endy. Kalau namanya Dhiku, tampilkan dhiku dhiku dhiku dhiku dhiku.</li>
<li>dengan input bulan dan tahun, buat function/method untuk menghitung jumlah harinya</li>
</ul>
</li>
<li>Topik-topik populer
<ul>
<li>HTML syntax</li>
<li>Tableless layout with CSS</li>
<li>SQL Injection</li>
</ul>
</li>
<li>
<p>Setelah itu masalah yang membutuhkan imajinasi, misalnya perbedaan pass by value dan pass by reference</p>
</li>
<li>Kalau masih lolos juga, matakuliah CS yang biasanya bikin ngantuk
<ul>
<li>Struktur Data</li>
<li>Algoritma tingkat menengah (tree, sorting, dsb)</li>
<li>Automata / Finite State Machine 5. Baru kemudian pertanyaan tentang wawasan</li>
<li>Primary Operating System, dan Secondary OS, yang biasa digunakan</li>
</ul>
</li>
</ul>
<p>Cuma pernah pakai Windows??? Hmm … terima kasih atas waktunya, nanti akan saya hubungi lagi.
Tidak pernah pakai OS selain FreeBSD?? Hmm … menarik juga … mari kita tanya lebih lanjut,</p>
<blockquote>
<p>Kamu sekolah TK di mana?</p>
</blockquote>
<p>Saya pernah posting tentang kandidat ideal menurut saya <a href="http://software.endy.muhardin.com/life/pengetahuan-wajib-buat-programmer/">di sini</a>.</p>
<p>Lalu banyak yang berkomentar tentang betapa sulitnya persyaratan tersebut.</p>
<p>Nah …kembali ke pertanyaan Joshua … kenapa sekarang banyak cari PHP Programmer?</p>
<p>Well … berdasarkan pengalaman saya, dengan tawaran 2-3 juta,
para kandidat programmer PHP ini umumnya mampu sampai pertanyaan 3.
Beberapa ada yang bisa jawab sampai nomer 4.
Belum ada yang sampai 5.</p>
<p>Bagaimana dengan koleganya, kandidat programmer Java?
Menyedihkan ….
Bahkan no 2 pun banyak yang gak bisa jawab.
Fresh graduate Java programmer, berdasarkan survei yang tidak serius dan tidak bisa dipertanggungjawabkan metodologinya, apalagi hasilnya, menyatakan bahwa mereka mengharapkan gaji setidaknya 3-4 juta.</p>
<p>Jadi … kalau saya punya budget 3-4 juta, lalu buka lowongan, bandingkan apa yang akan saya peroleh.</p>
<p>** PHP Programmer **</p>
<ul>
<li>Berpengalaman 2-3 tahun, sudah tahu sopan santun kerja di kantor</li>
<li>Bisa HTML</li>
<li>Bisa CSS, lengkap dengan div, span, bisa bikin table-less layout</li>
<li>Bisa AJAX, low level lagi pakai prototype.js atau whatever library JavaScript yang sedang trend</li>
<li>Ngerti konsep HTTP request-response, session, cookie, upload file, dan urusan remeh-temeh HTTP lainnya</li>
<li>Kalau beruntung, mungkin bisa dapat yang ngerti SOAP segala</li>
<li>Hey, 4 juta cukup mahal … coba kita lihat mungkin dia ngerti Photoshop juga :D</li>
</ul>
<p>** Java Programmer **</p>
<ul>
<li>Fresh graduate, masih bergaya mahasiswa</li>
<li>Ngerti HTML seadanya, belum tentu ngerti perilakunya frameset</li>
<li>Gak bisa CSS, apalagi table-less layout</li>
<li>Forget about AJAX</li>
<li>Forget about low-level HTTP, servlet mapping di web.xml aja belum tentu ngerti</li>
<li>SOAP?? Buat mandi??</li>
<li>Photoshop atau Corel Draw .. hmm .. itu kan kerjaannya Web Designer. Saya gak ikut-ikut.</li>
</ul>
<p>Nah …. lalu apa pesan moral dari artikel ini?</p>
<ol>
<li>Freshmen Java harus lebih tahu diri. Kerjakan PR dulu baru apply. Dengan kondisi seperti di atas, saya lebih suka mempekerjakan PHP programmer lalu diajari Java</li>
<li>Industri PHP harus lebih mengapresiasi komunitasnya</li>
<li>Sebagai company-owner, harus tahu kondisi di berbagai dunia</li>
</ol>
Java Stack 20082008-01-24T16:59:36+07:00https://software.endy.muhardin.com/java/stack-2008-1<p>Di milis Java sedang dibahas tentang kombinasi teknologi yang digunakan untuk membuat aplikasi Java.</p>
<p>Ada beberapa kategori teknologi, yaitu:</p>
<ul>
<li>
<p>Presentation Layer. Yaitu teknologi untuk membangun User Interface</p>
</li>
<li>
<p>Business Layer. Yaitu teknologi untuk menyediakan logika proses bisnis.</p>
</li>
<li>
<p>Data Access Layer. Yaitu teknologi untuk mengakses penyimpanan data, misalnya database.</p>
</li>
<li>
<p>Security Framework. Teknologi untuk mengelola otentikasi dan otorisasi.</p>
</li>
<li>
<p>Build System. Sistem untuk mengotomasi proses build.</p>
</li>
<li>
<p>Testing System. Sistem untuk mengotomasi pengetesan aplikasi.</p>
</li>
<li>
<p>Application Server</p>
</li>
<li>
<p>Database Server</p>
</li>
<li>
<p>Project Management Tools. Perangkat pembantu untuk mengelola project.</p>
</li>
</ul>
<p>Berikut adalah stack yang saya gunakan, per Januari 2008. Sesuai perkembangan jaman, mungkin sekali stack ini akan berubah.</p>
<h3 id="presentation-layer">Presentation Layer</h3>
<p>Tergantung tujuan developmentnya. Kalau butuh rapid, saya pakai JSF 1.2 dengan komponen RichFaces. Kalau ingin sederhana dan fleksibel, saya pakai Spring MVC.</p>
<p>Sebagai tambahan, sekarang saya sedang mengeksplorasi Spring Web Flow untuk mengelola state aplikasi. Framework ini bisa digunakan baik untuk JSF maupun Spring MVC.</p>
<p>Baik dalam Spring MVC maupun JSF, kita dapat memilih View Engine yang digunakan. Kebanyakan orang tidak tahu, atau tidak mau repot, atau mungkin juga merasa nyaman menggunakan JSP. Tapi saya tidak. Saya lebih suka pakai Velocity atau Freemarker untuk Spring MVC, dan pakai Facelets di JSF.</p>
<h3 id="business-layer">Business Layer</h3>
<p>Saya menggunakan Spring Framework untuk mengorkestrasi transaction, integrasi dengan berbagai data access layer, messaging system, dan sebagainya.</p>
<h3 id="data-access-layer">Data Access Layer</h3>
<p>Bila ingin rapid development, saya pakai Hibernate. Bila ingin main low level SQL, saya pakai Spring JDBC.</p>
<p>Saya tidak pakai JPA. Dulu pernah pakai iBatis, tapi belakangan sepertinya sudah cukup dengan Spring JDBC saja.</p>
<h3 id="security-framework">Security Framework</h3>
<p>Pakai Spring Security. Fiturnya cukup lengkap dan desainnya sudah teruji di skenario dunia nyata.</p>
<h3 id="build-system">Build System</h3>
<p>Orang-orang pada pakai Maven. Saya sudah merasa cukup pakai Ant saja.</p>
<h3 id="testing-system">Testing System</h3>
<p>Saat ini pakai JUnit 4 dan DBUnit. Tapi sedang eksplorasi TestNG. Saya tertarik dengan kemampuan test-grouping. Ini penting apabila kita ingin memisahkan test yang harus dieksekusi programmer sebelum dia commit (sehingga harus cepat), dan test yang dieksekusi oleh tools Continuous Integration saja (karena butuh waktu lama).</p>
<p>Test sebelum commit contohnya adalah unit test dan integration test. Selain itu, ada test yang butuh waktu lama, misalnya test End of Day/Month/Year process, dan test simulasi kinerja aplikasi dengan data satu tahun kedepan.</p>
<p>Untuk coverage test, saya gunakan Cobertura. Sedangkan untuk Static Code Analysis saya gunakan PMD.</p>
<p>Ke depan, saya sedang menganalisa efektifitas penggunakan JDepend untuk memastikan dependensi antar modul yang sudah ditentukan di awal tidak dilanggar programmer lain. Misalnya, tidak ada urusannya presentation layer melakukan import HibernateException. Nah, ini sepertinya bisa dienforce oleh JDepend, tapi belum sempat saya coba.</p>
<h3 id="application-server">Application Server</h3>
<p>Saya gunakan Winstone untuk tutorial dan pelatihan. Untuk development saya gunakan Tomcat. Untuk production saya gunakan Glassfish.</p>
<h3 id="database-server">Database Server</h3>
<p>Untuk development dan production, saya menggunakan PostgreSQL. Untuk pelatihan dan tutorial, saya gunakan MySQL.</p>
<h3 id="project-management-tools">Project Management Tools</h3>
<p>Untuk melakukan tracking terhadap:</p>
<ul>
<li>
<p>implementasi fitur</p>
</li>
<li>
<p>resiko dalam project</p>
</li>
<li>
<p>masalah dalam project</p>
</li>
<li>
<p>penyelesaian bug</p>
</li>
<li>
<p>perbaikan dokumen berdasarkan review/audit</p>
</li>
</ul>
<p>saya menggunakan Redmine.</p>
<p>Untuk membantu proses Configuration Management, saya menggunakan Subversion. Kadang-kadang dibantu dengan SVK bila ingin kerja offline.</p>
<p>Sebelum saya tutup artikel ini, pasti nanti ada yang ingin berkomentar seperti ini,</p>
<blockquote>
<p>Wuah, banyak sekali persenjataannya. Kok repot-repot sekali sih?</p>
</blockquote>
<p>Di sebelah rumah saya, tetangga sedang membangun rumah. Para pekerjanya (2-3 orang) cukup bermodalkan cangkul untuk mengaduk semen. Bila mereka ingin mengecor lantai dua, cukup membuat bekisting dari triplek dan papan bekas, yang setelah pakai bisa langsung dibuang. Sekali-sekali, saya melihat mereka menggotong perkakas dan bahan baku bila lokasi kerjanya pindah.</p>
<p>Di depan jendela saya di kantor, sedang dibangun gedung perkantoran yang baru. Untuk mengaduk semen, tidak pakai cangkul, melainkan dituang dari truk molen. Tim pekerjanya (200-300 orang) punya cetakan/bekisting khusus yang reusable untuk mengecor tiang dan lantai. Bila ada perkakas atau bahan baku yang perlu dipindahkan, mereka tidak gotong sendiri. Untuk keperluan itu, ada alat sendiri yang namanya <a href="http://science.howstuffworks.com/tower-crane.htm">tower crane</a>. Bahkan ada pilotnya khusus.</p>
<p>Nah, itu analoginya. Silahkan simpulkan sendiri, kenapa harus repot-repot.</p>
Aplikasi Web dengan Spring 2.5 [bagian 6]2008-01-19T17:24:03+07:00https://software.endy.muhardin.com/java/aplikasi-web-spring25-6<p>Pada artikel kali ini, kita akan mengurusi masalah sepele tapi penting, yaitu Internationalization (i18n) dan Localization (l10n). i18n adalah menyiapkan aplikasi kita supaya bisa beradaptasi dengan berbagai bahasa, format penomoran, mata uang, dan hal-hal lain yang berkaitan dengan globalisasi.</p>
<p>Para pembaca mungkin ada yang bertanya,</p>
<blockquote>
<p>Ah, aplikasi saya tidak perlu multibahasa kok. Saya bikin dalam bahasa Inggris, dan tidak akan diubah-ubah lagi.</p>
</blockquote>
<p>Baiklah, mungkin aplikasi kita tidak akan berganti bahasa. Tapi mungkin sekali akan terjadi banyak revisi tampilan. Kita sebagai programmer mungkin mahir berbahasa PHP, Java, Ruby, atau bahasa-bahasa komputer lainnya. Tetapi belum tentu kita dapat menyaingi seorang JS Badudu dalam urusan Bahasa Indonesia.</p>
<p>Oleh karena itu, penting untuk menyiapkan aplikasi kita agar setidaknya tulisan yang dilihat user bisa diganti dengan mudah.</p>
<p>Mari kita lihat template sederhana berikut.</p>
<h3 id="personlisthtml">personlist.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>:: List of All Person ::</title>
</head>
<body>
<h1>List of All Person</h1>
<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>
</code></pre></div></div>
<p>Seperti kita lihat, ada beberapa tulisan yang muncul di situ, yaitu:</p>
<ul>
<li>Judul halaman</li>
<li>Nama kolom pada tabel : Nama, Email</li>
<li>link untuk edit dan lihat</li>
</ul>
<p>Spring sudah memiliki dukungan built-in untuk mengeluarkan tulisan tersebut ke file text. Nantinya kita bisa edit file tersebut dengan mudah. Kita juga bisa punya beberapa file dalam berbagai bahasa, sehingga aplikasi dapat mendeteksi setting regional komputer, dan kemudian menampilkan bahasa yang sesuai.</p>
<p>Tapi untuk artikel ini, cukuplah kita mengeluarkan tulisan itu ke file text. Fitur-fitur tambahan lainnya silahkan dieksplorasi sendiri oleh pembaca.</p>
<p>Kita menggunakan tag <code class="language-plaintext highlighter-rouge">#springMessage</code> untuk mengeluarkan tulisan tersebut. Tag ini khusus untuk Velocity. Bila Anda menggunakan JSP atau Freemarker, silahkan lihat referensi Spring.</p>
<p>Template yang sudah diubah terlihat menjadi seperti ini.</p>
<h3 id="personlisthtml-1">personlist.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>:: #springMessage("personlist.title") ::</title>
</head>
<body>
<h1>#springMessage("personlist.title")</h1>
<table border="0" cellpadding="2" cellspacing="2">
<tr>
<th>#springMessage("personlist.name")</th>
<th>#springMessage("personlist.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">#springMessage("list.edit")</a> | <a
href="persondetail?person_id=$person.Id">#springMessage("list.view")</a></td>
</tr>
#end
</table>
</body>
</html>
</code></pre></div></div>
<p>Tag <code class="language-plaintext highlighter-rouge">#springMessage</code> membutuhkan satu argumen, yaitu nama yang kita berikan untuk tulisan tersebut. Ini akan menjadi jelas setelah kita lihat file text yang menampung tulisan tersebut, misalnya saya beri nama <code class="language-plaintext highlighter-rouge">messages.properties</code>.</p>
<h3 id="messagesproperties">messages.properties</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>list.edit = edit
list.view = lihat
personlist.title = Daftar Orang
personlist.name = Nama
personlist.email = Alamat Email
</code></pre></div></div>
<p>Setelah file tersebut siap, kita konfigurasi Spring agar membaca file tersebut. Lokasi konfigurasinya ada di <code class="language-plaintext highlighter-rouge">tutorial-servlet.xml</code>. Berikut isinya.</p>
<h3 id="tutorial-servletxml">tutorial-servlet.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="messages" />
</code></pre></div></div>
<p>Nah, silahkan deploy ulang aplikasinya. Sekarang semua tulisan yang tampil sudah bisa dikonfigurasi melalui text file.</p>
<p>Selanjutnya, bagaimana mengakses file tulisan tersebut dari kode Java? Kita membutuhkannya untuk menampilkan pesan error dari hasil import data.</p>
<p>Mudah saja, langsung saja gunakan object <code class="language-plaintext highlighter-rouge">messageSource</code> yang sudah kita deklarasikan tadi. Kita dapat mendapatkan object tersebut dengan menggunakan dependency injection. Berikut adalah kode PersonCSVParser yang akan menggunakan <code class="language-plaintext highlighter-rouge">messageSource</code>.</p>
<h3 id="personcsvparserjava">PersonCSVParser.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.spring25.helper;
@Component
public class PersonCSVParser {
private MessageSource messageSource;
@Autowired(required=true)
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
@SuppressWarnings("unchecked")
public void parse(List<String> data, List<Person> result, List<ParseError> errors) {
-- snip --
// check for malformed data
if(tokenizer.countTokens() != PERSON_NUM_FIELDS) {
errors.add(new ParseError(
counter, text,
messageSource.getMessage(
"parseerror.reason.malformed",
new Object[]{2,tokenizer.countTokens()},
Locale.getDefault()
)
));
}
-- snip --
if(bindErrors.hasErrors()) {
List<ObjectError> bindErrorContent = bindErrors.getAllErrors();
for (ObjectError objectError : bindErrorContent) {
errors.add(new ParseError(
counter, text,
messageSource.getMessage(
objectError.getCode(),
objectError.getArguments(),
Locale.getDefault()
)
));
}
}
result.add(person);
}
}
</code></pre></div></div>
<p>Nah, mudah bukan? Karena itu, dari hari pertama coding, langsung saja siapkan aplikasi Anda untuk i18n.</p>
Aplikasi Web dengan Spring 2.5 [bagian 5]2008-01-18T16:15:08+07:00https://software.endy.muhardin.com/java/aplikasi-web-spring25-5<p>Aplikasi web –berbeda dengan aplikasi desktop– ditakdirkan untuk stateless. Artinya dia tidak menyimpan data untuk masing-masing user yang sedang aktif. Keputusan ini menyebabkan aplikasi web bisa diakses jutaan user sekaligus. Tapi juga menyebabkan perlu teknik khusus agar data user yang sedang aktif dapat disimpan dengan baik.</p>
<p>Tanpa kemampuan penyimpanan state, semua data yang dikirim user akan hilang setelah dia pindah halaman. Misalnya user:</p>
<ol>
<li>mengisi form 1, kemudian menekan tombol Next</li>
<li>mengisi form 2, kemudian menekan tombol Next</li>
<li>tiba di form 3. Pada saat ini, data yang diisi di form 1 sudah hilang</li>
</ol>
<p>Sebelum kita melihat kode program dengan Spring 2.5, terlebih dulu kita bahas konsepnya.</p>
<p>Data user yang kita bicarakan di sini adalah data sementara. Contoh klasik dari state user yang harus disimpan antara lain:</p>
<ul>
<li>kita berbelanja online. Barang-barang yang sudah kita pilih, tapi belum kita pesan, ini harus disimpan oleh aplikasi. Data ini sering disebut dengan istilah shopping cart (atau troli belanja)</li>
<li>kita sudah login untuk melihat email. Selama kita belum menyatakan selesai (dengan cara logout), kita ingin aplikasi menyimpan username dan password kita. Sehingga kita tidak perlu memasukkan username dan password setiap kali pindah dari satu email ke email lain.</li>
<li>kita mengisi form secara bertahap (wizard-style). Keseluruhan rangkaian proses baru berakhir pada saat form terakhir diisi. Tanpa state management yang baik, kita tidak bisa menggunakan rangkaian form. Kita terpaksa menggunakan satu form yang sangat panjang, dan menyuruh user mengisinya sekaligus dalam satu kali proses.</li>
</ul>
<p>Contoh yang kita sebutkan di atas membahas tentang data sementara yang harus dikelola sebelum disimpan ke database. Umurnya relatif lebih pendek daripada data permanen yang disimpan di database. Misalnya, username dan password selama user membaca email, disimpan dalam jangka waktu beberapa jam saja. Data wizard mungkin hanya disimpan beberapa menit saja, sampai form terakhir selesai dan keseluruhan data disimpan ke database. Walaupun beberapa situs, seperti Amazon, mungkin saja menyimpan data shopping-cart kita selama berbulan-bulan.</p>
<p>Ada beberapa cara yang dapat kita pilih untuk menyimpan state. Setidaknya ada dua pilihan lokasi :</p>
<ul>
<li>di client. Dengan menggunakan mekanisme cookie, kita dapat menyimpan data di browser client. Cara ini tidak membebani server, tapi kapasitas cookie di client relatif kecil. Kita tidak bisa menyimpan data yang terlalu banyak di cookie.</li>
<li>di server. Mekanismenya mirip dengan kalau kita belanja di hypermarket. Tas dan jaket kita titipkan di pintu masuk, kemudian kita akan diberikan token. Pada waktu kita keluar, token kita tukarkan dengan barang yang kita titipkan.</li>
</ul>
<p>Demikian juga dengan penyimpanan state. Ketika user pertama kali datang, dia akan mendapat token atau session ID. Aplikasi menyimpan state user di lokasi tertentu yang ditandai dengan session ID tersebut. Setiap request yang datang akan dilihat session IDnya dan datanya diambil untuk diproses.</p>
<p>Ada beberapa cara untuk menitipkan token pada user, beberapa yang sering digunakan antara lain:</p>
<ul>
<li>masukkan ke cookie. Ya kita pakai cookie lagi, tapi tidak menyimpan keseluruhan data state, melainkan cuma session ID saja.</li>
<li>disisipkan di setiap link dan form. URL yang tadinya seperti ini : <code class="language-plaintext highlighter-rouge">http://localhost/aplikasi/personlist</code> menjadi seperti ini: <code class="language-plaintext highlighter-rouge">http://localhost/aplikasi/personlist?session_id=12a75tj67</code>.</li>
<li>dikirim ke server sebagai HTML input. Biasanya tipe yang dipilih adalah <em>hidden</em>. Cara ini memiliki kelemahan, karena tidak semua request bisa dibungkus dalam form.</li>
</ul>
<p>Data state yang disimpan di server juga memiliki beberapa pilihan lokasi penyimpanan, antara lain:</p>
<ul>
<li>di webserver. Webserver menulis satu file per satu session. Semua data disimpan di sini. Cara ini paling mudah dikonfigurasi, sehingga banyak webserver menjadikan cara ini sebagai default. Kelemahannya, metode ini menyulitkan kita untuk mengcluster webserver, karena kita kemudian harus memikirkan bagaimana mensinkronisasikan data session di masing-masing anggota cluster</li>
<li>di database. Webserver dikonfigurasi agar menyimpan datanya di database. Cara ini cukup sulit, mengingat dialek SQL masing-masing database berbeda. Banyak vendor webserver tidak mau menulis adapter untuk masing-masing merek database, sehingga kita harus periksa dulu apakah database yang kita gunakan didukung atau tidak. Bila tidak, kita terpaksa menulis sendiri kode program untuk state management.</li>
<li>di distributed cache. Ada beberapa aplikasi cache yang mengkhususkan diri untuk menyimpan data secara terdistribusi, misalnya <a href="name=">memcached</a> yang open source atau <a href="http://www.oracle.com/technology/products/coherence/index.html">Oracle Coherence</a> yang berbayar. Penyimpanan ini bisa dicluster sendiri, terpisah dari webserver. Dengan pendekatan ini, kita bisa mengcluster webserver sesuka hati, karena statenya tidak disimpan webserver, melainkan dititipkan di distributed cache. Kelemahan cara ini sama dengan metode database. Tidak semua webserver menyediakan support. Kadang kita harus tulis kodenya sendiri.</li>
<li>di upstream layer. Beberapa orang menggunakan arsitektur empat layer : client - web layer - application layer - database. Dengan arsitektur ini, semua state disimpan di application layer. Biasanya server yang digunakan untuk application layer memiliki mekanisme state management yang lebih baik daripada webserver. Terutama jika kita ingin melakukan clustering.</li>
</ul>
<p>Sekarang setelah kita mengerti konsepnya, mari kita implementasikan pada contoh aplikasi kita. Kita akan menggunakan state management untuk menampilkan hasil upload data Person.</p>
<p>Kode upload kita kemarin bekerja sebagai berikut :</p>
<ol>
<li>user melakukan upload dengan mensubmit form</li>
<li>server memproses, kemudian mengirim redirect ke halaman berikut</li>
<li>browser menerima perintah redirect, kemudian melakukan request GET ke URL lain sesuai perintah redirect</li>
<li>server menerima request GET, kemudian melakukan proses dan merender hasilnya</li>
<li>browser menampilkan respon dari server</li>
</ol>
<p>Teknik ini sering disebut Post-Redirect-Get (PRG) pattern.</p>
<p>Kesalahan umum programmer web pemula adalah memproses form sebagai berikut:</p>
<ol>
<li>user melakukan upload dengan mensubmit form</li>
<li>server memproses, kemudian merender hasilnya</li>
<li>browser menampilkan respon dari server</li>
</ol>
<p>Bila user merefresh halaman hasil, maka yang dilakukan browser adalah mengulang dari langkah 1. Ini akan mengakibatkan form tersubmit dua kali. Berbeda dengan teknik PRG, kalau user merefresh halaman hasil, maka browser akan mengulangi dari langkah ketiga, yaitu GET untuk halaman hasil. Dengan demikian form tidak disubmit dua kali.</p>
<p>Kita menghadapi tantangan untuk mengimplementasikan PRG pada halaman upload result. Kalau hanya sekedar pesan sukses, kita bisa membuat redirect ke <code class="language-plaintext highlighter-rouge">http://localhost/aplikasi/personuploadresult?msg=Sukses</code>. Tapi yang ingin kita tampilkan adalah daftar yang memuat mana data yang sukses diimport, dan mana data yang gagal. Sulit untuk dikirim melalui URL.</p>
<p>Untuk itu, kita harus simpan data tersebut di session. Berikut adalah kode pemrosesan upload yang sudah dimodifikasi.</p>
<h3 id="personuploadcontrollerjava">PersonUploadController.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.spring25.ui.springmvc;
@Controller
@RequestMapping("/personuploadform")
public class PersonUploadController {
private static final Log LOG = LogFactory.getLog(PersonUploadController.class);
private static final String REDIRECT_PERSONUPLOADRESULT = "redirect:personuploadresult";
@RequestMapping(method=RequestMethod.POST)
public String processForm(@RequestParam("persondata") MultipartFile file, final HttpSession session) {
// parse file into list of strings
List<String> contents = new ArrayList<String>();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()));
String content = reader.readLine();
while(content != null) {
contents.add(content);
content = reader.readLine();
}
reader.close();
} catch (IOException e) {
LOG.warn(e.getMessage(), e);
}
// parse list of strings into list of Persons
List<Person> persons = new ArrayList<Person>();
List<ParseError> errors = new ArrayList<ParseError>();
personDataParser.parse(contents, persons, errors);
for (Person person : persons) {
personDao.save(person);
}
session.setAttribute(Constants.PERSONUPLOAD_ERRORS_KEY, errors);
session.setAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY, persons);
return REDIRECT_PERSONUPLOADRESULT;
}
}
</code></pre></div></div>
<p>Perhatikan dua baris <code class="language-plaintext highlighter-rouge">session.setAttribute</code> di bagian bawah.
Setelah menyimpan data di session, kita redirect ke halaman hasil.</p>
<p>Pada halaman hasil, terlebih dulu kita pindahkan data hasil upload dari session ke request. Kemudian data yang ada di session kita hapus, agar tidak membebani memori ataupun kapasitas penyimpanan server. Lalu baru kita render tampilannya.</p>
<p>Berikut adalah kode untuk memindahkan data dari session ke request. Terletak di class <code class="language-plaintext highlighter-rouge">PersonController</code>, dalam method <code class="language-plaintext highlighter-rouge">uploadResult</code>.</p>
<h3 id="personcontroller">PersonController</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.spring25.ui.springmvc;
@Controller
public class PersonController {
@RequestMapping("/personuploadresult")
public ModelMap uploadResult(final HttpSession session){
final ModelMap modelMap = new ModelMap();
if (session.getAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY) != null) {
modelMap.addAttribute(
session.getAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY)
);
session.removeAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY);
}
if (session.getAttribute(Constants.PERSONUPLOAD_ERRORS_KEY) != null) {
modelMap.addAttribute(
session.getAttribute(Constants.PERSONUPLOAD_ERRORS_KEY)
);
session.removeAttribute(Constants.PERSONUPLOAD_ERRORS_KEY);
}
return modelMap;
}
}
</code></pre></div></div>
<p>Dan ini adalah template yang digunakan untuk menampilkan hasil upload.</p>
<h3 id="personuploadresulthtml">personuploadresult.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head><title>:: Person Upload Result ::</title></head>
<body>
<h1>Person Upload Result</h1>
<h2>Successfully Imported Person Data</h2>
#if (!$personList || ${personList.isEmpty()})
<h3>No Data</h3>
#else
<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?id=$person.Id">edit</a> |
<a href="persondetail?id=$person.Id">view</a>
</td>
</tr>
#end
</table>
#end
<h2>Erroneous Data</h2>
#if (!$parseErrorList || ${parseErrorList.isEmpty()})
<h3>No Data</h3>
#else
<table border="0" cellpadding="2" cellspacing="2">
<tr>
<th>Row</th>
<th>Data</th>
<th>Reason</th>
</tr>
#foreach($error in $parseErrorList)
<tr>
<td>$error.Line</td>
<td>$error.Text</td>
<td>$error.Reason</td>
</tr>
#end
</table>
#end
</body>
</html>
</code></pre></div></div>
<p>Di framework Ruby on Rails, sudah ada dukungan untuk kegiatan ini, namanya flash message. Flash message akan menyimpan data di session, kemudian menghapusnya pada request berikutnya.</p>
<p>Pada artikel ini kita sudah melakukan state management sederhana. Untuk kemampuan yang lebih canggih seperti pengelolaan shopping cart atau wizard, kita bisa menggunakan framework Spring Web Flow. Dengan SWF, flow aplikasi bisa diedit secara grafis. Ini akan memudahkan kita untuk mendokumentasikan flow navigasi aplikasi.</p>
<p><a href="/images/uploads/2008/01/webflow_editor.png"><img src="/images/uploads/2008/01/webflow_editor.png" alt="Spring Web Flow Editor " /></a></p>
<p>Gambar diambil dari <a href="http://springide.org/project/wiki/WebFlowEditorUsage">situsnya SWF</a>.</p>
Another Subversion Backup Script2008-01-18T01:05:30+07:00https://software.endy.muhardin.com/aplikasi/svn-parentpath-backup<p>Dulu saya pernah menulis tentang <a href="http://endy.artivisi.com/blog/aplikasi/subversion-backup-dan-restore/">backup script Subversion untuk Linux</a>, maupun <a href="http://endy.artivisi.com/blog/aplikasi/subversion-backup-script-untuk-windows/">untuk Windows</a>. Sayangnya, script tersebut hanya bisa digunakan untuk satu repository saja.</p>
<p>Biasanya saya menghosting beberapa Subversion repository sekaligus, dipublikasikan menggunakan Apache dengan konfigurasi <code class="language-plaintext highlighter-rouge">SVNParentPath</code>. Setidaknya ada 10 repository yang saya kelola, sehingga untuk mengkonfigurasi backup otomatisnya cukup melelahkan juga.</p>
<p>Oleh karena itu, saya membuat backup script lagi. Kali ini mampu menangani satu folder yang berisi banyak repository. Berikut scriptnya, masih dalam bahasa Ruby.</p>
<h3 id="subversion-repos-full-backuprb">subversion-repos-full-backup.rb</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require 'zlib'
if ARGV.length < 2
puts "Usage : ruby full-backup.rb <SVNParentPath folder> <backupfolder>"
exit
end
# some configuration
svn_parent_path = ARGV[0]
backup_folder = ARGV[1]
# variable initialization
current_date = Time.now.strftime("%Y%m%d")
Dir.foreach(svn_parent_path) { |repo|
next if('.' == repo || '..' == repo)
puts "Start to process folder : "+repo
puts "Performing svndump"
repo_name = svn_parent_path + File::SEPARATOR + repo
dumpfile_name = repo + '-' +current_date + '.dmp'
dumpfile = backup_folder + File::SEPARATOR + dumpfile_name
`svnadmin dump #{repo_name} > #{dumpfile}`
puts "Compressing dumpfile"
Zlib::GzipWriter.open(dumpfile+".gz") do |gz|
gz.write(File.read(dumpfile))
end
puts "Deleting uncompressed file"
File.delete(dumpfile)
}
</code></pre></div></div>
<p>Cara menjalankannya tidak sulit, cukup panggil dia melalui command prompt:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby subversion-repos-full-backup.rb /path/ke/repos /path/ke/backup-folder
</code></pre></div></div>
<p>Semoga bermanfaat</p>
Aplikasi Web dengan Spring 2.5 [bagian 4]2008-01-17T17:21:58+07:00https://software.endy.muhardin.com/java/aplikasi-web-spring25-4<p>Belajar membuat aplikasi web belum lengkap tanpa tahu caranya mengupload file dan mengelola state. Pada artikel ini kita akan belajar tentang cara menangani upload file dengan Spring MVC versi 2.5. Di artikel selanjutnya baru kita akan bahas tentang state management.</p>
<p>Studi kasus kita kali ini sederhana saja. Kita sudah punya aplikasi buku alamat sederhana pada rangkaian artikel sebelumnya. Kita sudah bisa <a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-1/">menampilkan daftar data orang</a>, <a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-2/">mengedit data yang sudah ada atau menambah data baru</a>, serta <a href="http://software.endy.muhardin.com/java/aplikasi-web-spring25-3/">menggunakan template untuk header dan footer</a>. Kali ini kita akan membuat fasilitas import data berupa text file berformat Comma Separated Value (CSV).</p>
<p>File yang akan kita import kira-kira berbentuk seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Endy Muhardin,endy.muhardin@gmail.com
Hadikusuma Wahab,dhiku@artivisi.com
Ifnu Bima,ifnubima@gmail.com
</code></pre></div></div>
<p>Setelah file tersebut diupload, kita akan memasukkan masing-masing data ke dalam database. Setelah itu, user akan kita arahkan ke halaman daftar data orang, sehingga data yang baru saja diupload bisa dilihat.</p>
<p>Untuk melakukan upload, kita harus membuat HTML form dengan <em>encoding type</em> multipart form data. Artinya, data kita akan dikirim melalui HTTP POST dalam beberapa bagian (multipart). Kira-kira mekanismenya mirip dengan mengirim attachment melalui email.</p>
<p>Berikut adalah kode program HTMLnya.</p>
<h3 id="personuploadformhtml">personuploadform.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head><title>:: Upload Person Data ::</title></head>
<body>
<h1>Upload Person Data</h1>
<form method="POST" enctype="multipart/form-data">
<table>
<tr>
<td><label for="file">Person Data</label></td>
<td><input type="file" name="persondata"></td>
</tr><tr>
<td> </td>
<td><input type="submit" class="inputbutton" value="Upload"/></td>
</tr>
</table>
</form>
</body>
</html>
</code></pre></div></div>
<p>Perhatikan tag form. Di sana ada satu hal yang berbeda, yaitu <code class="language-plaintext highlighter-rouge">enctype="multipart/form-data"</code>. Semua aplikasi web yang ingin mengupload file harus membuat tag form seperti itu, apapun bahasa pemrograman yang digunakan. Ini sering menimbulkan kebingungan di kalangan pemula.</p>
<blockquote>
<p>Saya sudah ikuti semua instruksi, tapi kenapa file yang diupload tidak terbaca?</p>
</blockquote>
<p>Untuk menerima file, kita gunakan <code class="language-plaintext highlighter-rouge">input type="file"</code>.</p>
<p>Selanjutnya masuk ke kode Java. Untuk menampilkan dan memproses form ini, kita buatkan class PersonUploadController. Bentuknya mirip dengan PersonFormController yang kemarin sudah kita buat. Ada method untuk menampilkan halaman form, dan ada method untuk memproses data yang dikirim user. Berikut class PersonUploadController.</p>
<h3 id="personuploadcontrollerjava">PersonUploadController.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.spring25.ui.springmvc</span><span class="o">;</span>
<span class="nd">@Controller</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/personuploadform"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PersonUploadController</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Log</span> <span class="no">LOG</span> <span class="o">=</span> <span class="nc">LogFactory</span><span class="o">.</span><span class="na">getLog</span><span class="o">(</span><span class="nc">PersonUploadController</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="kd">private</span> <span class="nc">PersonCSVParser</span> <span class="n">personDataParser</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">PersonDao</span> <span class="n">personDao</span><span class="o">;</span>
<span class="nd">@Autowired</span><span class="o">(</span><span class="n">required</span><span class="o">=</span><span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setPersonDao</span><span class="o">(</span><span class="nc">PersonDao</span> <span class="n">personDao</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">personDao</span> <span class="o">=</span> <span class="n">personDao</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Autowired</span><span class="o">(</span><span class="n">required</span><span class="o">=</span><span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setPersonDataParser</span><span class="o">(</span><span class="nc">PersonCSVParser</span> <span class="n">personDataParser</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">personDataParser</span> <span class="o">=</span> <span class="n">personDataParser</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">method</span><span class="o">=</span><span class="nc">RequestMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ModelMap</span> <span class="nf">displayForm</span><span class="o">(){</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ModelMap</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">method</span><span class="o">=</span><span class="nc">RequestMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">processForm</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="s">"persondata"</span><span class="o">)</span> <span class="nc">MultipartFile</span> <span class="n">file</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// parse file into list of strings</span>
<span class="nc">List</span> <span class="n">contents</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">();</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">BufferedReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">()));</span>
<span class="nc">String</span> <span class="n">content</span> <span class="o">=</span> <span class="n">reader</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span>
<span class="k">while</span><span class="o">(</span><span class="n">content</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="s">""</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">content</span><span class="o">))</span> <span class="o">{</span>
<span class="n">content</span> <span class="o">=</span> <span class="n">reader</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span>
<span class="k">continue</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">contents</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">content</span><span class="o">);</span>
<span class="n">content</span> <span class="o">=</span> <span class="n">reader</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">reader</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="no">LOG</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// parse list of strings into list of Persons</span>
<span class="nc">List</span> <span class="n">persons</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">();</span>
<span class="nc">List</span> <span class="n">errors</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">();</span>
<span class="n">personDataParser</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">contents</span><span class="o">,</span> <span class="n">persons</span><span class="o">,</span> <span class="n">errors</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Person</span> <span class="n">person</span> <span class="o">:</span> <span class="n">persons</span><span class="o">)</span> <span class="o">{</span>
<span class="n">personDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">person</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="s">"redirect:personlist"</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Logika kode di atas tidak sulit. Untuk mendisplay form, praktis tidak ada yang perlu dilakukan. Kita cukup memberikan model kosong, karena form upload tidak membutuhkan data apa-apa.</p>
<p>Pada saat memproses form, kita menerima parameter MultipartFile. Ini adalah file yang sudah diparsing oleh Spring. Parameter ini diambil dari input form dengan nama <code class="language-plaintext highlighter-rouge">persondata</code>.</p>
<p>Supaya Spring bisa memisahkan file yang diupload dari keseluruhan HttpRequest, kita harus menyediakan resolver. Ada dua library yang biasa digunakan orang untuk memproses file upload, yaitu:</p>
<ul>
<li><a href="http://commons.apache.org/fileupload/">Jakarta Commons FileUpload</a></li>
<li><a href="http://www.servlets.com/cos/">COS (com.oreilly.servlet)</a></li>
</ul>
<p>Spring mendukung kedua library. Saya biasanya menggunakan Jakarta Commons. Untuk mengaktifkan dukungan ini, kita perlu menambahkan resolver di konfigurasi DispatcherServlet.</p>
<h3 id="tutorial-servletxml">tutorial-servlet.xml</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"multipartResolver"</span> <span class="na">class=</span><span class="s">"org.springframework.web.multipart.commons.CommonsMultipartResolver"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"maxUploadSize"</span> <span class="na">value=</span><span class="s">"1000000"</span><span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Setelah file didapat, kita lalu membaca isinya. Hanya operasi I/O standar di sini. Kita membuka <code class="language-plaintext highlighter-rouge">InputStream</code> yang dibungkus dengan <code class="language-plaintext highlighter-rouge">BufferedReader</code> yang memiliki method <code class="language-plaintext highlighter-rouge">readLine</code> yang praktis. Kemudian kita looping setiap baris. Jangan lupa untuk memeriksa baris kosong. Hasil pembacaan file kita simpan ke List<String> untuk pemrosesan selanjutnya.</String></p>
<p>Pemrosesan String menjadi Person kita lakukan di class PersonCSVParser supaya ada pembagian tanggung jawab yang jelas. Memisahkan parser di class tersendiri akan memudahkan kita untuk mengetes kode parser tersebut.</p>
<p>PersonCSVParser tidak ditampilkan di sini. Bagi yang ingin melihat kode programnya dapat langsung pergi ke <a href="https://github.com/endymuhardin/hello-spring-25/blob/master/src/java/tutorial/spring25/helper/PersonCSVParser.java">Github</a>. Demikian juga kelengkapannya, seperti:</p>
<ul>
<li><a href="https://github.com/endymuhardin/hello-spring-25/blob/master/src/java/test/spring25/helper/PersonCSVParserTest.java">PersonCSVParserTest</a></li>
<li><a href="https://github.com/endymuhardin/hello-spring-25/blob/master/fixtures/person.csv">person.csv</a> : sampel data test, semua data normal</li>
<li><a href="https://github.com/endymuhardin/hello-spring-25/blob/master/fixtures/person-with-malformed-emails.csv">person-with-malformed-emails.csv</a> : sampel data test, ada email yang formatnya salah</li>
</ul>
<p>Setelah data diproses menjadi kumpulan Person, kita looping lagi untuk menyimpan hasilnya ke database dengan menggunakan <code class="language-plaintext highlighter-rouge">personDao</code>.</p>
<p>Terakhir, setelah semua selesai, kita redirect ke halaman daftar orang. Seharusnya kita menampilkan hasil upload. Mana data yang error, dan mana data yang sukses diimpor. Tapi materi ini membutuhkan pengetahuan tentang session, yang akan kita bahas pada artikel mendatang.</p>
<p>Selamat mencoba.</p>
Continuous Integration2008-01-16T22:37:23+07:00https://software.endy.muhardin.com/manajemen/continuous-integration<p>Tools untuk Continuous Integration sudah sering dibahas di sini. Ada <a href="http://endy.artivisi.com/blog/java/cruise-control/">CruiseControl</a>, <a href="http://endy.artivisi.com/blog/java/luntbuild/">Luntbuild</a>, dan <a href="http://endy.artivisi.com/blog/java/hudson/">Hudson</a>. Apa yang begitu pentingnya dari proses ini sehingga saya meluangkan waktu untuk melihat setiap tools yang ada?</p>
<p>Continuous Integration, <a href="http://martinfowler.com/articles/continuousIntegration.html">menurut Martin Fowler</a>, begini:</p>
<blockquote>
<p>Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day.</p>
</blockquote>
<p>Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible.</p>
<p>Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly.</p>
<p>Ada beberapa keyword di sini:</p>
<ul>
<li>integrate their work</li>
<li>frequently</li>
<li>verified by automated build (including test)</li>
<li>detect errors as quickly as possible</li>
<li>significant reduced problems</li>
<li>develop .. rapidly</li>
</ul>
<p>Mari kita bahas lebih jauh.</p>
<p>Sebagai contoh, kita membuat aplikasi dengan bahasa pemrograman Java. Langkah-langkah yang harus dilakukan sampai suatu kode program bisa digunakan antara lain:</p>
<ol>
<li>tulis source code</li>
<li>kompilasi</li>
<li>coba dijalankan sendiri (unit-test)</li>
<li>coba gabungkan dengan kode lain (integration-test)</li>
<li>deploy / install</li>
<li>coba fitur-fiturnya</li>
<li>kalau semua OK, cepat-cepat diarsip</li>
</ol>
<p>Cukup banyak kegiatan yang harus dilakukan untuk menambah satu fitur. Penggunaan bahasa dinamik (seperti PHP atau Ruby) hanya mengurangi langkah kedua. Sisanya sama.</p>
<p>Kegiatan yang banyak ini akan sangat membosankan kalau dikerjakan secara manual. Bukan cuma boring, tapi juga rawan error. Di tengah tekanan deadline, sangat mungkin kita akan melewatkan test yang sudah pernah dijalankan sebelumnya.</p>
<blockquote>
<p>Hei, dulu saja jalan .. kenapa sekarang tidak?</p>
</blockquote>
<p>Ya belum tentu. Membangun aplikasi mirip seperti membangun rumah-rumahan dari kartu. Sedikit penambahan bisa merobohkan seluruh bangunan. Jadi sangat penting kita untuk sering-sering melakukan integrasi. Dan yang juga penting, test yang sudah ada harus dijalankan semuanya.</p>
<p>Sekarang sudah ada tools yang membantu kita melakukannya. Cukup investasi waktu 10 menit untuk melakukan instalasi dan konfigurasi. Investasi 10 menit ini akan menghasilkan:</p>
<ul>
<li>build scheduler</li>
<li>laporan kemajuan project setiap saat</li>
<li>notifikasi setiap ada error</li>
<li>arsip build</li>
</ul>
<p>Lalu apa manfaat dari fitur-fitur ini?</p>
<p>Banyak, mari kita bahas satu persatu.</p>
<h3 id="build-scheduler">Build Scheduler</h3>
<p>Semua kegiatan build kita tadi, mulai dari checkout dari version control sampai membuat installer aplikasi bisa dilakukan secara terjadwal. Misalnya satu jam sekali, satu hari tiga kali, dan sebagainya.</p>
<p>Kita juga bisa mengkonfigurasi macam-macam jadwal. Misalnya untuk test dengan data sedikit bisa dijalankan sering-sering. Test yang melibatkan data banyak –seperti test End Of Day process atau simulasi performance aplikasi dengan satu tahun data– bisa dijalankan di malam hari, waktu semua orang sedang tidur.</p>
<h3 id="laporan-kemajuan-project">Laporan Kemajuan Project</h3>
<p>Pernahkah Anda, sedang asyik coding, puluhan variabel beterbangan di dalam otak, tiba-tiba dipanggil oleh Project Manager.</p>
<blockquote>
<p>Fitur XX sudah selesai?</p>
</blockquote>
<p>Kita jawab, “Sudah”. Kemudian balik lagi ke kode program, hanya untuk menyadari bahwa seluruh konsentrasi sudah buyar, sehingga butuh waktu 10 menit untuk mengingat-ingat apa yang tadi sedang dikerjakan.</p>
<p>Dengan adanya tools Continuous Integration, Project Manager bisa langsung melihat kemajuan project di websitenya. Sehingga dia tidak perlu mengganggu konsentrasi orang lain.</p>
<h3 id="notifikasi-error">Notifikasi Error</h3>
<p>Misalnya kita mengubah nama kolom di database, ternyata menyebabkan fungsi-fungsi lain jadi error. Akan lebih cepat untuk memperbaiki hal ini jika antara kita mengubah nama kolom dan saat error ketahuan selisihnya singkat. Bagaimana cara supaya error cepat ketahuan? Ada dua cara:</p>
<ol>
<li>test suite harus lengkap, sehingga mencakup seluruh bagian kode.</li>
<li>test suite harus sering-sering dijalankan.</li>
</ol>
<p>Tools CI membantu kita di item #2. Tetapi test suite tetap harus kita yang membuatnya.</p>
<h3 id="arsip-build">Arsip Build</h3>
<p>Project Manager lagi-lagi mengganggu konsentrasi kita. Kali ini dia mau presentasi ke customer dan mau menunjukkan aplikasi terbaru. Sialnya, tadi pagi kita baru saja mulai mengganti framework, tadinya pakai <a href="http://www.icefaces.org/main/home/index.jsp">IceFaces</a> mau diganti jadi <a href="http://labs.jboss.com/jbossrichfaces/">RichFaces</a>. Wow, kenapa harus diganti?? Hmm .. sayangnya itu diluar pembahasan artikel ini.</p>
<p>Karena ada perombakan arsitektur besar-besaran, maka aplikasi saat ini sedang berantakan. Tidak mungkin kita memberikan aplikasi terbaru ini untuk dibawa presentasi. Bisa-bisa Project Manager kita mendapat malu karena bukannya mendemokan aplikasi, malah mendemokan stack trace.</p>
<p>Untuk mengambil versi sebelum dirombak juga belum tentu pekerjaan mudah. Tergantung apakah Anda menggunakan Subversion atau tidak. Dan apakah kita rajin membuat tag untuk menandai titik-titik penting.</p>
<p>Continuous Integration Tools to the rescue ….</p>
<p>Langsung saja buka arsip build. Cari build terakhir yang sukses, dan berikan pada bos. Jangan lupa diberi pesan, “Kapan-kapan kalau butuh yang seperti ini lagi, langsung saja buka URL ini ya.”</p>
<p>Bila proses CI ini berjalan lancar, hidup programmer jadi jauh lebih nyaman. Dia tinggal:</p>
<ul>
<li>coding</li>
<li>buat unit test dan integration test</li>
<li>jalankan dua-duanya</li>
<li>kalau OK, commit</li>
<li>balik lagi ke coding</li>
</ul>
<p>Sekarang kita kembali ke daftar keywordnya Martin Fowler di atas. Agar proses CI ini bisa berjalan baik, semua orang harus <em>integrate their work</em>. Setiap kode yang ditulis harus segera digabungkan dengan kode yang lainnya (yang ditulis anggota tim lainnya).</p>
<p>Ini harus dilakukan <em>frequently</em>, sering-sering. Ini agar error yang muncul bisa segera terdeteksi.</p>
<p>Seluruh kegiatan mulai dari kompilasi sampai <a href="http://endy.artivisi.com/blog/java/membuat-installer-dengan-izpack/">bikin installer</a> harus diotomasi. Kalau sudah otomatis, CI tools akan bisa mengeksekusinya dengan mudah.</p>
<p>Kalau semua ini dilakukan, Martin Fowler berjanji bahwa kegiatan coding akan menjadi lebih <em>rapid</em> dan <em>significantly reduces problem</em>.</p>
<p>Nah, investasinya cuma 5-10 menit instalasi dan konfigurasi, keuntungannya banyak sekali. Lalu kenapa tidak banyak yang mengimplementasikannya?</p>
<p>Oh iya, saya lupa kasi tau. Implementasi CI sendiri mungkin 5-10 menit. Tapi proses ini punya ketergantungan terhadap tools dan praktek lain, yaitu:</p>
<ul>
<li>Automated Testing</li>
<li>Version Control</li>
</ul>
<p>Dua praktek ini mengharuskan kita mengubah pola pikir dan kebiasaan, mirip seperti orang yang mau kurus atau berhenti merokok.</p>
<p>So … Good Luck</p>
<p>:D</p>
Konfigurasi Shorewall di Ubuntu Gutsy2007-12-27T23:51:27+07:00https://software.endy.muhardin.com/aplikasi/linux/gutsy-shorewall<p>Bila kita ingin mempublish komputer di internet, hal pertama yang kita pikirkan adalah firewall. Bagaimana membatasi akses hanya ke port-port yang kita ijinkan.</p>
<p>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.</p>
<p>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.</p>
<p>Untungnya di Ubuntu tidak kekurangan aplikasi front end untuk iptables. Untuk komputer personal, kita bisa gunakan <a href="http://www.fs-security.com/">Firestarter</a>. 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.</p>
<p>Selain Firestarter, kita juga bisa menggunakan <a href="http://www.simonzone.com/software/guarddog/">GuardDog</a>.</p>
<p>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.</p>
<p>Setelah tanya kanan kiri, <a href="http://linux2.arinet.org">Bos Ari</a> memberikan petuah agar sebaiknya saya pakai <a href="http://www.shorewall.net/">Shorewall</a> saja. Selain Shorewall, masih ada beberapa alternatif, seperti <a href="http://rocky.molphys.leidenuniv.nl/">Arno’s Firewall</a> yang direkomendasikan <a href="http://www.antonraharja.web.id">Anton</a>. Tapi melihat sekilas dari tutorialnya, nampaknya Shorewall adalah yang paling intuitif untuk digunakan.</p>
<p>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.</p>
<p>Langkah pertama tentunya adalah menginstal Shorewall. Di Ubuntu tidak sulit, langsung saja</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install shorewall
</code></pre></div></div>
<p>Shorewall akan segera terinstal, tapi tidak aktif. Kita harus mengkonfigurasi dulu, baru kemudian mengaktifkannya.</p>
<p>Konfigurasi Shorewall ada di folder <code class="language-plaintext highlighter-rouge">/etc/shorewall</code>.</p>
<p>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.</p>
<p>Jaringan komputer, dalam dunia Shorewall disebut dengan istilah zone. Konfigurasinya ditulis di file bernama <code class="language-plaintext highlighter-rouge">zones</code>. Karena kita cuma punya satu jaringan, yaitu internet, berikut adalah isi file tersebut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fw firewall
net ipv4
</code></pre></div></div>
<p>Shorewall ingin menaruh dirinya sendiri ke dalam zone terpisah. Oleh karena itu kita punya dua zone di file tersebut, yaitu net dan fw. <code class="language-plaintext highlighter-rouge">ipv4</code> adalah jenis zone. Ada tiga jenis zone dalam shorewall, yaitu ipv4, firewall, dan ipsec. Bila kita menggunakan koneksi terenkripsi, kita bisa menggunakan opsi ipsec.</p>
<p>Zone dan device dihubungkan di file yang namanya <code class="language-plaintext highlighter-rouge">interfaces</code>. Isinya adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>net eth0 detect tcpflags,logmartians,nosmurfs,norfc1918
</code></pre></div></div>
<p>Kolom pertama menyatakan bahwa zone <code class="language-plaintext highlighter-rouge">net</code> dilayani oleh kartu jaringan <code class="language-plaintext highlighter-rouge">eth0</code> yang disebutkan di kolom kedua. Selain kartu jaringan, kita juga bisa mendaftarkan perangkat lain seperti modem.</p>
<p>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 <code class="language-plaintext highlighter-rouge">detect</code>.</p>
<p>Kolom terakhir paling kanan memuat opsi. Banyak opsi yang disediakan, saya cuma jelaskan opsi yang digunakan di atas saja.</p>
<ul>
<li>tcpflags : ini memeriksa paket-paket yang memiliki kombinasi flags tidak lazim.</li>
<li>logmartians : ini artinya kita akan mencatat paket yang alamat asalnya aneh.</li>
<li>nosmurfs : ini mengatasi paket yang alamat asalnya sama dengan alamat broadcast.</li>
<li>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</li>
</ul>
<p>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:</p>
<ul>
<li>mesin kita ini boleh menghubungi siapa saja. Semua paket keluar diijinkan (ACCEPT)</li>
<li>paket yang berasal dari luar menuju firewall akan diabaikan (DROP)</li>
<li>paket yang berasal dari luar menuju komputer dibalik firewall akan diabaikan (DROP)</li>
<li>selain itu, tolak semua (REJECT)</li>
</ul>
<p>Konfigurasi kebijakan di atas ditulis di file bernama <code class="language-plaintext highlighter-rouge">policy</code>. Berikut isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#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
</code></pre></div></div>
<p>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.</p>
<p>Shorewall sudah punya konfigurasi standar (disebut dengan istilah macro) untuk aplikasi server yang umum digunakan. Kita tulis aturan ini di file <code class="language-plaintext highlighter-rouge">rules</code>. Berikut isinya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Ping/REJECT</code>, <code class="language-plaintext highlighter-rouge">SSH/ACCEPT</code> dan <code class="language-plaintext highlighter-rouge">Web/ACCEPT</code> di atas adalah macro. Sebetulnya kita juga bisa membuka akses webserver di port 80 tanpa macro dengan konfigurasi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ACCEPT net $FW tcp 80
</code></pre></div></div>
<p>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 <code class="language-plaintext highlighter-rouge">FTP/ACCEPT</code></p>
<p>Selanjutnya, kita edit file shorewall.conf. Ini adalah konfigurasi global. Isi konfigurasi yang disediakan Ubuntu Gutsy sudah cukup bagus. Saya cuma mengubah baris ini</p>
<p><code class="language-plaintext highlighter-rouge">STARTUP_ENABLED=No</code></p>
<p>menjadi</p>
<p><code class="language-plaintext highlighter-rouge">STARTUP_ENABLED=Yes</code></p>
<p>Selain itu, juga ada satu file lagi, yaitu <code class="language-plaintext highlighter-rouge">/etc/default/shorewall</code>. Ganti baris</p>
<p><code class="language-plaintext highlighter-rouge">startup=0</code></p>
<p>menjadi</p>
<p><code class="language-plaintext highlighter-rouge">startup=1</code></p>
<p>Semuanya sudah siap. Silahkan nyalakan firewall Anda.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /etc/init.d/shorewall start
</code></pre></div></div>
<p>Oh iya, berhati-hatilah kalau menginstal shorewall secara remote. Salah konfigurasi bisa menyebabkan kita terkunci di luar.</p>
Aplikasi Web dengan Spring 2.5 [bagian 3]2007-12-21T21:07:10+07:00https://software.endy.muhardin.com/java/aplikasi-web-spring25-3<p>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.</p>
<p>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.</p>
<h3 id="personlisthtml">personlist.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#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")
</code></pre></div></div>
<h3 id="headerhtml">header.html</h3>
<p>berisi kira-kira seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>:: List of All Person ::</title>
</head>
<body>
</code></pre></div></div>
<p>dan</p>
<h3 id="footerhtml">footer.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code></body>
</html>
</code></pre></div></div>
<p>Saya sendiri kurang suka dengan pendekatan seperti ini, karena:</p>
<ul>
<li>kita terpaksa membuat halaman html yang tidak lengkap. Di header.html tidak ada tutup body dan di footer.html tidak ada tag pembuka body.</li>
<li>perintah include untuk header dan footer akan diulangi di setiap halaman.</li>
</ul>
<p>Ada cara yang lebih baik, yaitu menggunakan decorator.</p>
<p>Pustaka decorator di Java cukup banyak, antara lain:</p>
<ul>
<li>SiteMesh</li>
<li>Tiles</li>
<li>Facelets</li>
</ul>
<p>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.</p>
<p>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.</p>
<p>Sitemesh adalah decorator independen. Tidak terikat dengan framework apa-apa. Dia juga mendukung JSP, Freemarker, dan Velocity. Kita akan menggunakan SiteMesh pada artikel ini.</p>
<p>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.</p>
<p>Sebagai contoh,</p>
<h3 id="personlisthtml-1">personlist.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><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>
</code></pre></div></div>
<p>Seperti kita lihat, tidak ada kode khusus. Kodenya sama persis dengan artikel sebelumnya.</p>
<p>Ini adalah decoratornya, kita beri nama main.html.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?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>
</code></pre></div></div>
<p>Ada beberapa tag khusus yang digunakan dalam decorator kita, yaitu</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">${base}</code></li>
<li><code class="language-plaintext highlighter-rouge">${title}</code></li>
<li><code class="language-plaintext highlighter-rouge">${body}</code></li>
</ul>
<p>Ini adalah variabel yang disediakan SiteMesh untuk kita. <code class="language-plaintext highlighter-rouge">${base}</code> adalah path menuju aplikasi web kita. Kita menggunakan <code class="language-plaintext highlighter-rouge">${base}</code> untuk membuat URL yang lengkap. Variabel ini sangat berguna agar aplikasi kita bisa dideploy ke berbagai context.</p>
<p>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.</p>
<p>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.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><filter></span>
<span class="nt"><filter-name></span>sitemesh<span class="nt"></filter-name></span>
<span class="nt"><filter-class></span>com.opensymphony.module.sitemesh.filter.PageFilter<span class="nt"></filter-class></span>
<span class="nt"></filter></span>
<span class="nt"><filter-mapping></span>
<span class="nt"><filter-name></span>sitemesh<span class="nt"></filter-name></span>
<span class="nt"><url-pattern></span>/*<span class="nt"></url-pattern></span>
<span class="nt"></filter-mapping></span>
</code></pre></div></div>
<p>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.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nt"><servlet></span>
<span class="nt"><servlet-name></span>sitemesh-velocity<span class="nt"></servlet-name></span>
<span class="nt"><servlet-class></span>com.opensymphony.module.sitemesh.velocity.VelocityDecoratorServlet<span class="nt"></servlet-class></span>
<span class="nt"></servlet></span>
<span class="nt"><servlet-mapping></span>
<span class="nt"><servlet-name></span>sitemesh-velocity<span class="nt"></servlet-name></span>
<span class="nt"><url-pattern></span>*.html<span class="nt"></url-pattern></span>
<span class="nt"></servlet-mapping></span>
</code></pre></div></div>
<p>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):</p>
<ul>
<li>halaman utama. Lengkap dengan menubar, header, dan footer</li>
<li>halaman login. Tanpa menubar, header, dan footer</li>
<li>tampilan siap print. Tanpa warna-warni.</li>
</ul>
<p>Sebagai contoh, kita cuma akan menggunakan satu decorator, yaitu main.html. Decorator ini berlaku untuk semua request. Berikut konfigurasinya.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><decorators</span> <span class="na">defaultdir=</span><span class="s">"/WEB-INF/decorators"</span><span class="nt">></span>
<span class="nt"><decorator</span> <span class="na">name=</span><span class="s">"default"</span> <span class="na">page=</span><span class="s">"main.html"</span><span class="nt">></span>
<span class="nt"><pattern></span>/*<span class="nt"></pattern></span>
<span class="nt"></decorator></span>
<span class="nt"></decorators></span>
</code></pre></div></div>
<p>File ini diletakkan di dalam folder WEB-INF, bersama-sama dengan web.xml.</p>
<p>Pada konfigurasi decorator tersebut, kita jelaskan bahwa semua request (/*) akan dicegat dan didekorasi dengan main.html yang ada di folder /WEB-INF/decorators.</p>
<p>Terakhir, kita lengkapi pustaka yang dibutuhkan. Jar yang dibutuhkan adalah:</p>
<ul>
<li>sitemesh.jar</li>
<li>commons-digester.jar</li>
<li>velocity-tools-view.jar</li>
</ul>
<p>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.</p>
<p>Selesai sudah. Mudah bukan?</p>
<p>Contoh ini sudah dipublish di GoogleCode, Anda bisa mengunduh:</p>
<ul>
<li><a href="https://github.com/endymuhardin/hello-spring-25.git">Checkout project</a></li>
<li><a href="https://github.com/endymuhardin/hello-spring-25/archive/master.zip">Download keseluruhan project folder</a></li>
</ul>
<p>Atau Anda juga bisa menggunakan Subversion untuk menarik keseluruhan project folder, dengan perintah:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/endymuhardin/hello-spring-25.git
</code></pre></div></div>
<p>Setelah project folder diperoleh, dibutuhkan database MySQL dengan konfigurasi sebagai berikut:</p>
<ul>
<li>nama database : belajar</li>
<li>username : belajar</li>
<li>password : java</li>
</ul>
<p>Tabel <code class="language-plaintext highlighter-rouge">T_PERSON</code> yang digunakan pada contoh ini dapat dibuat dengan menggunakan script sql yang ada di folder <code class="language-plaintext highlighter-rouge">src/sql/mysql-schema.sql</code>.</p>
<p>Untuk menjalankan aplikasi ini, dibutuhkan Ant 1.7.0 dan Java 6. Cukup ketik ant run di konsol. Kemudian browse ke http://localhost:8080/</p>
<p>Selamat mencoba</p>
Mengaktifkan commit email Subversion2007-12-07T22:22:59+07:00https://software.endy.muhardin.com/aplikasi/svn-commit-email<p>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:</p>
<ul>
<li>meningkatnya collective code ownership. Semua anggota tim akan merasa memiliki.</li>
<li>membantu penyebaran pengetahuan dan pengalaman. Kode yang kurang optimal akan dikomentari di ruang publik (milis developer), sehingga semua anggota akan mendapat pencerahan.</li>
<li>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.</li>
</ul>
<p>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.</p>
<p>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?</p>
<p>Masalah yang sama dihadapi oleh seluruh pengembang aplikasi open source di seluruh dunia. Dan mereka sudah punya solusinya, yaitu commit email.</p>
<p>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.</p>
<p>Dengan commit email ini, reviewer tidak perlu melihat keseluruhan kode program, tapi cukup yang berubah saja. Ini akan sangat meringankan kegiatan review.</p>
<p>Subversion sudah mempaketkan script trigger untuk commit email. Di <a href="http://subversion.tigris.org/tools_contrib.html">halaman Tools & Contrib</a> ada beberapa script untuk keperluan ini.</p>
<p>Saya menggunakan <a href="http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/commit-email.pl.in">commit script yang dibuat dengan Perl</a>. Sebelum dipakai, script ini harus diedit sedikit, di bagian SMTP server.</p>
<p><code class="language-plaintext highlighter-rouge">$smtp_server = "127.0.0.1";</code></p>
<p>dan bagian path menuju perintah svnlook.</p>
<p><code class="language-plaintext highlighter-rouge">my $svnlook = "@SVN_BINDIR@/svnlook";</code></p>
<p>Kalau di Windows, <code class="language-plaintext highlighter-rouge">svnlook</code> ada di <code class="language-plaintext highlighter-rouge">C:\Program Files\Subversion\bin</code>, sedangkan kalau di Linux biasanya ada di <code class="language-plaintext highlighter-rouge">/usr/bin</code>.</p>
<p>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.</p>
<p>Untuk Linux mudah, cukup rename <code class="language-plaintext highlighter-rouge">post-commit.tmpl</code> menjadi <code class="language-plaintext highlighter-rouge">post-commit</code>, dan ganti modenya menjadi executable.</p>
<p><code class="language-plaintext highlighter-rouge">chmod +x post-commit</code></p>
<p>Kemudian edit sedikit untuk memasukkan beberapa parameter:</p>
<ul>
<li>Recipient : penerima commit email. Biasanya ini saya arahkan ke milis developer</li>
<li>Subject : subject pada email yang dikirim</li>
<li>Reply To : kalau penerima email menekan tombol Reply, inilah alamat yang akan muncul di field To</li>
<li>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</li>
</ul>
<p>Berikut isi dari file post-commit saya</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/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
</code></pre></div></div>
<p>Untuk di Windows, kita harus membuat file <code class="language-plaintext highlighter-rouge">post-commit.bat</code>, di folder hooks juga. Berikut isi dari <code class="language-plaintext highlighter-rouge">post-commit.bat</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@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%
</code></pre></div></div>
<p>Tentunya kita harus menginstal dulu <a href="http://www.activestate.com/store/productdetail.aspx?prdGuid=81fbce82-6bd5-49bc-a915-08d58c2648ca">Perl untuk Windows</a> agar script ini bisa berjalan.</p>
<p>Selamat mencoba</p>
Annotation dan XML2007-12-07T21:30:47+07:00https://software.endy.muhardin.com/java/konfigurasi-anotasi-vs-xml<p>Salah satu <a href="http://endy.artivisi.com/blog/java/akses-database-spring25/#comment-9582">komentator bertanya seperti ini</a>,</p>
<blockquote>
<p>kenapa annotation lebih diprefer daripada xml configuration ? bukannya xml configuration di spring membuat kontrol kita lebih sentralisasi, sehingga lebih mudah dimaintain ?</p>
</blockquote>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>Baiklah, masalah sudah ditemukan, saatnya untuk memperbaiki. Memperbaiki jauh lebih mudah daripada menemukan bug, kan?</p>
<p>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.</p>
<p>Akhirnya setelah google kesana kemari, saya menemukan empat cara untuk melakukan setting transaksi:</p>
<ul>
<li>coding di VB</li>
<li>coding di Stored Procedure</li>
<li>konfigurasi class properties di VB</li>
<li>konfigurasi di COM service pada Control Panel Windows</li>
</ul>
<p>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.</p>
<p>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.</p>
<p>Kode seperti ini mudah dipahami dan mudah didebug.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Transactional(readOnly=false)
public void transferAntarRekening(Rekening asal, Rekening tujuan, BigDecimal jumlah){
asal.credit(jumlah);
tujuan.debet(jumlah);
}
</code></pre></div></div>
<p>Kode seperti ini perlu banyak Alt-Tab untuk mendebugnya.</p>
<p>Di Java:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void transferAntarRekening(Rekening asal, Rekening tujuan, BigDecimal jumlah){
asal.credit(jumlah);
tujuan.debet(jumlah);
}
</code></pre></div></div>
<p>Di XML</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><property name="transactionAttributes">
<props>
<prop key="transfer*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</code></pre></div></div>
<p>Bedakan antara cara melakukan transaksi, dan cara melakukan konfigurasi. Di Java, kita bisa memilih beberapa cara transaksi:</p>
<ul>
<li>Local Transaction: mengelola transaksi melalui java.sql.Connection dalam source code</li>
<li>Programmatic Transaction: mengambil object Transaction dari JTS provider, kemudian menggunakannya untuk commit/rollback. Ini juga dilakukan dalam source code.</li>
<li>Declarative Transaction: mengatur transaksi melalui konfigurasi.</li>
</ul>
<p>Kalau kita pilih declarative transaction, ada beberapa cara konfigurasinya:</p>
<ul>
<li>Melalui XML</li>
<li>Melalui Annotation</li>
</ul>
<p>Yang mana yang paling dekat dengan source code yang bertransaksi? Tentu saja annotation.</p>
<p>Ok, sudah jelas mengenai transaction, sebaiknya di annotation saja.</p>
<p>Berikutnya, deklarasi DAO. Sebelumnya kita melakukan deklarasi DAO di XML. Yang biasa saya lakukan kalau ada DAO baru biasanya sama:</p>
<ul>
<li>copy paste deklarasi DAO di atasnya</li>
<li>ganti bean id</li>
<li>ganti nama class</li>
</ul>
<p>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.</p>
<p>Jadi, lebih baik kalau kita suruh Spring autodetect saja dari annotation @Repository.</p>
<p>Bagaimana dengan relasi dengan datasource? Bukankah kalau di XML jadi terlihat DAO mana menggunakan DataSource mana?</p>
<p>Hmm … untuk kasus datasource, ada dua kemungkinan:</p>
<ul>
<li>menggunakan satu datasource</li>
<li>menggunakan lebih dari satu datasource</li>
</ul>
<p>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.</p>
<p>Untuk kasus kedua, mau tidak mau memang harus XML. Soalnya @Autowired by type tidak akan bisa, karena ada lebih dari satu bean bertipe DataSource.</p>
<p>Sekarang kita lihat, apa yang tertinggal di konfigurasi XML, setelah banyak hal kita pindahkan ke annotation:</p>
<ul>
<li>deklarasi datasource</li>
<li>deklarasi transaction manager</li>
</ul>
<p>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 <a href="http://winstone.sourceforge.net">Winstone</a>, maka kita tidak menggunakan JTA, jadi transaction manager menggunakan LocalTransactionManager.</p>
<p>Setelah selesai dengan isu backend, mari sekarang kita bahas presentation layer. Ada beberapa anotasi di sini:</p>
<ul>
<li>@Controller : untuk menandai class Controller</li>
<li>@RequestMapping : untuk mapping URL atau Request Method ke handlernya</li>
<li>@RequestParam : untuk mem-bind request parameter ke variabel</li>
<li>@ModelAttribute : untuk mem-bind object ke request attribute</li>
<li>@SessionAttribute : untuk mem-bind object ke session attribute</li>
</ul>
<p>@Controller sama dengan @Repository. Keberadaannya menghilangkan boilerplate deklarasi di XML. Menurut saya, ini sangat mengurangi clutter dan duplikasi di XML.</p>
<p>@RequestMapping, saya 50:50 di sini.</p>
<p>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.</p>
<p>Tapi di sisi lain, saya ingin menggunakan Convention over Configuration ala <a href="http://rubyonrails.org">Rails</a>. 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?</p>
<p>Jadi untuk @RequestMapping, posisi saya adalah, tergantung situasi. Ada saatnya mapping di XML, dan ada saatnya mapping dengan annotation.</p>
<p>@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.</p>
<p>Yah, begitulah sekilas perbandingan antara konfigurasi melalui anotasi dan XML. Sepertinya menambah bingung. Tapi jangan khawatir, untuk bisa mengerti harus bingung dulu.</p>
<p>Semoga bermanfaat.</p>
<p>:D</p>
Aplikasi Web dengan Spring 2.5 [bagian 2]2007-12-06T18:27:16+07:00https://software.endy.muhardin.com/java/aplikasi-web-spring25-2<p>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.</p>
<p>Kita akan menggunakan template yang sama untuk pengeditan Person yang sudah ada maupun pendaftaran Person baru. Templatenya bernama personform.html. Berikut kodenya.</p>
<h3 id="personformhtml">personform.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><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>
</code></pre></div></div>
<p>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.</p>
<p>Kalau dijadikan kode Java, kira-kira $!person sama dengan ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">String</span> <span class="n">personName</span><span class="o">;</span>
<span class="k">if</span><span class="o">(</span><span class="n">person</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">person</span><span class="o">.</span><span class="na">getName</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">personName</span> <span class="o">=</span> <span class="n">person</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">personName</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>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.</p>
<p>Berikut kerangka class PersonFormController.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kn">package</span> <span class="nn">tutorial.spring25.ui.springmvc</span><span class="o">;</span>
<span class="nd">@Controller</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/personform"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PersonFormController</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">PersonDao</span> <span class="n">personDao</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setPersonDao</span><span class="o">(</span><span class="kd">final</span> <span class="nc">PersonDao</span> <span class="n">personDao</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">personDao</span> <span class="o">=</span> <span class="n">personDao</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ModelMap</span> <span class="nf">displayForm</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"person_id"</span><span class="o">,</span> <span class="n">required</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">processForm</span><span class="o">(</span><span class="nd">@ModelAttribute</span><span class="o">(</span><span class="s">"person"</span><span class="o">)</span> <span class="nc">Person</span> <span class="n">person</span><span class="o">,</span> <span class="nc">BindingResult</span> <span class="n">result</span><span class="o">,</span> <span class="nc">SessionStatus</span> <span class="n">status</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>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.</p>
<p>Kedua method dimapping ke request /personform. Dengan demikian, request ke <code class="language-plaintext highlighter-rouge">http://localhost:8080/tutorial-spring25/tutorial/personform</code> 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.</p>
<p>Tetapi, bagaimana kita memilih kapan harus mendisplay form dan memproses form? Kita membedakannya dengan memasang annotation <code class="language-plaintext highlighter-rouge">@RequestMapping</code> dengan parameter <code class="language-plaintext highlighter-rouge">RequestMethod</code>. Bila requestnya GET (terjadi bila kita mengetik <code class="language-plaintext highlighter-rouge">http://localhost:8080/tutorial-spring25/tutorial/personform</code> di browser dan menekan Enter), maka jalankan method displayForm. Tapi bila requestnya POST (terjadi bila kita menekan tombol Submit di <code class="language-plaintext highlighter-rouge">personform.html</code>), maka jalankan method processForm.</p>
<p>Sekarang mari kita isi method displayForm. Berikut isinya</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ModelMap</span> <span class="nf">displayForm</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"person_id"</span><span class="o">,</span> <span class="n">required</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Person</span> <span class="n">person</span> <span class="o">=</span> <span class="n">personDao</span><span class="o">.</span><span class="na">getById</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">person</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="n">person</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="o">();</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ModelMap</span><span class="o">(</span><span class="n">person</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>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.</p>
<p>Logika pada method ini tidak rumit. Ambil object Person dari database berdasarkan id. Kalau tidak ada, berikan saja object baru.</p>
<p>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.</p>
<p>Berikutnya, kita akan implementasi method untuk memproses form. Berikut isi method processForm</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">processForm</span><span class="o">(</span><span class="nd">@ModelAttribute</span><span class="o">(</span><span class="s">"person"</span><span class="o">)</span> <span class="nc">Person</span> <span class="n">person</span><span class="o">)</span> <span class="o">{</span>
<span class="n">personDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">person</span><span class="o">);</span>
<span class="k">return</span> <span class="s">"redirect:personlist"</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Mudah kan? Cukup gunakan personDao untuk menyimpan object ke database, kemudian redirect ke halaman personlist.</p>
<p>Begitu saja? Tidak ada yang lupa?</p>
<p>Ya untuk memproses form begitu saja langkahnya, tidak perlu susah-susah.</p>
<p>Bagaimana dengan validasi? Mana ada form tanpa validasi.</p>
<p>Baiklah, mari kita tambahkan kode validasi. Untuk itu, method processForm perlu dimodifikasi menjadi seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">processForm</span><span class="o">(</span><span class="nd">@ModelAttribute</span><span class="o">(</span><span class="s">"person"</span><span class="o">)</span> <span class="nc">Person</span> <span class="n">person</span><span class="o">,</span> <span class="nc">BindingResult</span> <span class="n">result</span><span class="o">,</span> <span class="nc">SessionStatus</span> <span class="n">status</span><span class="o">)</span> <span class="o">{</span>
<span class="k">new</span> <span class="nf">PersonValidator</span><span class="o">().</span><span class="na">validate</span><span class="o">(</span><span class="n">person</span><span class="o">,</span> <span class="n">result</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">hasErrors</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"personform"</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">personDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">person</span><span class="o">);</span>
<span class="n">status</span><span class="o">.</span><span class="na">setComplete</span><span class="o">();</span>
<span class="k">return</span> <span class="s">"redirect:personlist"</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>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.</p>
<p>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.</p>
<p>Isi class PersonValidator juga tidak banyak. Berikut kodenya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kn">package</span> <span class="nn">tutorial.spring25.validator</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PersonValidator</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EMAIL_FORMAT</span> <span class="o">=</span> <span class="s">".*@.*\\.com"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">Person</span> <span class="n">person</span><span class="o">,</span> <span class="nc">Errors</span> <span class="n">errors</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// field nama harus diisi</span>
<span class="k">if</span><span class="o">(!</span><span class="nc">StringUtils</span><span class="o">.</span><span class="na">hasText</span><span class="o">(</span><span class="n">person</span><span class="o">.</span><span class="na">getName</span><span class="o">()))</span> <span class="o">{</span>
<span class="n">errors</span><span class="o">.</span><span class="na">rejectValue</span><span class="o">(</span><span class="s">"name"</span><span class="o">,</span> <span class="s">"required"</span><span class="o">,</span> <span class="s">"nama harus diisi"</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// bila field email diisi, formatnya harus benar</span>
<span class="k">if</span> <span class="o">(</span><span class="nc">StringUtils</span><span class="o">.</span><span class="na">hasLength</span><span class="o">(</span><span class="n">person</span><span class="o">.</span><span class="na">getEmail</span><span class="o">())</span> <span class="o">&&</span> <span class="o">!</span><span class="n">person</span><span class="o">.</span><span class="na">getEmail</span><span class="o">().</span><span class="na">matches</span><span class="o">(</span><span class="no">EMAIL_FORMAT</span><span class="o">)</span> <span class="o">)</span> <span class="o">{</span>
<span class="n">errors</span><span class="o">.</span><span class="na">rejectValue</span><span class="o">(</span><span class="s">"email"</span><span class="o">,</span> <span class="s">"email.format"</span><span class="o">,</span> <span class="s">"format email salah"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Mudah bukan?</p>
<p>Para penggemar framework berbasis komponen (seperti Tapestry atau JSF) mungkin bertanya, untuk apa saya belajar lagi Spring MVC? Sepertinya tidak lebih mudah.</p>
<p>Coba perhatikan URL yang kita gunakan:</p>
<ul>
<li>
<p>http://localhost:8080/tutorial-spring25/tutorial/personlist</p>
</li>
<li>
<p>http://localhost:8080/tutorial-spring25/tutorial/persondetail?person_id=100</p>
</li>
<li>
<p>http://localhost:8080/tutorial-spring25/tutorial/personform</p>
</li>
<li>
<p>http://localhost:8080/tutorial-spring25/tutorial/personform?person_id=100</p>
</li>
</ul>
<p>Semuanya bersih dan <em>bookmarkable</em>. Dengan Spring MVC kita bisa mengatur URL sesuai keinginan.</p>
<p>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.</p>
<p>Implikasinya, selama data yang disuplai tidak berubah, hanya dengan mengubah konfigurasi kita dapat mengubah tampilan. Tentunya kita harus menyediakan template yang sesuai.</p>
<p>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.</p>
<p>Kelebihan lainnya, kita mengendalikan secara penuh output HTML aplikasi kita. Implikasinya, kita bisa menerapkan teknik-teknik teruji dalam protokol HTTP seperti <a href="http://www.mnot.net/cache_docs/">Cache Control pada HTTP Header</a>.</p>
<p>Atau kita bisa manfaatkan <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">HTTP Response Code</a> <em>304 Not Modified</em> 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.</p>
<p>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.</p>
<p>Source code untuk rangkaian artikel ini sudah dipublish di GoogleCode. Anda bisa:</p>
<ul>
<li><a href="https://github.com/endymuhardin/hello-spring-25.git">Checkout project</a></li>
<li><a href="https://github.com/endymuhardin/hello-spring-25/archive/master.zip">Download keseluruhan project folder</a></li>
</ul>
<p>Demikian sekilas tentang framework Spring MVC. Semoga bermanfaat.</p>
Aplikasi Web dengan Spring 2.5 [bagian 1]2007-12-05T20:35:16+07:00https://software.endy.muhardin.com/java/aplikasi-web-spring25-1<p>Setelah pada <a href="http://endy.artivisi.com/blog/java/akses-database-spring25">artikel sebelumnya</a> kita berhasil mengakses database, kali ini kita akan membuat tampilan berbasis web yang menggunakan kode program kita kemarin.</p>
<p>Fiturnya tidak terlalu sulit, dari tabel T_PERSON kemarin kita akan buatkan beberapa tampilan untuk mengelola data Person. Tampilan yang akan kita sediakan adalah:</p>
<ul>
<li>daftar semua Person</li>
<li>informasi Person yang dipilih</li>
<li>form untuk membuat object Person baru</li>
<li>form untuk mengedit object Person yang sudah ada</li>
</ul>
<p>Sebelum kita mulai, ada baiknya kita mengetahui cara kerja Spring dalam mengelola aplikasi web. Sequence diagram berikut akan memudahkan pemahaman kita.</p>
<p><a href="/images/uploads/2007/12/spring-mvc-sequence.png"><img src="/images/uploads/2007/12/spring-mvc-sequence.png" alt="Alur kerja Spring MVC " /></a></p>
<p>Seperti kita lihat pada gambar, semua request akan diterima oleh DispatcherServlet. Mereka yang pernah membaca buku Core J2EE Pattern akan segera mengenali jurus ini, yang sering disebut dengan istilah FrontController. DispatcherServlet akan menyuruh handler mapping untuk memilih class yang akan menangani request. Ada beberapa implementasi handler mapping, diantaranya:</p>
<ul>
<li>BeanNameUrlHandlerMapping</li>
<li>SimpleUrlHandlerMapping</li>
<li>ControllerClassNameHandlerMapping</li>
</ul>
<p>Class yang menangani request disebut dengan istilah Controller. Class Controller ini yang akan kita tulis sendiri. Spring menyediakan beberapa superclass Controller yang bisa kita subclass untuk mengurangi kode yang harus ditulis. Beberapa superclass yang disediakan Spring antara lain:</p>
<ul>
<li>Controller</li>
<li>MultiActionController</li>
<li>SimpleFormController</li>
<li>AbstractWizardController</li>
</ul>
<p>Selain membuat turunan dari superclass di atas, kita juga bisa membuat class biasa yang dilengkapi dengan annotation. Pada artikel ini kita tidak akan membuat turunan apa-apa, karena semua bisa dikerjakan dengan annotation.</p>
<p>Tanggung jawab controller selain memproses request adalah menentukan nama template yang akan digunakan untuk menampilkan hasil pemrosesan controller. Spring menyebut template ini dengan istilah View. Kita cuma perlu menyebutkan nama View dan Spring yang akan mencarikan file template yang sesuai dan kemudian mengisi datanya. Proses mencarikan template ini ditangani oleh ViewResolver. Ada beberapa implementasi ViewResolver, antara lain untuk memproses template berjenis:</p>
<ul>
<li>JSP dan JSTL</li>
<li>Freemarker atau Velocity</li>
<li>XML dengan XSLT</li>
<li>Jasper Report</li>
<li>Document View (PDF dan XLS)</li>
</ul>
<p>Sekarang setelah kita mengetahui arsitektur umum dari aplikasi web Spring, kita bisa segera coding. Class-class yang akan kita buat adalah:</p>
<ul>
<li>PersonController. Class ini akan menangani tampilan daftar Person dan detail Person.</li>
<li>PersonFormController. Class ini akan menangani tampilan pengeditan object Person, baik yang belum terdaftar maupun yang sudah ada di dalam database.</li>
</ul>
<p>File konfigurasi yang akan kita buat adalah:</p>
<ul>
<li>web.xml. Ini adalah konfigurasi standar untuk semua aplikasi web dengan Java.</li>
<li>tutorial-servlet.xml. Ini adalah konfigurasi DispatcherServlet untuk menampung deklarasi HandlerMapping, Controller, dan ViewResolver.</li>
</ul>
<p>Untuk menampilkan halaman web, kita akan menggunakan template engine <a href="http://velocity.apache.org">Velocity</a>. Velocity adalah template engine yang kecil dan ringan, tapi fiturnya cukup lengkap dan mudah digunakan. Saya lebih suka menggunakan Velocity daripada JSP, karena JSP membutuhkan kompilasi menjadi Servlet dan kemudian menjadi bytecode. Ini menyebabkan halaman JSP lebih sulit didebug bila terjadi error. Selain itu, kompilasi JSP membutuhkan dua kompiler, satu untuk JSP ke Servlet, dan satu lagi untuk Servlet menjadi bytecode. Dengan demikian, kita harus menginstal JDK di server. Tanpa JSP, kita dapat menggunakan Servlet container yang ringan dan kecil seperti <a href="http://jetty.mortbay.org">Jetty</a> atau <a href="http://winstone.sourceforge.net">Winstone</a> dan tidak perlu menginstal JDK, cukup JRE saja.</p>
<p>Template untuk menampilkan daftar orang dibuat dalam HTML yang sudah disisipi kode Velocity, disimpan dengan nama <code class="language-plaintext highlighter-rouge">personlist.html</code>. Kodenya terlihat seperti ini.</p>
<h3 id="personlisthtml">personlist.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head>
<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>
</code></pre></div></div>
<p>Kode yang diawali dengan tanda # merupakan perintah dalam Velocity. Dengan menggunakan perintah <code class="language-plaintext highlighter-rouge">#foreach</code>, kita melakukan looping untuk setiap baris record.</p>
<p>Kode yang diawali tanda $ merupakan variabel dalam Velocity. Isi variabel ini nantinya akan kita sediakan melalui controller Spring.</p>
<p>Untuk menampilkan detail informasi Person, kita buat <code class="language-plaintext highlighter-rouge">persondetail.html</code>. Kodenya seperti ini.</p>
<h3 id="persondetailhtml">persondetail.html</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head>
<title>:: $person.Name's Detail Info ::</title>
</head>
<body>
<table>
<tr>
<td>Nama</td>
<td>$person.Name</td>
</tr>
<tr>
<td>Email</td>
<td>$person.Email</td>
</tr>
</table>
</body>
</html>
</code></pre></div></div>
<p>Sekarang mari kita isi template tersebut dengan data yang dibutuhkannya. Template <code class="language-plaintext highlighter-rouge">personlist.html</code> membutuhkan data List<Person> dengan nama variabel personList, sedangkan `persondetail.html` membutuhkan data Person dengan nama variabel person.</Person></p>
<p>Pertama, kita akan mengisi <code class="language-plaintext highlighter-rouge">personlist.html</code>. Template ini akan disuplai oleh PersonController, melalui method yang bernama list. Berikut kode programnya.</p>
<h3 id="personcontrollerjava">PersonController.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.spring25.ui.springmvc;
@Controller
public class PersonController {
private PersonDao personDao;
@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
@RequestMapping("/personlist")
public ModelMap list(){
return new ModelMap(personDao.getAll());
}
}
</code></pre></div></div>
<p>Mudah bukan? Cukup panggil method personDao.getAll, kemudian masukkan hasilnya ke dalam ModelMap.</p>
<p>Kita melihat beberapa annotation pada kode ini. Annotation <code class="language-plaintext highlighter-rouge">@Controller</code> merupakan penanda bagi Spring bahwa class ini adalah sebuah Controller. Kelas yang memiliki annotation ini akan dipindai pada saat start-up dan diregistrasi ke <code class="language-plaintext highlighter-rouge">ApplicationContext</code>. Annotation <code class="language-plaintext highlighter-rouge">@Autowired</code> menyuruh Spring untuk menginjeksikan object <code class="language-plaintext highlighter-rouge">PersonDao</code>. Dengan annotation <code class="language-plaintext highlighter-rouge">@RequestMapping</code>, kita menentukan bahwa request menuju ke <code class="language-plaintext highlighter-rouge">http://namaserver:port/namaaplikasi/namaservlet/personlist</code> akan ditangani oleh method ini.</p>
<p>Pada saat dideploy, <code class="language-plaintext highlighter-rouge">DispatcherServlet</code> milik Spring akan menemukan dan memanggil method ini. Kemudian, dia akan menerima hasilnya berupa ModelMap untuk kemudian diserahkan ke ViewHandler Velocity untuk digabungkan dengan template dan menghasilkan halaman HTML.</p>
<p>Object yang kita berikan pada ModelMap akan diberi nama oleh Spring secara otomatis. Karena kita mensuplai object dengan tipe <code class="language-plaintext highlighter-rouge">List<Person></code>, maka Spring akan memberikan nama <code class="language-plaintext highlighter-rouge">personList</code>. Demikian juga pada controller berikutnya kita akan memberikan object bertipe <code class="language-plaintext highlighter-rouge">Person</code> ke controller, Spring akan memberikan nama person pada object tersebut. Dengan nama itulah (<code class="language-plaintext highlighter-rouge">$person</code>) kita mengaksesnya di template Velocity.</p>
<p>Cukup satu dulu implementasi kita. Sekarang tiba saatnya konfigurasi. Aplikasi kita akan dideploy dengan nama context <code class="language-plaintext highlighter-rouge">tutorial-spring25</code>. Di dalamnya, kita akan memasang <code class="language-plaintext highlighter-rouge">DispatcherServlet</code> yang akan mengambil semua request dengan path /tutorial/*. Jadi, method <code class="language-plaintext highlighter-rouge">public ModelMap list</code> akan diakses melalui URL <code class="language-plaintext highlighter-rouge">http://localhost:8080/tutorial-spring25/tutorial/personlist</code>.</p>
<p>Konfigurasi pertama ada di web.xml. Di sini kita akan mengkonfigurasi <code class="language-plaintext highlighter-rouge">DispatcherServlet</code> dan mendaftarkan <code class="language-plaintext highlighter-rouge">applicationContext.xml</code>. Berikut isi <code class="language-plaintext highlighter-rouge">web.xml</code></p>
<h3 id="webxml">web.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="ISO-8859-1"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Tutorial Spring</display-name>
<description>Tutorial Spring 2.5</description>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>tutorial</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>tutorial</servlet-name>
<url-pattern>/tutorial/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ApplicationContext</code> yang berisi konfigurasi database dan transaksi, yang dibahas pada artikel sebelumnya, didaftarkan melalui context-param. File konfigurasinya, <code class="language-plaintext highlighter-rouge">applicationContext.xml</code> disimpan di classpath, yaitu di folder <code class="language-plaintext highlighter-rouge">WEB-INF/classes</code>. Oleh karena itu, kita tulis pathnya <code class="language-plaintext highlighter-rouge">classpath:applicationContext.xml</code></p>
<p><code class="language-plaintext highlighter-rouge">ApplicationContext</code> ini harus diaktifkan pada saat aplikasi web dideploy dan dinon-aktifkan pada saat aplikasi web di-undeploy. Untuk itu, kita harus memasang <code class="language-plaintext highlighter-rouge">ContextLoaderListener</code> untuk memonitor aktifitas aplikasi web.</p>
<p>Selanjutnya, kita daftarkan <code class="language-plaintext highlighter-rouge">DispatcherServlet</code> dengan nama <code class="language-plaintext highlighter-rouge">tutorial</code>. Spring akan mencari file bernama <code class="language-plaintext highlighter-rouge">tutorial-servlet.xml</code> sebagai file konfigurasi <code class="language-plaintext highlighter-rouge">DispatcherServlet</code> ini di dalam folder <code class="language-plaintext highlighter-rouge">WEB-INF</code>. <code class="language-plaintext highlighter-rouge">DispatcherServlet</code> tutorial akan dimapping untuk menangani semua request dengan pola <code class="language-plaintext highlighter-rouge">/tutorial/*</code></p>
<p>Sekarang, mari kita lihat isi <code class="language-plaintext highlighter-rouge">tutorial-servlet.xml</code>.</p>
<h3 id="tutorial-servletxml">tutorial-servlet.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="tutorial.spring25.ui.springmvc" />
<bean id="velocityConfig"
class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/templates/velocity/" />
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".html" />
</bean>
</beans>
</code></pre></div></div>
<p>Pertama, kita konfigurasi Spring agar memindai isi package <code class="language-plaintext highlighter-rouge">tutorial.spring25.ui.springmvc</code> dan mendaftarkan semua class yang beranotasi <code class="language-plaintext highlighter-rouge">@Controller</code>.
Kedua, kita mengkonfigurasi <code class="language-plaintext highlighter-rouge">VelocityConfigurer</code> untuk mencari template di dalam folder <code class="language-plaintext highlighter-rouge">WEB-INF/templates/velocity</code>.
Terakhir, kita melakukan konfigurasi <code class="language-plaintext highlighter-rouge">VelocityViewResolver</code> untuk menerjemahkan nama view menjadi nama file template. Misalnya kita memberikan nama view <code class="language-plaintext highlighter-rouge">personlist</code>, maka <code class="language-plaintext highlighter-rouge">VelocityViewResolver</code> akan memberikan file <code class="language-plaintext highlighter-rouge">WEB-INF/templates/velocity/personlist.html</code>.</p>
<p>Pembaca yang teliti akan segera protes, “Kita kan tidak pernah menyebutkan nama view di dalam Controller. Lalu dari mana nama view itu didapatkan?”</p>
<p>Baiklah, mari lihat lagi method tersebut.</p>
<h3 id="method-list">Method list</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @RequestMapping("/personlist")
public ModelMap list(){
return new ModelMap(personDao.getAll());
}
</code></pre></div></div>
<p>Method tersebut dimapping untuk menerima request <code class="language-plaintext highlighter-rouge">http://localhost:8080/tutorial-spring25/tutorial/personlist</code>. Kalau kita tidak melakukan konfigurasi apa-apa, Spring secara default akan menganggap nama request sama dengan nama view. Jadi method di atas akan menghasilkan nama view <code class="language-plaintext highlighter-rouge">personlist</code>.</p>
<p>Setelah semua konfigurasi di atas selesai, kita bisa langsung membuat paket war untuk dideploy.</p>
<p>Berikutnya, kita akan membuat tampilan informasi detail per Person. Untuk ini, kita membutuhkan parameter <code class="language-plaintext highlighter-rouge">person\_id</code> yang ingin ditampilkan. Sehingga bila kita ingin menampilkan Person dengan id 100, URLnya adalah <code class="language-plaintext highlighter-rouge">http://localhost/tutorial-spring25/tutorial/persondetail?person\_id=100</code></p>
<p>Class <code class="language-plaintext highlighter-rouge">PersonController</code> yang sudah ditambahi method detail tampak seperti ini.</p>
<h3 id="personcontrollerjava-1">PersonController.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.spring25.ui.springmvc;
@Controller
public class PersonController {
private PersonDao personDao;
@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
@RequestMapping("/personlist")
public ModelMap list(){
return new ModelMap(personDao.getAll());
}
@RequestMapping("/persondetail")
public ModelMap detail(@RequestParam("person_id") Long personId){
return new ModelMap(personDao.getById(personId));
}
}
</code></pre></div></div>
<p>Untuk mengambil parameter<code class="language-plaintext highlighter-rouge"> person\_id</code>, kita tinggal membuat method parameter biasa yang dilengkapi annotation <code class="language-plaintext highlighter-rouge">@RequestParam</code>. Konversi tipe data akan dilakukan oleh Spring. Dengan kata lain, kode ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RequestParam("person_id") Long personId
</code></pre></div></div>
<p>sama dengan ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Long personId = Long.valueOf(httpRequest.getParameter("person_id"));
</code></pre></div></div>
<p>bedanya, kita tidak perlu mengimport <code class="language-plaintext highlighter-rouge">javax.servlet.HttpServletRequest</code>.</p>
<p>Setelah selesai, redeploy aplikasi dan coba akses <code class="language-plaintext highlighter-rouge">http://localhost/tutorial-spring25/tutorial/personlist</code>. Dari sana, klik link view. Tampilan detail dari object Person yang dipilih akan segera terlihat.</p>
<p>Demikianlah bagian kedua dari seri Spring 2.5. Pada <a href="http://endy.artivisi.com/blog/java/aplikasi-web-spring25-2/">artikel selanjutnya</a>, kita akan melihat cara mengimplementasikan form untuk mengedit object Person yang sudah ada, maupun membuat object Person yang baru.</p>
Akses Database dengan Spring 2.52007-12-03T21:12:07+07:00https://software.endy.muhardin.com/java/akses-database-spring25<h1 id="akses-database-dengan-spring-25">Akses database dengan Spring 2.5</h1>
<p>Spring 2.5 baru saja keluar. Rilis kali ini membawa penambahan fitur yang cukup signifikan di sisi konfigurasi. Dalam Spring yang baru ini, kita bisa mengkonfigurasi aplikasi melalui annotation. Suatu hal yang sangat bermanfaat untuk mengurangi jumlah baris kode XML kita.</p>
<p>Sebetulnya tidak ada yang salah dengan XML. Walaupun demikian, ada beberapa hal yang menurut saya kurang tepat kalau dikonfigurasi melalui XML, diantaranya:</p>
<ul>
<li>konfigurasi transaction</li>
<li>deklarasi bean standar</li>
</ul>
<p>Konfigurasi transaction biasanya tergantung dari kode program yang ingin ber-transaction. Bila kita konfigurasi di XML, maka untuk memikirkan satu logika akses database, kita harus melihat di dua tempat yang berbeda; file java dan file XML. Menurut pendapat saya, fitur declarative transaction walaupun kelihatannya mirip konfigurasi, tapi pada dasarnya adalah logika aplikasi. Tempatnya bukan di konfigurasi XML, tapi di kode Java.</p>
<p>Di Spring, kita harus mendaftarkan object aplikasi kita ke dalam object ApplicationContext agar bisa dikelola oleh Spring. Pada rilis sebelumnya, pendaftaran ini dilakukan dalam file XML. Cara ini memiliki incremental cost yang tinggi. Bila kita punya 100 object yang ingin dikelola, maka kita harus punya 100 deklarasi di konfigurasi XML Spring. Sekarang kita bisa menandai object yang akan dikelola Spring melalui annotation. Jadi walaupun ada 100 object, konfigurasi XML kita tidak bertambah.</p>
<p>Ok, cukup berteori. Saatnya melihat contoh kode.</p>
<h2 id="domain-model">Domain Model</h2>
<p>Pada artikel kali ini, kita akan membuat kode akses database untuk class <code class="language-plaintext highlighter-rouge">Person</code>. Class ini tidak istimewa, cuma POJO biasa dengan tiga property: id, name, dan email. Berikut kode program <code class="language-plaintext highlighter-rouge">Person.java</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.spring25.model</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Person</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Jangan lupa membuat getter dan setter.</p>
<h2 id="skema-database">Skema Database</h2>
<p>Class ini akan kita simpan di database dalam tabel bernama <code class="language-plaintext highlighter-rouge">T_PERSON</code>. Berikut definisinya untuk database MySQL.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">T_PERSON</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">BIGINT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="n">name</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">),</span>
<span class="n">email</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<h2 id="interface-akses-database">Interface Akses Database</h2>
<p>Operasi database yang akan kita buat dijelaskan oleh interface <code class="language-plaintext highlighter-rouge">PersonDao</code>, sebagai berikut.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.spring25.dao</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">PersonDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="n">person</span><span class="o">></span> <span class="nf">getAll</span><span class="o">();</span>
<span class="kd">public</span> <span class="nc">Person</span> <span class="nf">getById</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">);</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">save</span><span class="o">(</span><span class="nc">Person</span> <span class="n">p</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Untuk tahap pertama, kita akan lihat cara mengakses database dengan JDBC helper yang disediakan Spring. Akses database dengan Hibernate akan dijelaskan pada artikel terpisah.</p>
<h2 id="implementasi-akses-database">Implementasi Akses Database</h2>
<p>Berikut adalah kerangka implementasi <code class="language-plaintext highlighter-rouge">PersonDao</code> dengan JDBC helper dari Spring. Kita simpan di file bernama <code class="language-plaintext highlighter-rouge">PersonDaoSpringJdbc.java</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.spring25.dao.springjdbc</span><span class="o">;</span>
<span class="nd">@Repository</span><span class="o">(</span><span class="s">"personDao"</span><span class="o">)</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span><span class="o">=</span><span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PersonDaoSpringJdbc</span> <span class="kd">implements</span> <span class="nc">PersonDao</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setDataSource</span><span class="o">(</span><span class="kd">final</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="n">person</span><span class="o">></span> <span class="nf">getAll</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Person</span> <span class="nf">getById</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">save</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Person</span> <span class="n">person</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Ada beberapa hal yang baru pada kode di atas. Kita melihat ada annotation <code class="language-plaintext highlighter-rouge">@Repository</code>, <code class="language-plaintext highlighter-rouge">@Transactional</code>, dan <code class="language-plaintext highlighter-rouge">@Autowired</code>.</p>
<p>Annotation <code class="language-plaintext highlighter-rouge">@Repository</code> memberi tahu pada Spring bahwa class ini adalah salah satu <code class="language-plaintext highlighter-rouge">@Component</code> dalam aplikasi kita. Semua @Component akan dipindai pada waktu inisialisasi dan kemudian diregistrasi ke dalam object <code class="language-plaintext highlighter-rouge">ApplicationContext</code> milik Spring. Selain <code class="language-plaintext highlighter-rouge">@Repository</code>, <code class="language-plaintext highlighter-rouge">@Component</code> juga memiliki turunan <code class="language-plaintext highlighter-rouge">@Service</code> dan <code class="language-plaintext highlighter-rouge">@Controller</code>. <code class="language-plaintext highlighter-rouge">@Service</code> biasanya digunakan untuk menandai class-class facade atau business delegate. Sedangkan <code class="language-plaintext highlighter-rouge">@Controller</code> digunakan untuk aplikasi web. <code class="language-plaintext highlighter-rouge">@Service</code> dan <code class="language-plaintext highlighter-rouge">@Controller</code> akan kita bahas di artikel terpisah.</p>
<p>Annotation <code class="language-plaintext highlighter-rouge">@Transactional</code> menandakan bahwa semua method dalam class ini akan dijalankan dalam transaksi database. Kita memberikan nilai <code class="language-plaintext highlighter-rouge">readOnly=true</code> pada deklarasi class, menandakan bahwa secara default transaksi hanya digunakan untuk mengambil data dari database. Perhatikan method save. Pada method ini, kita akan memasukkan atau mengubah data dalam database. Untuk satu method ini, kita membutuhkan transaksi yang tidak readOnly. Karena itu, kita override konfigurasi default dengan cara memberikan annotation <code class="language-plaintext highlighter-rouge">@Transactional(readOnly=false)</code>.</p>
<h2 id="konfigurasi-spring-framework">Konfigurasi Spring Framework</h2>
<p>Sekarang mari kita lihat konfigurasi Application Context. File ini disave dengan nama <code class="language-plaintext highlighter-rouge">applicationContext.xml</code>.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xmlns:p=</span><span class="s">"http://www.springframework.org/schema/p"</span>
<span class="na">xmlns:aop=</span><span class="s">"http://www.springframework.org/schema/aop"</span>
<span class="na">xmlns:context=</span><span class="s">"http://www.springframework.org/schema/context"</span>
<span class="na">xmlns:jee=</span><span class="s">"http://www.springframework.org/schema/jee"</span>
<span class="na">xmlns:tx=</span><span class="s">"http://www.springframework.org/schema/tx"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"</span><span class="nt">></span>
<span class="nt"><context:property-placeholder</span> <span class="na">location=</span><span class="s">"classpath:jdbc.properties"</span><span class="nt">/></span>
<span class="nt"><context:annotation-config/></span>
<span class="nt"><context:component-scan</span> <span class="na">base-package=</span><span class="s">"tutorial.spring25"</span><span class="nt">/></span> <span class="c"><!-- tidak perlu deklarasi masing2 DAO --></span>
<span class="nt"><tx:annotation-driven</span> <span class="nt">/></span> <span class="c"><!-- tidak perlu deklarasi transaction setting per method --></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"dataSource"</span>
<span class="na">class=</span><span class="s">"org.springframework.jdbc.datasource.DriverManagerDataSource"</span>
<span class="na">destroy-method=</span><span class="s">"close"</span>
<span class="na">p:driverClassName=</span><span class="s">"${jdbc.driver}"</span>
<span class="na">p:url=</span><span class="s">"${jdbc.url}"</span>
<span class="na">p:username=</span><span class="s">"${jdbc.username}"</span>
<span class="na">p:password=</span><span class="s">"${jdbc.password}"</span> <span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"transactionManager"</span>
<span class="na">class=</span><span class="s">"org.springframework.jdbc.datasource.DataSourceTransactionManager"</span>
<span class="na">p:dataSource-ref=</span><span class="s">"dataSource"</span> <span class="nt">/></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>Seperti kita lihat di atas, kita tidak lagi membutuhkan deklarasi untuk object <code class="language-plaintext highlighter-rouge">personDao</code> seperti pada Spring sebelumnya. Kita juga tidak perlu membuat konfigurasi transaksi untuk masing-masing method dalam <code class="language-plaintext highlighter-rouge">PersonDao</code>. Sebagai gambaran, kita menghilangkan beberapa baris yang seperti ini.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"personDaoImpl"</span> <span class="na">class=</span><span class="s">"tutorial.spring25.dao.springjdbc.PersonDaoSpringJdbc"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dataSource"</span> <span class="na">ref=</span><span class="s">"dataSource"</span><span class="nt">></property></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"personDao"</span> <span class="na">class=</span><span class="s">"org.springframework.transaction.interceptor.TransactionProxyFactoryBean"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"target"</span> <span class="na">ref=</span><span class="s">"personDao"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"transactionManager"</span> <span class="na">ref=</span><span class="s">"transactionManager"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"transactionAttributes"</span><span class="nt">></span>
<span class="nt"><props></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"*"</span><span class="nt">></span>PROPAGATION_REQUIRED,readOnly<span class="nt"></prop></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"save*"</span><span class="nt">></span>PROPAGATION_REQUIRED<span class="nt"></prop></span>
<span class="nt"></props></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>Semakin banyak class DAO kita, deklarasinya juga akan semakin banyak. Pada aplikasi skala menengah, jumlah DAO bisa mencapai ratusan. Bisa dibayangkan dampaknya terhadap file xml tersebut. Dengan mencantumkan satu baris seperti ini,</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><context:component-scan</span> <span class="na">base-package=</span><span class="s">"tutorial.spring25"</span><span class="nt">/></span>
</code></pre></div></div>
<p>Spring dapat secara otomatis memeriksa seluruh package tutorial.spring dan mendaftarkan semua class yang memiliki annotation <code class="language-plaintext highlighter-rouge">@Component</code>, <code class="language-plaintext highlighter-rouge">@Repository</code>, <code class="language-plaintext highlighter-rouge">@Service</code>, dan <code class="language-plaintext highlighter-rouge">@Controller</code>.</p>
<p>Kita sudah lihat bagaimana keseluruhan kode program ditulis. Kecuali implementasi sebenarnya tentu saja. Sebelum melihat secara detail bagaimana kode program untuk <code class="language-plaintext highlighter-rouge">INSERT</code> dan <code class="language-plaintext highlighter-rouge">SELECT</code>, terlebih dulu kita lihat bagaimana class <code class="language-plaintext highlighter-rouge">PersonDaoSpringJdbc</code> ini digunakan.</p>
<h2 id="automated-testing">Automated Testing</h2>
<p>Daripada menggunakan cara yang kurang berwawasan (menggunakan method main), saya akan mengambil pendekatan yang lebih berpendidikan, yaitu menggunakan Unit Test. Lihat <a href="http://endy.artivisi.com/blog/java/ruthless-testing-1/">artikel saya tentang Unit Test dan Integration Test</a> untuk memahami kode berikut.</p>
<p>Ini adalah kerangka class test untuk <code class="language-plaintext highlighter-rouge">PersonDaoSpringJdbc</code>. Class ini dibuat dengan menggunakan JUnit 4. Isinya masih belum lengkap. Kita akan lengkapi sambil jalan.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">test.spring25.dao.springjdbc</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PersonDaoSpringJdbcTest</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ApplicationContext</span> <span class="n">applicationContext</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">PersonDao</span> <span class="n">personDao</span><span class="o">;</span>
<span class="nd">@BeforeClass</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">(){}</span>
<span class="nd">@Before</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">resetDatabase</span><span class="o">(){}</span>
<span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testGetById</span><span class="o">(){}</span>
<span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testGetAll</span><span class="o">(){}</span>
<span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testSave</span><span class="o">(){}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Seperti kita lihat, kita sudah menggunakan annotation untuk menandai method test dan inisialisasi. Penjelasan tentang JUnit 4 akan dibahas pada artikel terpisah.</p>
<p>Sekarang kita lihat isi masing-masing method. Method <code class="language-plaintext highlighter-rouge">init</code> isinya seperti ini.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@BeforeClass</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">(){</span>
<span class="n">ctx</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ClassPathXmlApplicationContext</span><span class="o">(</span><span class="s">"applicationContext.xml"</span><span class="o">);</span>
<span class="n">dataSource</span> <span class="o">=</span> <span class="o">(</span><span class="nc">DataSource</span><span class="o">)</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"dataSource"</span><span class="o">);</span>
<span class="n">personDao</span> <span class="o">=</span> <span class="o">(</span><span class="nc">PersonDao</span><span class="o">)</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"personDao"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Method <code class="language-plaintext highlighter-rouge">resetDatabase</code> dijalankan sebelum masing-masing test method. Fungsinya untuk menghapus isi tabel <code class="language-plaintext highlighter-rouge">T_PERSON</code> dan mengisi sampel data sesuai yang kita inginkan. Ini dilakukan menggunakan DBUnit. Isinya sebagai berikut</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Before</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">resetDatabase</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="kd">final</span> <span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="n">ds</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
<span class="nc">DatabaseOperation</span><span class="o">.</span><span class="na">CLEAN_INSERT</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="k">new</span> <span class="nc">DatabaseConnection</span><span class="o">(</span><span class="n">conn</span><span class="o">),</span> <span class="k">new</span> <span class="nc">FlatXmlDataSet</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="s">"fixtures/person.xml"</span><span class="o">)));</span>
<span class="n">conn</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="sample-data">Sample Data</h2>
<p>Method di atas akan menggunakan sampel data yang ada di file <code class="language-plaintext highlighter-rouge">person.xml</code>. Isinya seperti ini,</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dataset></span>
<span class="nt"><T_PERSON</span>
<span class="na">id=</span><span class="s">"100"</span>
<span class="na">name=</span><span class="s">"Endy Muhardin"</span>
<span class="na">email=</span><span class="s">"endy.muhardin@gmail.com"</span>
<span class="nt">/></span>
<span class="nt"></dataset></span>
</code></pre></div></div>
<p>Cukup satu record saja.</p>
<h2 id="implementasi-query-database">Implementasi Query Database</h2>
<p>Pertama kali, kita akan implementasi method <code class="language-plaintext highlighter-rouge">getById</code>. Isi testnya tidak rumit. Cukup jalankan method <code class="language-plaintext highlighter-rouge">getById</code> dan periksa hasilnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testGetById</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">Person</span> <span class="n">endy</span> <span class="o">=</span> <span class="n">personDao</span><span class="o">.</span><span class="na">getById</span><span class="o">(</span><span class="mi">100L</span><span class="o">);</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"Endy Muhardin"</span><span class="o">,</span> <span class="n">endy</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"endy.muhardin@gmail.com"</span><span class="o">,</span> <span class="n">endy</span><span class="o">.</span><span class="na">getEmail</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Implementasi <code class="language-plaintext highlighter-rouge">getById</code> dalam <code class="language-plaintext highlighter-rouge">PersonDaoSpringJdbc</code> seperti ini.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">Person</span> <span class="nf">getById</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">simpleJdbcTemplate</span><span class="o">.</span><span class="na">queryForObject</span><span class="o">(</span><span class="s">"select * from T_PERSON where id=?"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">PersonMapper</span><span class="o">(),</span> <span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Cukup satu baris saja.</p>
<h2 id="mapping-dari-resultset-menjadi-person">Mapping dari <code class="language-plaintext highlighter-rouge">ResultSet</code> menjadi <code class="language-plaintext highlighter-rouge">Person</code></h2>
<p>Method ini membutuhkan class <code class="language-plaintext highlighter-rouge">PersonMapper</code> untuk mengkonversi object <code class="language-plaintext highlighter-rouge">ResultSet</code> menjadi object <code class="language-plaintext highlighter-rouge">Person</code>. Class ini dibuat menjadi static final inner class dalam <code class="language-plaintext highlighter-rouge">PersonDaoSpringJdbc</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">PersonDaoSpringJdbc</span> <span class="kd">implements</span> <span class="nc">PersonDao</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">PersonMapper</span> <span class="kd">implements</span> <span class="nc">ParameterizedRowMapper</span><span class="o"><</span><span class="nc">Person</span><span class="o">>{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Person</span> <span class="nf">mapRow</span><span class="o">(</span><span class="kd">final</span> <span class="nc">ResultSet</span> <span class="n">rs</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">rowNum</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{</span>
<span class="kd">final</span> <span class="nc">Person</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="o">();</span>
<span class="n">result</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getLong</span><span class="o">(</span><span class="s">"id"</span><span class="o">));</span>
<span class="n">result</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"name"</span><span class="o">));</span>
<span class="n">result</span><span class="o">.</span><span class="na">setEmail</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"email"</span><span class="o">));</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="implementasi-query-lainnya">Implementasi Query lainnya</h2>
<p>Selanjutnya, kita akan implementasikan method <code class="language-plaintext highlighter-rouge">getAll</code>. Berikut test methodnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testGetAll</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Person</span><span class="o">></span> <span class="n">result</span> <span class="o">=</span> <span class="n">personDao</span><span class="o">.</span><span class="na">getAll</span><span class="o">();</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">result</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
<span class="nc">Person</span> <span class="n">endy</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"Endy Muhardin"</span><span class="o">,</span> <span class="n">endy</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"endy.muhardin@gmail.com"</span><span class="o">,</span> <span class="n">endy</span><span class="o">.</span><span class="na">getEmail</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dan ini implementasi dari method <code class="language-plaintext highlighter-rouge">getAll</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Person</span><span class="o">></span> <span class="nf">getAll</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">simpleJdbcTemplate</span><span class="o">.</span><span class="na">query</span><span class="o">(</span><span class="s">"select * from T_PERSON"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">PersonMapper</span><span class="o">(),</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">>());</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="implementasi-insert-data">Implementasi Insert Data</h2>
<p>Terakhir, mari kita implementasi method save. Method testnya sedikit lebih panjang, karena untuk yakin akan hasilnya, kita harus melakukan query ke database dengan JDBC murni.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testSave</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">Person</span> <span class="n">dhiku</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="o">();</span>
<span class="n">dhiku</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Hadikusuma Wahab"</span><span class="o">);</span>
<span class="n">dhiku</span><span class="o">.</span><span class="na">setEmail</span><span class="o">(</span><span class="s">"dhiku@gmail.com"</span><span class="o">);</span>
<span class="n">assertNull</span><span class="o">(</span><span class="n">dhiku</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="n">personDao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">dhiku</span><span class="o">);</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">dhiku</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="kd">final</span> <span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="n">ds</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
<span class="kd">final</span> <span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"select * from T_PERSON where id=?"</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setLong</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">dhiku</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="kd">final</span> <span class="nc">ResultSet</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">ps</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">();</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">());</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="n">dhiku</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"name"</span><span class="o">));</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="n">dhiku</span><span class="o">.</span><span class="na">getEmail</span><span class="o">(),</span> <span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"email"</span><span class="o">));</span>
<span class="n">ps</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="n">rs</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="n">conn</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Untungnya implementasi method <code class="language-plaintext highlighter-rouge">save</code> juga hanya satu baris.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">save</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Person</span> <span class="n">person</span><span class="o">)</span> <span class="o">{</span>
<span class="n">person</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">simpleJdbcInsert</span><span class="o">.</span><span class="na">executeAndReturnKey</span><span class="o">(</span><span class="k">new</span> <span class="nc">BeanPropertySqlParameterSource</span><span class="o">(</span><span class="n">person</span><span class="o">)).</span><span class="na">longValue</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Ada beberapa hal yang perlu dijelaskan dari kode di atas.
Pertama, kita perlu mengaktifkan transaksi database untuk mengubah isi database. Kita lakukan dengan <code class="language-plaintext highlighter-rouge">@Transactional(readOnly=false)</code>.</p>
<p>Kedua, kita bisa menggunakan fitur terbaru Spring JDBC, yaitu <code class="language-plaintext highlighter-rouge">SimpleJdbcInsert</code>. Fitur ini mampu melihat ke dalam database dan mengambil daftar nama fieldnya. Object ini diinisialisasi pada saat kita menginjeksi <code class="language-plaintext highlighter-rouge">DataSource</code>. Berikut kodenya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Autowired</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setDataSource</span><span class="o">(</span><span class="kd">final</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">simpleJdbcTemplate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleJdbcTemplate</span><span class="o">(</span><span class="n">dataSource</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">simpleJdbcInsert</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleJdbcInsert</span><span class="o">(</span><span class="n">dataSource</span><span class="o">).</span><span class="na">withTableName</span><span class="o">(</span><span class="s">"T_PERSON"</span><span class="o">).</span><span class="na">usingGeneratedKeyColumns</span><span class="o">(</span><span class="s">"id"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada saat menginisialisasi object <code class="language-plaintext highlighter-rouge">SimpleJdbcInsert</code> kita memberi tahu Spring tentang nama tabel dan field yang isinya autogenerated, misalnya <code class="language-plaintext highlighter-rouge">id</code>.</p>
<p>Selama nama field dalam <code class="language-plaintext highlighter-rouge">T_PERSON</code> sama dengan nama properti di class <code class="language-plaintext highlighter-rouge">Person</code>, kita bisa menghilangkan kode untuk mapping properti ke <code class="language-plaintext highlighter-rouge">PreparedStatement</code>. Kode yang biasanya tiga baris seperti ini</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"insert into T_PERSON (name, email) values(?,?)"</span><span class="o">);</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">person</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">person</span><span class="o">.</span><span class="na">getEmail</span><span class="o">());</span>
</code></pre></div></div>
<p>Dapat direduksi menjadi satu baris seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">SqlParameterSource</span> <span class="n">parameterSource</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BeanPropertySqlParameterSource</span><span class="o">(</span><span class="n">person</span><span class="o">);</span>
</code></pre></div></div>
<p>Object <code class="language-plaintext highlighter-rouge">parameterSource</code> ini bisa langsung diumpankan ke <code class="language-plaintext highlighter-rouge">simpleJdbcInsert</code> seperti ini untuk melakukan insert sekaligus mengambil nilai id yang digenerate database.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Long</span> <span class="n">newId</span> <span class="o">=</span> <span class="n">simpleJdbcInsert</span><span class="o">.</span><span class="na">executeAndReturnKey</span><span class="o">(</span><span class="n">parameterSource</span><span class="o">).</span><span class="na">longValue</span><span class="o">()</span>
</code></pre></div></div>
<p>Fitur <code class="language-plaintext highlighter-rouge">SimpleJdbcInsert</code> ini sangat bermanfaat kalau entity class kita terdiri dari puluhan field. Adanya method <code class="language-plaintext highlighter-rouge">executeAndReturnKey</code> untuk mengambil auto-generated primary key dari database juga akan sangat membantu kita untuk menghilangkan perbedaan antar database. Biasanya masing-masing merek database memiliki cara yang berbeda-beda untuk mengambil nilai ini.</p>
<p>Sebagai contoh, bila kita lakukan secara manual untuk database MySQL, kodenya akan tampak seperti ini.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span><span class="o">=</span><span class="kc">false</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">save</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Person</span> <span class="n">person</span><span class="o">)</span> <span class="o">{</span>
<span class="n">simpleJdbcInsert</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="k">new</span> <span class="nc">BeanPropertySqlParameterSource</span><span class="o">(</span><span class="n">person</span><span class="o">));</span>
<span class="nc">Long</span> <span class="n">newId</span> <span class="o">=</span> <span class="n">simpleJdbcInsert</span><span class="o">.</span><span class="na">getJdbcOperations</span><span class="o">().</span><span class="na">queryForLong</span><span class="o">(</span><span class="s">"select last_insert_id()"</span><span class="o">);</span>
<span class="n">person</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">newId</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Demikianlah sekilas tentang penggunaan fitur Spring terbaru untuk mengakses database. Pada artikel selanjutnya, kita akan lihat fitur-fitur baru di sisi MVC framework.</p>
Mengelola Proyek dengan Redmine2007-11-07T15:33:55+07:00https://software.endy.muhardin.com/aplikasi/redmine<p><a href="http://www.redmine.org/">Redmine</a> adalah aplikasi manajemen proyek yang dibuat menggunakan framework Ruby on Rails.
Pada saat artikel ini ditulis, Redmine sudah mencapai versi 0.5.1 yang dirilis 15 Juli 2007.</p>
<p>Selain Redmine, banyak juga aplikasi manajemen proyek lainnya, misalnya:</p>
<ul>
<li>
<p><a href="http://www.activecollab.com/">ActiveCollab</a> (dulu gratis sekarang bayar)</p>
</li>
<li>
<p><a href="http://trac.edgewall.org/">Trac</a></p>
</li>
<li>
<p><a href="http://www.basecamphq.com/">BaseCamp</a> (hanya bisa diakses di websitenya, tidak bisa diinstal lokal)</p>
</li>
<li>
<p><a href="http://www.dotproject.net/">DotProject</a></p>
</li>
</ul>
<p>Redmine mendukung multiple project. Jadi kita bisa menginstal Redmine di perusahaan untuk mengelola semua proyek yang sedang berjalan.</p>
<p>Untuk pengelolaan proyek, Redmine memiliki Gantt chart dan Calendar. Untuk mengelola dokumentasi proyek, kita bisa menggunakan wiki yang sudah tersedia. Tugas dibagikan pada team member dengan menggunakan konsep issue yang akan dijelaskan di bawah. Kita bahkan bisa melihat kode program yang sudah dibuat menggunakan version control browser. Saat ini Redmine dapat melihat isi repository Subversion, CVS, Mercurial, dan Darcs.</p>
<p>Pertama, kita <a href="http://dhiku.wordpress.com/2007/10/23/installing-redmine-di-windows/">instal dulu Redmine</a>.</p>
<p>Selanjutnya, mari kita langsung saja menggunakan Redmine.</p>
<p>Setelah Redmine terinstal, kita dapat langsung membuka browser dan melihat halaman depan Redmine. Segera klik tombol Login untuk masuk ke dalam sistem.</p>
<p><a href="/images/uploads/2007/11/redmine-login.png"><img src="/images/uploads/2007/11/redmine-login.png" alt="Login Screen " /></a></p>
<p>Default username dan passwordnya adalah admin/admin. Segera masuk ke halaman My Account untuk mengganti password administrator.</p>
<h3 id="membuat-user">Membuat user</h3>
<p>Buat user baru melalui menu Administration > Users > New.</p>
<p><a href="/images/uploads/2007/11/new-user.png"><img src="/images/uploads/2007/11/new-user.png" alt="Menu New User " /></a></p>
<p>Isi informasi yang sesuai di halaman form user baru.</p>
<p><a href="/images/uploads/2007/11/new-user-form.png"><img src="/images/uploads/2007/11/new-user-form.png" alt="Form User " /></a></p>
<p>Jangan lupa untuk membuat beberapa user, agar bisa digunakan di</p>
<h3 id="membuat-project">Membuat project</h3>
<p>Selanjutnya, kita mendefinisikan project. Buat project baru melalui menu Administration > Projects > New.
Isi informasi tentang project.</p>
<p><a href="/images/uploads/2007/11/new-project-summary.png"><img src="/images/uploads/2007/11/new-project-summary.png" alt="Project Summary " /></a></p>
<p>Isikan juga informasi tentang version control bila ada.</p>
<p>Berikutnya, tentukan anggota project. Tentu saja saja anggota ini sudah harus didaftarkan dulu seperti pada langkah sebelumnya.</p>
<p><a href="/images/uploads/2007/11/project-member.png"><img src="/images/uploads/2007/11/project-member.png" alt="Assign Member " /></a></p>
<p>Setelah itu, kita tentukan Version dalam proyek. Version ini bisa iterasi, atau fase, tergantung dari siklus pengembangan yang kita gunakan dalam proyek. Version ini nantinya digunakan sebagai target penyelesaian suatu issue.</p>
<p><a href="/images/uploads/2007/11/version.png"><img src="/images/uploads/2007/11/version.png" alt="Project Version " /></a></p>
<p>Di halaman selanjutnya, kita akan menentukan kategori untuk issue. Semua tugas di dalam Redmine disebut Issue. Ada tiga jenis issue:</p>
<ul>
<li>
<p>Feature: Ini digunakan untuk membuat semacam To Do List untuk fitur yang akan dibuat dalam proyek kita.</p>
</li>
<li>
<p>Bug: Ini digunakan untuk mencatat dan melacak status penyelesaian defect dalam proyek kita.
Selain untuk bug aplikasi, saya biasanya menggunakan jenis issue ini untuk mencatat</p>
<ul>
<li>
<p>Resiko Proyek</p>
</li>
<li>
<p>Kesalahan dokumen (salah requirement, revisi project schedule, dsb)</p>
</li>
<li>
<p>Masalah yang terjadi dalam proyek</p>
</li>
</ul>
</li>
<li>
<p>Support: fitur ini tidak saya gunakan. Mungkin ini ditujukan untuk pertanyaan dari user yang belum tentu bug.</p>
</li>
</ul>
<p>Saya menggunakan kategori berikut untuk issue:</p>
<ul>
<li>
<p>Project Document: Dokumentasi project seperti schedule, progress report, dsb</p>
</li>
<li>
<p>Functional Specification: spesifikasi aplikasi yang ingin dibuat, sering disebut juga dengan dokumen analisa</p>
</li>
<li>
<p>Technical Specification: spesifikasi tentang bagaimana cara membuatnya, sering disebut juga dengan dokumen desain</p>
</li>
<li>
<p>User Documentation: segala issue yang berkaitan dengan dokumen user manual</p>
</li>
<li>
<p>Business Layer: komponen logika bisnis dari aplikasi</p>
</li>
<li>
<p>User Interface Layer: komponen tampilan aplikasi</p>
</li>
<li>
<p>Data Access Layer: komponen aplikasi yang berinteraksi dengan database</p>
</li>
</ul>
<p>Persiapan project selesai. Sekarang kita bisa langsung membuat daftar pekerjaan. Melalui menu, klik Nama Project > New Issue > Feature.</p>
<p><a href="/images/uploads/2007/11/new-feature.png"><img src="/images/uploads/2007/11/new-feature.png" alt="New Feature " /></a></p>
<p>Kita bisa daftarkan tugas yang harus dilakukan dengan mengisi informasi pada formnya. Setelah diisi, tekan Save dan lihat hasilnya pada tampilan daftar issue.</p>
<p><a href="/images/uploads/2007/11/list-issue.png"><img src="/images/uploads/2007/11/list-issue.png" alt="List of Issue " /></a></p>
<p>Issue yang sudah didaftarkan dapat dilacak pengerjaannya. Dari tampilan daftar issue, klik nama issue sehingga muncul tampilan detailnya.</p>
<p><a href="/images/uploads/2007/11/detail-issue.png"><img src="/images/uploads/2007/11/detail-issue.png" alt="Detail Issue " /></a></p>
<p>Kita bisa klik Log Time untuk memasukkan waktu yang sudah kita gunakan untuk menyelesaikan issue tersebut.</p>
<p><a href="/images/uploads/2007/11/log-time.png"><img src="/images/uploads/2007/11/log-time.png" alt="Log Time " /></a></p>
<p>Isikan jumlah jam yang digunakan, misalnya 2 jam, lalu klik Save. Selanjutnya kita akan diarahkan ke halaman Spent Time. Halaman ini menunjukkan jumlah waktu yang sudah digunakan untuk berbagai task dalam project. Kita bisa melihat jumlah waktu untuk satu task saja ataupun keseluruhan project.</p>
<p>Sekarang kita sudah memiliki beberapa issue yang terdaftar. Data tersebut ditampilkan oleh Redmine dalam berbagai bentuk, misalnya:</p>
<h4 id="calendar">Calendar</h4>
<p><a href="/images/uploads/2007/11/calendar.png"><img src="/images/uploads/2007/11/calendar.png" alt="Calendar " /></a></p>
<h4 id="gantt-chart">Gantt Chart</h4>
<p><a href="/images/uploads/2007/11/gantt.png"><img src="/images/uploads/2007/11/gantt.png" alt="Gantt Chart " /></a></p>
<h4 id="report">Report</h4>
<p><a href="/images/uploads/2007/11/report.png"><img src="/images/uploads/2007/11/report.png" alt="Report " /></a></p>
<h4 id="activity">Activity</h4>
<p><a href="/images/uploads/2007/11/activity.png"><img src="/images/uploads/2007/11/activity.png" alt="Activity " /></a></p>
<h4 id="roadmap">Roadmap</h4>
<p><a href="/images/uploads/2007/11/roadmap.png"><img src="/images/uploads/2007/11/roadmap.png" alt="Roadmap " /></a></p>
<p>Untuk tampilan Activity dan Roadmap mirip sekali dengan Trac. Activity di Trac disebut dengan Timeline. Entah disengaja atau tidak, dari tampilan sampai cara kerjanya tidak dapat dibedakan. Silahkan lihat sendiri.</p>
<h4 id="timeline-trac">Timeline Trac</h4>
<p><a href="/images/uploads/2007/11/timeline-trac.png"><img src="/images/uploads/2007/11/timeline-trac.png" alt="Timeline ala Trac " /></a></p>
<h4 id="roadmap-trac">Roadmap Trac</h4>
<p><a href="/images/uploads/2007/11/roadmap-trac.png"><img src="/images/uploads/2007/11/roadmap-trac.png" alt="Roadmap ala Trac " /></a></p>
<p>Kita juga bisa melihat isi repository Subversion kita.
<a href="/images/uploads/2007/11/repo-browser.png"><img src="/images/uploads/2007/11/repo-browser.png" alt="Repo Browser " /></a></p>
<p>Seperti repo browser lainnya, kita bisa melihat perbandingan antara dua versi file yang berbeda.
<a href="/images/uploads/2007/11/diff-inline.png"><img src="/images/uploads/2007/11/diff-inline.png" alt="Inline Diff " /></a></p>
<p>Perbedaan ini bisa ditampilkan secara inline seperti gambar di atas, atau secara berdampingan seperti ini.</p>
<p><a href="/images/uploads/2007/11/diff-byside.png"><img src="/images/uploads/2007/11/diff-byside.png" alt="Side by Side Diff " /></a></p>
<p>Demikianlah sekilas tentang aplikasi Redmine. Masih banyak fitur Redmine yang belum dieksplorasi, misalnya wiki, document management, dan file management. Mengingat umurnya yang masih muda, besar harapan Redmine akan semakin canggih di masa yang akan datang.</p>
<p>Dengan menggunakan aplikasi ini, kita dapat mengelola berbagai aspek dalam manajemen proyek kita secara terpusat. Redmine gratis dan mudah diinstal, jadi tunggu apa lagi … segera gunakan.</p>
Pakai IDE apa?2007-11-02T17:09:52+07:00https://software.endy.muhardin.com/java/pakai-ide-apa<p>Posting ini dibuat untuk menanggapi diskusi di milis Netbeans.
Saya menyarankan para programmer, daripada menghabiskan waktunya untuk memperdebatkan editor, lebih baik menginvestasikan waktu dan energi untuk memperdalam konsep.
Ekstrimnya, coding dengan Notepad bila perlu.</p>
<p>Salah satu <a href="http://www.antonraharja.web.id/">programmer hebat yang saya kenal</a>, sampai hari ini masih coding menggunakan editornya Midnight Commander (MC). Untuk urusan produktivitas, <a href="http://www.antonraharja.web.id/curriculum-vitae/">daftar hasil karyanya</a> lebih panjang daripada anda-anda yang berdebat Eclipse vs Netbeans.</p>
<p>Dari keseluruhan hasil karyanya, perkiraan saya paling tidak 75% dibuat dengan editor MC. Dan aplikasi yang ada di sana adalah aplikasi betulan, bukan tugas kuliah, bukan skripsi.</p>
<p>Editor Midnight Commander, kalau ingin tau, terlihat seperti ini:</p>
<p><a href="/images/uploads/2007/11/mcedit.png"><img src="/images/uploads/2007/11/mcedit.png" alt="Tampilan Editor MC " /></a></p>
<p>Tidak ada autocomplete, tidak bisa mouse over terus keluar JavaDoc, tidak bisa klik kanan - Deploy in Tomcat.</p>
<p>Komentar di milis Netbeans tentang coding pakai editor seperti ini:</p>
<blockquote>
<p>waduh pake notepad… be-darah2 itu mah codingnyah</p>
</blockquote>
<p>Not really … gak juga kok.
Memang lebih lambat daripada pakai Eclipse/Netbeans, soalnya kalo ada syntax error baru ketahuan setelah compile.</p>
<p>Workflownya mirip dengan coding PHP.
Edit kodenya, save, refresh browser.</p>
<p>Kalau Java, edit, save, Alt-Tab ke konsol, ant compile.
Nanti kan keluar pesan errornya di baris berapa.</p>
<p>Buat yang belum merasakan, memang terkesan seperti latihan kungfu di Shaolin.
Keras dan melelahkan.</p>
<p>Tapi ada hasilnya:</p>
<ul>
<li>
<p>Bebas mau pakai IDE apa aja. <br />
Bisa pakai the right tools for the right job. Spring development, pakai Eclipse. Desktop development, pakai Netbeans. Bikin aplikasi desktop yang mengakses Webservice, pakai 2-2nya</p>
</li>
<li>
<p>Lebih jeli dalam mendebug. <br />
Sampai saat ini, sebagian besar XML code harus didebug manual, karena memang by nature sulit untuk disupport IDE. Development jaman sekarang, pasti harus melibatkan XML. Dengan coding pakai editor minimalis, kita akan terbiasa melihat error message dan mengartikannya.</p>
</li>
<li>
<p>Lebih mudah mempelajari teknik automation, misalnya Continuous Integration (CI). <br />
Soalnya CI itu mengharuskan kita bisa compile tanpa IDE. Buat yang tidak terbiasa (apalagi lulusan VB/Delphi), pasti akan kaget, bagaimana bisa compile tanpa IDE?</p>
</li>
<li>
<p>Lebih cepat belajar bahasa pemrograman baru.<br />
Kalau kita terbiasa coding pakai IDE, begitu belajar bahasa baru, hal pertama yang kita pikirkan adalah, “IDE-nya pakai apa ya??”. Lalu menghabiskan dua minggu debat di milis, baru pilih salah satu. Download butuh waktu 2 hari. Setelah terinstal, baca Help setengah jam, baru tau cara bikin file baru. Setelah coding Hello World, bingung cari tombol Compile dan Run, 2 jam lagi untuk baca Help. Notepad coder tidak. Langsung buka apapun editor yang tersedia, mulai coding dan compile via command-line. Pada saat IDE-code baru bisa menjalankan Hello World, Notepad-coder sudah paham tentang <a href="http://en.wikipedia.org/wiki/Duck_typing">duck-typing</a>.</p>
</li>
</ul>
<p>Satu pertanyaan yang selalu saya ajukan kalau ada yang tanya tentang IDE, RAD tools, dan sejenisnya.</p>
<blockquote>
<p>“Apakah ini coding untuk belajar, atau coding untuk mencari nafkah??”</p>
</blockquote>
<p>Kalau jawabannya belajar, entah itu mahasiswa, atau pro yang belajar framework baru, gunakan tools sesedikit mungkin.</p>
<p>Alasannya adalah, karena tujuan kita untuk paham, bukan untuk selesai.
Semakin banyak/canggih tools yang digunakan, pemahaman semakin minim.
Tambahan lagi, kalau ada error (yang mana akan sering terjadi, namanya juga lagi belajar) sulit membedakan error karena salah pakai tools, atau error di kode program kita.</p>
<p>Belajar Spring AOP dengan Notepad, tidak mungkin notepad yang salah, pasti kode kita.
Belajar Spring AOP dengan Netbeans/Eclipse, ada kemungkinan tools tersebut salah loading classpath, punya asumsi sendiri tentang struktur folder, dsb.</p>
<p>Kalau jawabannya untuk mencari nafkah, gunakan tools tercanggih yang bisa diperoleh dengan halal.
Hal ini hanya mungkin jika kita tidak terikat pada tools tertentu.
Tidak akan bisa coding desktop dengan Netbeans, kemudian buka Eclipse untuk coding server-side backend.</p>
<p>Demikian saran dari saya.</p>
Konfigurasi Mentari GPRS dengan LG KG300 di Ubuntu Gutsy2007-10-26T16:34:32+07:00https://software.endy.muhardin.com/linux/gprs-kg300<p>Maraknya akses internet 3G rupanya berimbas pada turunnya harga layanan GPRS. Salah satu yang sudah turun harga adalah Mentari, dari Rp. 5/kB menjadi Rp. 1/kB, setidaknya sampai Januari 2008.</p>
<p>Dengan adanya perkembangan yang menggembirakan ini, saya segera mengaktifkan koneksi GPRS tersebut agar bisa digunakan dari laptop. Caranya tidak terlalu sulit, seperti akan kita lihat segera.</p>
<h3 id="hardware">Hardware</h3>
<p>Laptop saya <a href="http://minisites.nec-computers-ap.com/e3100/techspecs.html">NEC Versa E3100</a>, tepatnya seri E3100-1800DR. Handphone yang saya gunakan adalah <a href="http://tokolg.com/productsDetail.asp?item=KG300&kategori=HP">LG KG300</a>, seperti yang diiklankan Agnes Monica di TV. Jadi kalau kapan-kapan Agnes mau setting GPRS juga, bisa lihat artikel ini :D</p>
<p>Koneksi handphone ke laptop menggunakan kabel data, karena laptop saya tidak ada blututnya.</p>
<h3 id="software">Software</h3>
<p>Saya menggunakan Ubuntu Gutsy Gibbon 7.10, yang sudah dilengkapi dengan wvdialconf dan wvdial untuk melakukan koneksi. Tidak perlu ada instalasi software tambahan, karena kedua aplikasi ini sudah terinstal dari sananya.</p>
<h3 id="provider">Provider</h3>
<p>Saya pakai Mentari. Sebelum mencoba koneksi melalui komputer, pastikan dulu kita bisa browsing langsung dari HP. Ini untuk memastikan bahwa dari HP ke Provider sudah OK. Jangan sampai sudah pusing-pusing utak-atik Ubuntu, ternyata masalahnya ada di HP.
Untuk LG KG300, kita bisa konfigurasi otomatis melalui OTA. Cukup kirim SMS ke 3000 dengan isi sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gprs lg kg300
</code></pre></div></div>
<p>Nanti kita akan terima SMS, yang bila dibuka akan ada konfirmasi apakah kita ingin mengkonfigurasi GPRS. Tentu saja jawab Yes.
Silahkan browsing ke Yahoo di http://m.yahoo.com atau Gmail di http://gmail.com/app.</p>
<p>Bila Anda punya akun YM, bisa dicoba chatting di sana.</p>
<p>LG KG300 punya fasilitas Java dengan versi MIDP 2.0. Bila malas chat melalui antarmuka Yahoo, bisa gunakan aplikasi <a href="http://shmessenger.ro/lang_en/index.jsp">shMessenger</a>.</p>
<h3 id="konfigurasi">Konfigurasi</h3>
<p>Setelah lancar browsing dan chatting di handphone, kini tiba saatnya kita browsing di laptop. Hubungkan handphone ke laptop melalui kabel data yang disediakan. Nanti di handphone akan muncul pertanyaan, apakah kita ingin terhubung sebagai Storage Media atau COM2. Pilih COM2. Storage Media dipilih bila ingin melakukan transfer file.</p>
<p>Oh iya, untuk memudahkan troubleshooting, selama melakukan konfigurasi, tampilkan kernel log dengan perintah <code class="language-plaintext highlighter-rouge">tail -f /var/log/messages</code> di konsol. Biarkan window ini tetap terbuka selama konfigurasi.</p>
<p>Di kernel log akan muncul laporan deteksi modem seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Oct 26 09:19:28 sweetdreams kernel: [ 507.608000] usb 1-2: new full speed USB device using uhci_hcd and address 2
Oct 26 09:19:29 sweetdreams kernel: [ 507.792000] usb 1-2: configuration #1 chosen from 1 choice
Oct 26 09:19:29 sweetdreams kernel: [ 508.100000] cdc_acm 1-2:1.1: ttyACM0: USB ACM device
Oct 26 09:19:29 sweetdreams kernel: [ 508.104000] usbcore: registered new interface driver cdc_acm
Oct 26 09:19:29 sweetdreams kernel: [ 508.104000] /build/buildd/linux-source-2.6.22-2.6.22/drivers/usb/class/cdc-acm.c: v0.25:USB Abstract Control Model driver for USB modems and ISDN adapters
</code></pre></div></div>
<p>Setelah itu, jalankan wvdialconf sebagai root.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo wvdialconf
Editing `/etc/wvdial.conf'.
Scanning your serial ports for a modem.
Modem Port Scan: S0 S1 S2 S3 SL0
WvModem: Cannot get information for serial port.
ttyACM0: ATQ0 V1 E1 -- OK
ttyACM0: ATQ0 V1 E1 Z -- OK
ttyACM0: ATQ0 V1 E1 S0=0 -- OK
ttyACM0: ATQ0 V1 E1 S0=0 &C1 -- OK
ttyACM0: ATQ0 V1 E1 S0=0 &C1 &D2 -- OK
ttyACM0: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
ttyACM0: Modem Identifier: ATI -- MTK2
ttyACM0: Speed 4800: AT -- OK
ttyACM0: Speed 9600: AT -- OK
ttyACM0: Speed 19200: AT -- OK
ttyACM0: Speed 38400: AT -- OK
ttyACM0: Speed 57600: AT -- OK
ttyACM0: Speed 115200: AT -- OK
ttyACM0: Speed 230400: AT -- OK
ttyACM0: Speed 460800: AT -- OK
ttyACM0: Max speed is 460800; that should be safe.
ttyACM0: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
Found an USB modem on /dev/ttyACM0.
/etc/wvdial.conf: Can't open '/etc/wvdial.conf' for reading: No such file or directory
/etc/wvdial.conf: ...starting with blank configuration.
Modem configuration written to /etc/wvdial.conf.
ttyACM0: Speed 460800; init "ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0"
</code></pre></div></div>
<p>Hasilnya adalah sebuah file <code class="language-plaintext highlighter-rouge">/etc/wvdial.conf</code> sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Dialer Defaults]
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Modem Type = USB Modem
; Phone =
ISDN = 0
; Username =
Init1 = ATZ
; Password =
Modem = /dev/ttyACM0
Baud = 460800
</code></pre></div></div>
<p>Edit menjadi seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Dialer Defaults]
Modem = /dev/ttyACM0
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Init3 = AT+CGDCONT=1,"IP","indosatgprs"
Stupid Mode = on
Modem Type = USB Modem
Phone = *99***1#
ISDN = 0
Username = indosat
Password = indosat
Baud = 460800
</code></pre></div></div>
<h3 id="go-online">Go Online</h3>
<p>Kalau sudah, saatnya kita online. Gunakan perintah wvdial untuk melakukan dial-up.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo wvdial
WvDial<*1>: WvDial: Internet dialer version 1.56
WvModem<*1>: Cannot get information for serial port.
WvDial<*1>: Initializing modem.
WvDial<*1>: Sending: ATZ
WvDial Modem<*1>: ATZ
WvDial Modem<*1>: OK
WvDial<*1>: Sending: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
WvDial Modem<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
WvDial Modem<*1>: OK
WvDial<*1>: Sending: AT+CGDCONT=1,"IP","indosatgprs"
WvDial Modem<*1>: AT+CGDCONT=1,"IP","indosatgprs"
WvDial Modem<*1>: OK
WvDial<*1>: Modem initialized.
WvDial<*1>: Sending: ATDT*99***1#
WvDial<*1>: Waiting for carrier.
WvDial Modem<*1>: ATDT*99***1#
WvDial Modem<*1>: CONNECT
WvDial Modem<*1>: ~[7f]}#@!}!} } }2}"}&} } } } }#}$@#}'}"}(}"R[04]~
WvDial<*1>: Carrier detected. Starting PPP immediately.
WvDial<Notice>: Starting pppd at Fri Oct 26 09:25:58 2007
WvDial<Notice>: Pid of pppd: 6028
WvDial<*1>: Using interface ppp0
WvDial<*1>: local IP address 10.33.41.205
WvDial<*1>: remote IP address 10.64.64.64
WvDial<*1>: primary DNS address 202.155.0.10
WvDial<*1>: secondary DNS address 202.155.46.77
</code></pre></div></div>
<p>Setelah itu, silahkan coba browsing. Jangan tutup window wvdial supaya tidak disconnect.</p>
<p>Untuk menghentikan koneksi, cukup ketik Ctrl-C di window wvdial.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Caught signal 2: Attempting to exit gracefully...
WvDial<*1>: Terminating on signal 15
WvDial<*1>: Connect time 1.2 minutes.
WvDial<*1>: Disconnecting at Fri Oct 26 09:27:20 2007
</code></pre></div></div>
<p>Kita dapat melihat keseluruhan sesi internet, berikut jumlah trafik yang kita gunakan di kernel log, sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Oct 26 09:25:58 sweetdreams pppd[6028]: pppd 2.4.4 started by root, uid 0
Oct 26 09:25:58 sweetdreams pppd[6028]: Using interface ppp0
Oct 26 09:25:58 sweetdreams pppd[6028]: Connect: ppp0 <--> /dev/ttyACM0
Oct 26 09:26:00 sweetdreams pppd[6028]: PAP authentication succeeded
Oct 26 09:26:12 sweetdreams pppd[6028]: Could not determine remote IP address: defaulting to 10.64.64.64
Oct 26 09:26:12 sweetdreams pppd[6028]: local IP address 10.33.41.205
Oct 26 09:26:12 sweetdreams pppd[6028]: remote IP address 10.64.64.64
Oct 26 09:26:12 sweetdreams pppd[6028]: primary DNS address 202.155.0.10
Oct 26 09:26:12 sweetdreams pppd[6028]: secondary DNS address 202.155.46.77
Oct 26 09:27:19 sweetdreams pppd[6028]: Terminating on signal 15
Oct 26 09:27:19 sweetdreams pppd[6028]: Connect time 1.2 minutes.
Oct 26 09:27:19 sweetdreams pppd[6028]: Sent 0 bytes, received 0 bytes.
Oct 26 09:27:19 sweetdreams pppd[6028]: Connection terminated.
Oct 26 09:27:19 sweetdreams pppd[6028]: Exit.
</code></pre></div></div>
<p>Demikianlah cara konfigurasi modem LG KG300 di Ubuntu. Mudah-mudahan dengan adanya artikel ini, Agnes Monica bisa segera online.</p>
Continuous Integration dengan Hudson2007-10-09T23:16:08+07:00https://software.endy.muhardin.com/java/hudson<p>Pada artikel sebelumnya, kita sudah membahas <a href="http://endy.artivisi.com/blog/java/luntbuild/">penggunaan Luntbuild</a> dan <a href="http://endy.artivisi.com/blog/java/cruise-control/">CruiseControl</a> untuk menerapkan Continuous Integration (CI). Kali ini, kita akan mencoba <a href="https://hudson.dev.java.net/">Hudson</a>, aplikasi CI lain yang tersedia.</p>
<p>Hudson dapat <a href="https://hudson.dev.java.net/servlets/ProjectDocumentList?folderID=2761&expandFolder=2761&folderID=0">diunduh dari websitenya</a>. Setelah mengunduh, kita akan mendapatkan satu file *war. File ini dapat langsung dijalankan standalone dengan perintah:</p>
<p><code class="language-plaintext highlighter-rouge">java -jar hudson.war</code></p>
<p>Ataupun dideploy ke servlet engine favorit Anda, seperti Tomcat atau sejenisnya.</p>
<p>Setelah dideploy, kita bisa melihat tampilan awalnya melalui browser ke alamat http://localhost:8080 (untuk standalone) atau http://localhost:8080/hudson (bila dideploy ke servlet engine)</p>
<p>Keunggulan pertama Hudson adalah kemudahan setupnya. Segera setelah dijalankan (baik standalone maupun dideploy), kita bisa segera mengunjungi halaman depannya.</p>
<p><a href="/images/uploads/2007/10/welcome.png"><img src="/images/uploads/2007/10/welcome.png" alt="Hudson Welcome Page " /></a></p>
<p>Hal pertama yang saya lakukan adalah konfigurasi email. Bila ini tidak dilakukan, Hudson akan gagal mengirim email laporan hasil build.</p>
<p>Klik Manage Hudson di sebelah kiri atas. Layar konfigurasi akan muncul.</p>
<p><a href="/images/uploads/2007/10/manage-hudson.png"><img src="/images/uploads/2007/10/manage-hudson.png" alt="Manage Hudson " /></a></p>
<p>Selanjutnya, pilih System Configuration, lalu scroll ke bagian bawah untuk mengatur email.</p>
<p><a href="/images/uploads/2007/10/email-config.png"><img src="/images/uploads/2007/10/email-config.png" alt="Email Configuration " /></a></p>
<p>Setelah selesai, klik OK.</p>
<p>Bila Ant belum ada di PATH komputer Anda, jangan lupa untuk menyebutkan folder instalasi Ant di halaman ini.</p>
<p>Setelah selesai, kita bisa langsung mendaftarkan job. Klik tombol New Job di kiri atas.</p>
<p><a href="/images/uploads/2007/10/create-job.png"><img src="/images/uploads/2007/10/create-job.png" alt="Create New Job " /></a></p>
<p>Project saya menggunakan Ant biasa, tanpa Maven. Jadi saya pilih free-style. Klik OK.</p>
<p>Selanjutnya, kita konfigurasi repository project. Isikan nilai yang sesuai di bagian Source Code Management.</p>
<p><a href="/images/uploads/2007/10/configure-svn.png"><img src="/images/uploads/2007/10/configure-svn.png" alt="Configure Subversion " /></a></p>
<p>Segera setelah kursor meninggalkan field Repository URL (on blur), Hudson akan memeriksa URL yang kita berikan. Bila URL tersebut membutuhkan otentikasi, Hudson akan mengeluarkan pesan error dengan link untuk mengkonfigurasi username dan password.</p>
<p><a href="/images/uploads/2007/10/svn-auth.png"><img src="/images/uploads/2007/10/svn-auth.png" alt="Subversion Authentication " /></a></p>
<p>Isikan nilai yang sesuai. Seperti kita lihat, Hudson mendukung beberapa modus otentikasi.</p>
<p>Bagian selanjutnya adalah jadwal build atau trigger. Hudson menggunakan format yang mirip dengan Cron.</p>
<p><a href="/images/uploads/2007/10/build-schedule.png"><img src="/images/uploads/2007/10/build-schedule.png" alt="Build Schedule " /></a></p>
<p>Bila kita ingin melakukan build setiap jam, hanya pada hari kerja, entrinya adalah sebagai berikut:</p>
<p><code class="language-plaintext highlighter-rouge">1 * * * 1-5</code></p>
<p>Agar build berjalan setiap jam 1 dini hari, hari Senin dan Rabu, entrinya sebagai berikut:</p>
<p><code class="language-plaintext highlighter-rouge">0 * 1 * 1,3</code></p>
<p>Setelah jadwal, kita mengkonfigurasi proses build itu sendiri. Di project saya, build dilakukan oleh Ant. Kita bisa mendaftarkan target yang akan dieksekusi.</p>
<p><a href="/images/uploads/2007/10/build-method.png"><img src="/images/uploads/2007/10/build-method.png" alt="Build Method " /></a></p>
<p>Terakhir, kita mengkonfigurasi post build. Ini adalah kegiatan yang dilakukan setelah build sukses dijalankan. Pada tahap ini, saya ingin Hudson mempublikasikan *.jar atau *.war yang dihasilkan agar siap didonlod.</p>
<p><a href="/images/uploads/2007/10/post-build.png"><img src="/images/uploads/2007/10/post-build.png" alt="Post Build " /></a></p>
<p>Selain itu, saya juga bisa menyuruh Hudson untuk memproses hasil unit test JUnit. Hudson mengerti file XML yang dihasilkan JUnit dan TestNG.</p>
<p>Hudson juga bisa disuruh mengirim email apabila terjadi kegagalan build. Di sini biasanya saya isi dengan alamat mailing list developer.</p>
<p>Setelah selesai, klik Save.</p>
<p>Selanjutnya, kita tinggal melihat-lihat hasilnya.</p>
<p>Di halaman depan, kita bisa lihat rangkuman status tiap project.</p>
<p><a href="/images/uploads/2007/10/build-status.png"><img src="/images/uploads/2007/10/build-status.png" alt="Build Status " /></a></p>
<p>Klik salah satu project, dan lihat detailnya</p>
<p><a href="/images/uploads/2007/10/build-result.png"><img src="/images/uploads/2007/10/build-result.png" alt="Build Result " /></a></p>
<p>Di situ kita bisa lihat artifact yang sudah dipublish. Dari semua build yang sudah dilakukan, kita bisa lihat trendnya.</p>
<p><a href="/images/uploads/2007/10/build-time-trend.png"><img src="/images/uploads/2007/10/build-time-trend.png" alt="Trend of Build Time " /></a></p>
<p>Selain itu, kita juga bisa lihat hasil JUnit test.</p>
<p><a href="/images/uploads/2007/10/junit-result.png"><img src="/images/uploads/2007/10/junit-result.png" alt="JUnit Result " /></a></p>
<p>Kesimpulan akhir, berikut adalah perbandingan ketiga tools ini.</p>
<p>Tools Setup Tampilan Aksesoris Tag Otomatis</p>
<p>CruiseControl</p>
<p>Sulit, semua harus pakai XML</p>
<p>Kurang bagus, terlihat kuno</p>
<p>Lengkap</p>
<p>Bisa, menggunakan publisher</p>
<p>Luntbuild</p>
<p>Mudah, setup melalui web</p>
<p>Rumit dan kurang intuitif</p>
<p>Sangat sedikit</p>
<p>Bisa, dikonfigurasi melalui web</p>
<p>Hudson</p>
<p>Sangat Mudah</p>
<p>Bagus, intuitif, Web 2.0, AJAX</p>
<p>Eclipse plugin, Netbeans Plugin, Trac Plugin</p>
<p>Tidak bisa, harus manual</p>
<p>Demikianlah, semoga bermanfaat.</p>
Dump Restore PostgreSQL2007-09-25T22:01:48+07:00https://software.endy.muhardin.com/aplikasi/dump-restore-postgresql<p>Pada tahap implementasi, fitur dump-restore database sangat penting. Dengan fitur ini, kita bisa melakukan migrasi data di mesin development, melakukan troubleshoot, data cleansing, dan sebagainya dengan tenang. Begitu sudah selesai, kita dump struktur tabel berikut datanya dari mesin development, kemudian buat database baru di mesin production, lalu restore.</p>
<p>Agar tidak lupa, berikut saya tulis rangkaian langkah-langkahnya. Diasumsikan kita sudah memiliki database development dengan parameter sebagai berikut:</p>
<ul>
<li>Nama Database : <code class="language-plaintext highlighter-rouge">buku_tamu</code></li>
<li>Username : <code class="language-plaintext highlighter-rouge">belajar</code></li>
<li>Password : <code class="language-plaintext highlighter-rouge">java</code></li>
<li>File hasil dump : <code class="language-plaintext highlighter-rouge">buku_tamu-schema-20070925-2021.sql</code> dan <code class="language-plaintext highlighter-rouge">buku_tamu-data-20070925-2021.sql</code></li>
</ul>
<p>Untuk melakukan dump skema tabel, berikut adalah perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pg_dump --schema-only --no-owner -h localhost --username belajar buku_tamu | grep -v "^--" > buku_tamu-schema.sql && sed -i '/^SET/d' buku_tamu-schema.sql && sed -i '/^SELECT/d' buku_tamu-schema.sql && mv buku_tamu-schema.sql buku_tamu-schema-`date +%Y%m%d-%H%M`.sql
</code></pre></div></div>
<p>Penjelasannya sebagai berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">--pg_dump</code> : adalah aplikasi command line untuk melakukan import</li>
<li><code class="language-plaintext highlighter-rouge">--schema-only</code> : hanya membuat DDL statement, tanpa data</li>
<li><code class="language-plaintext highlighter-rouge">--no-owner</code> : tidak perlu mengatur kepemilikan tabel dan view</li>
<li><code class="language-plaintext highlighter-rouge">-h</code> : supaya koneksi dilakukan melalui TCP/IP, bukan Unix socket</li>
<li><code class="language-plaintext highlighter-rouge">grep -v "^--"</code> : menghilangkan baris-baris comment di file hasil backup</li>
<li><code class="language-plaintext highlighter-rouge">sed -i '/^SET/d'</code> : menghilangkan command-command <code class="language-plaintext highlighter-rouge">SET</code> yang kita tidak perlukan</li>
<li><code class="language-plaintext highlighter-rouge">sed -i '/^SELECT/d'</code> : menghilangkan command-command <code class="language-plaintext highlighter-rouge">SELECT</code> yang kita tidak perlukan</li>
</ul>
<p>Untuk melakukan dump data dalam database, berikut adalah perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pg_dump --column-inserts --data-only -h localhost -U belajar buku_tamu | grep -v "^--" > buku_tamu-data.sql && sed -i '/^SET/d' buku_tamu-data.sql && sed -i '/^SELECT/d' buku_tamu-data.sql && mv buku_tamu-data.sql buku_tamu-data-`date +%Y%m%d-%H%M`.sql
</code></pre></div></div>
<p>Penjelasannya sebagai berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">--column-inserts</code> : mencantumkan nama kolom dalam statement insert data</li>
<li><code class="language-plaintext highlighter-rouge">--data-only</code> : hanya membuat <code class="language-plaintext highlighter-rouge">INSERT</code> statement</li>
</ul>
<p>Selanjutnya, tiba saat melakukan restore. Parameternya sama dengan database development, kecuali nama databasenya adalah <code class="language-plaintext highlighter-rouge">buku_tamu_prod</code>. Bila database belum ada, buat dulu dengan user postgres.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo su - postgres
$ createdb buku_tamu_prod
CREATE DATABASE
$ exit
</code></pre></div></div>
<p>Baru setelah itu kita lakukan restore.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql -h localhost -d buku_tamu_prod -U belajar -f buku_tamu-schema-20070925-2021.sql.sql
psql -h localhost -d buku_tamu_prod -U belajar -f buku_tamu-data-20070925-2021.sql.sql
</code></pre></div></div>
<p>Penjelasan opsinya adalah sebagai berikut:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">-h</code> : supaya connect melalui TCP/IP, bukan lewat Unix socket</li>
<li><code class="language-plaintext highlighter-rouge">-d</code> : nama database yang akan direstore</li>
<li><code class="language-plaintext highlighter-rouge">-U</code> : username yang digunakan untuk koneksi</li>
<li><code class="language-plaintext highlighter-rouge">-f</code> : file dump</li>
</ul>
<p>Bila tidak ada pesan error, struktur tabel dan data seharusnya sudah masuk ke database baru.</p>
Lowongan Experd2007-09-25T15:36:49+07:00https://software.endy.muhardin.com/lain/lowongan-experd<p>Kemarin ada mbak-mbak yang kirim email ke saya, namanya mbak Hanna dari <a href="http://www.experd.org">Experd</a>, minta tolong dipostingkan lowongan. Entah kenapa, waktu saya cek websitenya, tulisannya Service Unavailable. Sedangkan www.experd.com bisa diakses. Entahlah, mungkin yang .org sedang bermasalah.</p>
<p>Begini katanya,</p>
<blockquote>
<p>Sekedar share informasi lowongan, bahwa saat ini kami ada klien, sebuah konsultan IT yang berpusat di New Jersey, sedang membutuhkan JAVA/J2EE programmer. Nantinya kandidat yang dihired ini akan ditugaskan untuk proyek salah satu top bank di Malaysia. Status pekerjaan yang ditawarkan permanent employee. (selengkapnya di bawah ini).</p>
</blockquote>
<p>Berikut detail lowongannya.</p>
<ul>
<li>
<p>Has 2-7 years experience in Java/J2EE Development</p>
</li>
<li>
<p>Experience in DB2/Oracle and Unix is an advantage</p>
</li>
<li>
<p>Preferably with Banking Project Experience</p>
</li>
<li>
<p>Can join in 2-6 weeks</p>
</li>
<li>
<p>Willing to work in Kuala Lumpur, Malaysia</p>
</li>
</ul>
<p>Mbak Hanna juga memberikan alamat kantornya, yaitu di:</p>
<p>Plaza 3 Pondok Indah Blok C2, Jl. T.B. Simatupang, Jakarta 12310</p>
<p>Telp. 62-21-75906448, Fax. 62-21-75906442</p>
<p>Email. hanna [at] experd [dot] org, http://www.experd.com</p>
<p>Jangan kirim lamaran ke saya, menurut mbak Hanna, kirimkan ke databank [at] experd [dot] org.</p>
<p><strong>Disclaimer: Saya tidak kenal sama mbak Hanna. Artikel ini juga bukan anjuran kepada pembaca untuk mengirim lamaran</strong></p>
<p>Saya juga sudah membaca bukunya Kevin Mitnick yang berjudul <a href="http://www.flazx.com/ebook1002.php">The Art of Deception</a>, yang berisi tentang <a href="http://en.wikipedia.org/wiki/Social_engineering_%28security%29">Social Engineering</a>. Jadi, saran saya bagi pembaca yang berminat, luangkan waktu beberapa menit untuk melakukan cross-check ke alamat dan nomer telepon yang ada di atas untuk memastikan kesahihan info lowongan ini sebelum mengirim resume Anda.</p>
<p>Sekedar smoke-test, saya sudah lookup ke <a href="http://www.who.is/">layanan whois</a>, dan hasilnya experd.com dan experd.org diregistrasi oleh orang yang sama, dan alamatnya sama dengan alamat kantornya di atas.</p>
<p>Untuk mbak Hanna, mohon maaf ya … bukannya tidak percaya. Tapi baca sendiri deh <a href="http://www.flazx.com/ebook4179.php">The Art of Deception</a>, nanti Anda akan mengerti kenapa saya <em>terkesan paranoid</em>. Anyway … terima kasih atas info lowongannya. Mudah-mudahan mendapatkan kandidat yang sesuai.</p>
<p>Kalau ada orang Experd yang membaca artikel ini, mohon konfirmasi atau sanggahannya.</p>
Menggunakan PostgreSQL2007-09-24T18:35:16+07:00https://software.endy.muhardin.com/java/menggunakan-postgresql<p>Saya baru saja memporting project yang sedang saya kerjakan, dari menggunakan <a href="http://www.mysql.org">ikan lumba-lumba</a> menjadi <a href="http://www.postgresql.org">gajah</a>. Sebetulnya saya pernah menggunakan database gajah ini pada tahun 2005, tapi setelah itu jarang digunakan sehingga butuh waktu agak lama untuk melakukan porting ini.</p>
<p>Agar lain kali tidak lupa lagi, baiklah saya tulis di sini saja. Mudah-mudahan bermanfaat juga untuk pembaca :D</p>
<h3 id="instalasi">Instalasi</h3>
<p>Saya menggunakan Ubuntu, jadi instalasi tidak sulit. Cukup lakukan:
<code class="language-plaintext highlighter-rouge">sudo apt-get install postgresql</code>
Beres .. :D</p>
<h3 id="konfigurasi-akses-jaringan">Konfigurasi Akses Jaringan</h3>
<p>Selanjutnya, konfigurasi Postgre agar meminta password setiap ada koneksi masuk, termasuk dari localhost. Ini dilakukan agar konfigurasi JDBCnya tidak terlalu berbeda dengan konfigurasi sebelumnya yang menggunakan MySQL.</p>
<p>Ganti otentikasi dalam <code class="language-plaintext highlighter-rouge">pg_hba.conf</code> menjadi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local all postgres peer
local all all password
host all all 127.0.0.1/32 password
host all all ::1/128 password
</code></pre></div></div>
<p>Konfigurasi di atas artinya, koneksi ke semua database, dari local maupun remote, mintalah password. File <code class="language-plaintext highlighter-rouge">pg_hba.conf</code> ini lokasinya tergantung distro yang digunakan. Di tempat saya, ada di folder <code class="language-plaintext highlighter-rouge">/etc/postgresql/9.3/main</code>. Jangan lupa merestart PostgreSQL setelah memodifikasi file ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /etc/init.d/postgresql restart
</code></pre></div></div>
<h3 id="menambahkan-user">Menambahkan User</h3>
<p>Untuk menambahkan user, kita perlu <em>menyamar</em> sebagai user <code class="language-plaintext highlighter-rouge">postgres</code>. Gunakan <code class="language-plaintext highlighter-rouge">su</code>.
<code class="language-plaintext highlighter-rouge">sudo su - postgres</code></p>
<p>Selanjutnya, jalankan perintah <code class="language-plaintext highlighter-rouge">createuser</code> dengan argumen <code class="language-plaintext highlighter-rouge">--interactive</code> agar diberikan form isian dan argumen <code class="language-plaintext highlighter-rouge">-P</code> agar kita bisa mengisi password.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createuser --interactive -P
Enter name of role to add: belajar
Enter password for new role:
Enter it again:
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) y
Shall the new role be allowed to create more new roles? (y/n) n
</code></pre></div></div>
<p>Perintah di atas menambahkan user dengan username <code class="language-plaintext highlighter-rouge">belajar</code> yang memiliki ijin untuk membuat database baru.</p>
<p>Selanjutnya, keluar dari user postgres dengan menggunakan perintah <code class="language-plaintext highlighter-rouge">exit</code>.</p>
<h3 id="membuat-database">Membuat Database</h3>
<p>Agar bisa menyimpan data, kita harus punya database. Mari kita buat database yang namanya <code class="language-plaintext highlighter-rouge">buku_tamu</code>. Gunakan perintah <code class="language-plaintext highlighter-rouge">createdb</code> dengan argumen U untuk menyebutkan username.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createdb -U belajar buku_tamu
Password:
CREATE DATABASE
</code></pre></div></div>
<p>Setelah selesai, coba koneksi ke database tersebut.</p>
<p><code class="language-plaintext highlighter-rouge">psql -U belajar -d buku_tamu</code></p>
<h3 id="konfigurasi-jdbc">Konfigurasi JDBC</h3>
<p>Kalau sudah OK, berikut adalah konfigurasi JDBC untuk mengakses database tersebut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jdbc.driver = org.postgresql.Driver
jdbc.url = jdbc:postgresql://localhost/buku_tamu
jdbc.username = belajar
jdbc.password = java
</code></pre></div></div>
<p>Saya menggunakan Hibernate, sehingga butuh satu parameter lagi, yaitu dialek SQL yang digunakan.</p>
<p><code class="language-plaintext highlighter-rouge">hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect</code></p>
<p>Dengan menggunakan Hibernate, maka porting database hanyalah perkara mengganti lima baris konfigurasi (bukan source code), dari seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost/buku_tamu
jdbc.username = belajar
jdbc.password = java
hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
</code></pre></div></div>
<p>Menjadi seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jdbc.driver = org.postgresql.Driver
jdbc.url = jdbc:postgresql://localhost/buku_tamu
jdbc.username = belajar
jdbc.password = java
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
</code></pre></div></div>
<p>Demikian setup PostgreSQL agar bisa diakses dari Java.</p>
Membuat validasi dalam aplikasi2007-09-12T23:44:55+07:00https://software.endy.muhardin.com/java/membuat-validasi-dalam-aplikasi<p>Pertanyaan berikut muncul di <a href="http://tech.groups.yahoo.com/group/netbeans-indonesia/">milis netbeans-indonesia</a>, “Di mana sebaiknya kita menulis aturan validasi? Di database, di business layer, atau di presentation layer?”</p>
<p>Saya pikir, pasti banyak juga yang memiliki kebimbangan serupa. Oleh karena itu, jawaban saya tulis di artikel ini.</p>
<p>Menurut saya, validasi itu idealnya di semua layer, mulai dari database, business logic, dan presentation layer (UI).</p>
<p>Kenapa di database harus ada validasi (dengan menggunakan database constraint) adalah karena data umurnya akan lebih panjang dari aplikasi. Front end bisa ditulis ulang dengan apapun teknologi/framework yang sedang populer, sedangkan data sekali sudah disimpan, biasanya akan terus ada dalam waktu yang lama.</p>
<p>Kalau kita tidak pakai constraint di database, begitu aplikasi kita usang dan diganti (misal tadinya desktop jadi web) maka databasenya jadi ‘telanjang’ tanpa validasi, sehingga rawan kemasukan data kotor. Dan siapa yang bisa memastikan database kita tidak diakses aplikasi lain? Mungkin sekarang tidak … tapi tahun depan ketika ada kebutuhan baru, bisa saja ada orang lain yang mengembangkan aplikasi di atas database kita tersebut.</p>
<p>Di sisi lain, validasi di presentation layer juga penting, supaya round-trip nya tidak terlalu panjang.
Katakanlah kita punya layer seperti ini:
presentation –> business –> data access</p>
<p>Kalau cuma ada validasi database, maka kita perlu meng-catch SQLException untuk kemudian diteruskan ke depan, entah itu as-is SQLException ataupun dienkapsulasi menjadi DataAccessException dan kemudian dibungkus lagi menjadi BusinessLayerException.
Apalagi kalau aplikasinya ada di mesin berbeda. Ongkos perjalanan Exceptionnya jadi mahal.
Itu makanya penting validasi di presentation layer.</p>
<p>So … idealnya validasi ada di semua layer.</p>
<p>Tapi ini tidak berarti saya menganjurkan untuk menulis kode validasi di semua layer lho. Kode program itu idealnya tidak boleh terduplikasi. Logika validasi hanya ditulis sekali. Kalau aturan yang sama ditulis berkali-kali nanti pasti akan datang masanya kita (atau orang lain yang mewarisi kode program kita) mengganti aturan di satu tempat, dan lupa mengganti di tempat lain. Jadi, untuk setiap aturan, harus satu kali saja menulisnya.</p>
<p>Banyak cara agar codingnya cukup satu kali saja, misalnya mendefinisikan constraint dengan Hibernate, dan biarkan dia yang menggenerate DDL. Biasanya constraint unique, required, dan semacamnya akan langsung dibuatkan database constraintnya.</p>
<p>Kemudian di presentation layer, buat aturan validasi yang dinamis, yang bisa membaca hibernate annotation dan kemudian menggenerate rutin validasi baik itu JavaScript (kalau aplikasinya web), atau pemanggilan method Java (kalau itu Swing).</p>
<p>Demikian pendapat saya tentang validasi. Bagaimana menurut Anda?</p>
Menggunakan HermesJMS2007-09-06T18:37:59+07:00https://software.endy.muhardin.com/java/menggunakan-hermesjms<p>Kalau kita ingin membuat aplikasi menggunakan database, tentunya hal pertama yang kita lakukan adalah menginstal server database. Selanjutnya, langkah kedua adalah menginstal aplikasi untuk mengelola database. Paling tidak, kita harus bisa membuat tabel, melihat isi tabel, mengisi ataupun menghapus data ke dalam tabel.</p>
<p>Demikian juga kalau kita membangun aplikasi menggunakan JMS (Java Messaging Service). Sebelum membuat Hello JMS, terlebih dulu kita harus menginstal JMS server dan aplikasi yang dapat melihat isi JMS Server tersebut.</p>
<p>Pada artikel kali ini, kita akan menginstal JMS Server <a href="http://activemq.apache.org/">ActiveMQ</a> dan aplikasi pengelolaan JMS yang bernama <a href="http://www.hermesjms.com/confluence/display/HJMS/Home">HermesJMS</a>.</p>
<p>ActiveMQ dan HermesJMS dapat diunduh di websitenya masing-masing.</p>
<p>Setelah diunduh, terlebih dulu kita instal ActiveMQ. Caranya sangat gampang, cukup diekstrak saja. Saya meletakkannya di folder <code class="language-plaintext highlighter-rouge">/opt</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>cd /opt
sudo tar xvzf apache-activemq-4.1.1.tar.gz
</code>
</code></pre></div></div>
<p>Setelah diekstrak, kita bisa langsung jalankan dari folder instalasinya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>cd /opt/apache-activemq-4.1.1
bin/activemq</code>
</code></pre></div></div>
<p>Kalau sukses, akan muncul log message seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>INFO TransportServerThreadSupport - Listening for connections at: tcp://sweetdreams:61616
INFO TransportConnector - Connector openwire Started
INFO TransportServerThreadSupport - Listening for connections at: ssl://sweetdreams:61617
INFO TransportConnector - Connector ssl Started
INFO TransportServerThreadSupport - Listening for connections at: stomp://sweetdreams:61613
INFO TransportConnector - Connector stomp Started
INFO NetworkConnector - Network Connector default-nc Started
INFO BrokerService - ActiveMQ JMS Message Broker (localhost, ID:sweetdreams-32991-1188983887811-1:0) started
</code>
</code></pre></div></div>
<p>Server kita sudah siap diakses di port 61616.</p>
<p>Sekarang kita akan menginstal HermesJMS. Inipun instalasinya tidak sulit, cukup jalankan saja installernya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>java -jar hermes-installer-1.12.jar</code>
</code></pre></div></div>
<p>Nantinya akan muncul layar instalasi. Klik saja Next … Next … seperti biasa sampai selesai.</p>
<p>Setelah Hermes berhasil diinstal, jalankan Hermes. Berikut adalah tampilan awalnya.
<a href="/images/uploads/2007/09/hello-hermes.png"><img src="/images/uploads/2007/09/hello-hermes.png" alt="Hello Hermes " /></a></p>
<p>Selanjutnya, kita akan menambahkan koneksi ke server ActiveMQ kita.
Buka menu Options - Preferences, kemudian pilih tab Classpath di sisi bawah panel.
<a href="/images/uploads/2007/09/no-classpath.png"><img src="/images/uploads/2007/09/no-classpath.png" alt="Belum ada classpath group " /></a></p>
<p>Selanjutnya, klik kanan di panel kosong tersebut
dan masukkan classpath group baru bernama ActiveMQ.
<a href="/images/uploads/2007/09/add-classpath.png"><img src="/images/uploads/2007/09/add-classpath.png" alt="Tambah Classpath Group " /></a></p>
<p>Nanti akan muncul entri ActiveMQ dengan tab dibawahnya yang bertulisan Library.
Klik kanan <strong>tepat pada tulisan Library</strong> dan pilih Add Jar.
Kita harus menambahkan beberapa jar berikut:</p>
<ul>
<li>
<p>apache-activemq-4.1.1.jar</p>
</li>
<li>
<p>backport-util-concurrent-2.1.jar</p>
</li>
<li>
<p>geronimo-j2ee-management_1.0_spec-1.0.jar</p>
</li>
</ul>
<p>Semua file tersebut dapat ditemukan di lokasi instalasi ActiveMQ.</p>
<p>Setelah konfigurasi dilakukan, layar tampilannya akan tampak seperti ini:
<a href="/images/uploads/2007/09/classpath-activemq.png"><img src="/images/uploads/2007/09/classpath-activemq.png" alt="Classpath ActiveMQ " /></a></p>
<p>Tutup layar Preference, dan buka lagi.
Entah kenapa kalau tidak ditutup dulu, pilihan ActiveMQ tidak akan keluar.</p>
<p>Di layar Preference tab Session, ketikkan nama Session yang baru, yaitu ActiveMQ.
Klik Apply, nantinya Hermes akan menanyakan apakah ini session baru atau bukan.
Jawab saja, “Ya, ini adalah session baru”.</p>
<p>Selanjutnya, ganti pilihan Loader menjadi ActiveMQ, dan pilih ActiveMQ Connection Factory pada pilihan Class.
Hasilnya akan nampak seperti ini:
<a href="/images/uploads/2007/09/session-activemq.png"><img src="/images/uploads/2007/09/session-activemq.png" alt="Session Baru ActiveMQ " /></a></p>
<p>Setelah selesai, session ActiveMQ akan muncul di panel sebelah kiri.
<a href="/images/uploads/2007/09/session-activemq-configured.png"><img src="/images/uploads/2007/09/session-activemq-configured.png" alt="Konfigurasi Session ActiveMQ " /></a></p>
<p>Kita bisa membuat queue baru di session tersebut. Gunakan tombol Create New Queue.
<a href="/images/uploads/2007/09/queue-new.png"><img src="/images/uploads/2007/09/queue-new.png" alt="Membuat Queue " /></a></p>
<p>Pembuatan queue pada layar ini bersifat sementara. Artinya, bila HermesJMS dimatikan, Queuenya akan hilang. Untuk membuat Queue yang permanen, kita dapat mendefinisikannya pada layar konfigurasi session.</p>
<p>Queue yang sudah dibuat dapat dilihat di panel sebelah kiri.
<a href="/images/uploads/2007/09/queue-browse.png"><img src="/images/uploads/2007/09/queue-browse.png" alt="Queue Browser " /></a></p>
<p>Pada queue yang sudah terbentuk, kita bisa mengirim Text Message.
Gunakan menu Messages - Send TextMessages. Kita harus memilih satu text file untuk dikirim.</p>
<p>Setelah pengiriman selesai dilakukan, refresh tampilan sehingga message baru tersebut muncul.
<a href="/images/uploads/2007/09/queue-browse-message.png"><img src="/images/uploads/2007/09/queue-browse-message.png" alt="Browse Message " /></a></p>
<p>Kita dapat mengambil message tersebut dengan menggunakan kode program Java sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>package tutorial.jms;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class HelloQueueReceiver {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
ActiveMQConnection.DEFAULT_BROKER_URL);
System.out.println("Connecting to: "+ActiveMQConnection.DEFAULT_BROKER_URL);
System.out.println("With username : "+ActiveMQConnection.DEFAULT_USER);
System.out.println("and password : "+ActiveMQConnection.DEFAULT_PASSWORD);
Connection conn = factory.createConnection();
System.out.println("Connected");
Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
System.out.println("Session created");
Queue destination = sess.createQueue("coba");
System.out.println("Queue created");
MessageConsumer consumer = sess.createConsumer(destination);
System.out.println("Consumer created");
System.out.println("Fetching message");
conn.start();
int counter = 1;
while(true) {
Message msg = consumer.receive(1000);
if (msg == null) break;
System.out.println("Retrieving message #"+counter++);
System.out.println("Message fetched");
if (msg instanceof TextMessage) {
TextMessage txtmsg = (TextMessage) msg;
System.out.println("Message ID: "+txtmsg.getJMSMessageID());
System.out.println("Message Content: "+txtmsg.getText());
}
}
System.out.println("Closing connections");
consumer.close();
sess.close();
conn.close();
System.out.println("All closed");
}
}
</code>
</code></pre></div></div>
<p>Setelah kode program di atas dijalankan, refresh queue browser di Hermes. Harusnya pengambilan message tersebut akan menghapus message di server.</p>
<p>Kita juga bisa mengirim message menggunakan kode program berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>package tutorial.jms;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class HelloQueueSender {
public static void main(String[] args) throws JMSException {
ConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
ActiveMQConnection.DEFAULT_BROKER_URL);
System.out.println("Connecting to: "+ActiveMQConnection.DEFAULT_BROKER_URL);
System.out.println("With username : "+ActiveMQConnection.DEFAULT_USER);
System.out.println("and password : "+ActiveMQConnection.DEFAULT_PASSWORD);
Connection conn = factory.createConnection();
System.out.println("Connected");
Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
System.out.println("Session created");
Queue destination = sess.createQueue("coba");
System.out.println("Queue created");
MessageProducer prod = sess.createProducer(destination);
TextMessage msg = sess.createTextMessage();
msg.setText("Hello JMS");
prod.send(msg);
System.out.println("Message sent");
prod.close();
sess.close();
conn.close();
}
}
</code>
</code></pre></div></div>
<p>Setelah kode program di atas dijalankan, kita dapat merefresh queue browser di Hermes untuk memastikan bahwa kode program kita berhasil mengirim message.</p>
<p>Demikianlah, dengan menggunakan HermesJMS, kita dapat memeriksa secara visual apakah kode program kita dapat mengakses JMS Server dengan benar.</p>
Network Address Translation2007-08-30T00:50:36+07:00https://software.endy.muhardin.com/linux/network-address-translation<p>Pada artikel kali ini, kita akan membahas penggunaan NAT untuk memungkinkan aplikasi kita yang jalan di laptop bisa diakses orang dari internet. NAT adalah singkatan dari Network Address Translation.</p>
<p>Gunanya supaya kita bisa mempublish aplikasi yang berjalan komputer di jaringan internal. Misalnya kita menjalankan aplikasi web di port 8080 di laptop dengan IP private : 192.168.0.10. Kita ingin aplikasi ini bisa diakses dari orang di internet. Laptop kita terhubung ke router yang memiliki IP public 202.159.11.11.</p>
<p>Normalnya, orang di internet tidak bisa langsung mengakses aplikasi kita melalui dengan alamat http://192.168.0.10:8080 dari internet. Karena alamat IP 192.168.0.10 adalah alamat internal, yang tidak dikenali di luar.</p>
<p>Untuk itu, kita harus mengkonfigurasi gateway internet kita agar bisa meneruskan request dari internet ke laptop kita. Ini bisa dilakukan dengan memasang konfigurasi NAT di gateway internet kita. Berikut cara kerjanya.</p>
<p>Request http dilakukan dari komputer asal ke komputer tujuan, dengan menuliskan alamat pengirim dan alamat penerima.
Mirip dengan kalau kita kirim surat. Pesannya ditulis di kertas, masukkan amplop, tulis alamat penerima dan alamat pengirim.</p>
<p>Contoh paketnya kira-kira seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Dari : 202.159.22.22
Untuk : 202.159.11.11
Pesan : halo
</code></pre></div></div>
<p>Pesan di atas akan bisa sampai ke gateway, karena <code class="language-plaintext highlighter-rouge">202.159.11.11</code> adalah IP public yang dikenal semua orang di internet.</p>
<p>Bila gateway ingin meneruskan pesan ini ke komputer internal, maka dia akan membungkus paket tersebut dengan amplop baru, alamat tujuannya diganti dengan IP internal, agar bisa mencapai laptop yang alamat IPnya internal.
Proses ini dinamakan DNAT (Destination NAT).</p>
<p>Bila alamat tujuan tidak diganti, maka paket tidak akan sampai ke komputer internal.</p>
<p>Di Linux, ini dilakukan dengan perintah iptables.
Perintahnya adalah sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -t nat -A PREROUTING -d 202.159.11.11 -p tcp --dport 8080 -j DNAT --to-destination 192.168.0.10:8080
</code></pre></div></div>
<p>Setelah di<code class="language-plaintext highlighter-rouge">DNAT</code>, paketnya menjadi seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Dari : 202.159.22.22
Untuk : 192.168.0.10
Pesan : halo
</code></pre></div></div>
<p>Setelah itu, amplop lewat proses routing di gateway. Router akan bisa menyampaikan paket tersebut ke laptop, karena dia kenal <code class="language-plaintext highlighter-rouge">192.168.0.10</code> itu adalah laptop.</p>
<p>Akhirnya paket sampai di tujuan.</p>
<p>Dengan menggunakan DNAT ini, kita bisa mempublikasikan laptop kita di jaringan internal agar bisa diakses dari luar.</p>
<p>Bagaimana kalau laptop kita ingin mengirim balasan data?</p>
<p>Prosesnya kira-kira sama. Dia masukkan ke amplop, menaruh alamat pengirim menjadi alamat tujuan, dan menaruh alamat laptop menjadi alamat pengirim. Kemudian data tersebut dikirim ke router.</p>
<p>Paketnya kira-kira seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Dari : 192.168.0.10
Untuk : 202.159.22.22
Pesan : halo juga
</code></pre></div></div>
<p>Laptop kita akan mengalami kebingungan untuk mengirim paket tersebut. Karena dia tidak kenal <code class="language-plaintext highlighter-rouge">202.159.22.22</code>. Dia hanya kenal teman-temannya sesama anggota jaringan <code class="language-plaintext highlighter-rouge">192.168.0.0</code>. Paket balasan ini tidak akan berhasil terkirim.</p>
<p>Untuk itu, gateway perlu melakukan satu perubahan lagi. Sebelum dia mengirim paket tadi ke laptop kita, dia perlu mengganti alamat pengirim menjadi alamat internalnya sendiri (biasanya nomor terkecil, yaitu <code class="language-plaintext highlighter-rouge">192.168.0.1</code>). Jadinya kira-kira seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Dari : 192.168.0.1
Untuk : 192.168.0.10
Pesan : halo
</code></pre></div></div>
<p>Proses ini dinamakan SNAT (Source NAT). Perintahnya adalah sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -t nat -A POSTROUTING -p tcp --dport 8080 -j SNAT --to-source 192.168.0.1
</code></pre></div></div>
<p>Setelah di<code class="language-plaintext highlighter-rouge">SNAT</code> pesan tersebut bisa dibalas oleh aplikasi di laptop kita seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Dari : 192.168.0.10
Untuk : 192.168.0.1
Pesan : halo juga
</code></pre></div></div>
<p>Pesan ini akan diterima oleh gateway, dan kemudian akan diteruskan ke pengirim aslinya yaitu <code class="language-plaintext highlighter-rouge">202.159.22.22</code></p>
<p>Dengan menggunakan SNAT, kita juga bisa mengimplementasikan Internet Connection Sharing, yaitu satu koneksi internet dibagi beramai-ramai.</p>
<p>Ada satu kasus khusus untuk SNAT, namanya Masquerade.
Ini digunakan apabila IP public yang digunakan berubah-ubah.
Misalnya kalau kita pakai dialup connection.</p>
<p>Kalau kita nekat pakai SNAT, nanti akan repot, karena harus update rule setiap dial ke internet dan mendapatkan IP public yang berbeda.</p>
<p>Solusinya, kita gunakan masquerade.
Berikut perintahnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j MASQUERADE
</code></pre></div></div>
<p>Dengan menggunakan masquerade, kita tidak perlu menyebutkan <code class="language-plaintext highlighter-rouge">--to-source</code> karena alamat IP asal (IP publik gateway kita) berubah-ubah.</p>
<p>Perhatian: Jangan lupa untuk mengaktifkan IP Forwarding di gateway dengan perintah</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat 1 > /proc/sys/net/ipv4/ip_forward
</code></pre></div></div>
<p>Rangkaian perintah ini akan hilang pada saat reboot.
Jadi harus ada usaha tambahan agar konfigurasi ini jadi permanen.
Caranya, tergantung masing-masing distro. Biasanya, kita simpan dulu ke file menggunakan perintah <code class="language-plaintext highlighter-rouge">iptables-save</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables-save > /etc/iptables/rules.v4
</code></pre></div></div>
<p>Kemudian file tersebut kita load menggunakan perintah <code class="language-plaintext highlighter-rouge">iptables-restore</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables-restore < /etc/iptables/rules.v4
</code></pre></div></div>
<p>Agar berjalan setiap kali booting, panggil perintah <code class="language-plaintext highlighter-rouge">iptables-restore</code> dari script <code class="language-plaintext highlighter-rouge">rc.local</code>. Lokasi script ini berbeda antar distro, untuk keluarga Debian terletak di folder <code class="language-plaintext highlighter-rouge">/etc</code>.</p>
<p>Sayangnya saat ini iptables hanya ada di Linux, dan nampaknya tidak akan ada versi Windowsnya. Karena iptables sangat <em>tightly-coupled</em> dengan kernel linux.</p>
<p>Untuk Windows, kita dapat gunakan fitur Internet Connection Sharing apabila ada. Beberapa versi Windows (misalnya XP Home), tidak punya fitur ini. Jadi solusinya adalah dengan menggunakan aplikasi tambahan seperti misalnya <a href="http://www.wingate.com/product-wingate.php">WinGate</a> atau <a href="http://www.kerio.com/kwf_home.html">WinRoute</a>.</p>
Konfigurasi mod_jk2007-08-15T22:52:26+07:00https://software.endy.muhardin.com/aplikasi/konfigurasi-mod_jk<p>Ada kalanya, kita ingin mempublikasikan aplikasi Java kita melalui Apache HTTPD, webserver paling populer dan paling banyak digunakan di dunia.</p>
<p>Beberapa alasan untuk melakukan ini antara lain:</p>
<ul>
<li>
<p>Kita ingin menjalankan Tomcat dalam cluster, diakses melalui satu pintu gerbang, yaitu Apache HTTPD</p>
</li>
<li>
<p>Kita tidak ingin membuka port 8080 ke seluruh dunia, cukup buka port 80 dan 443 saja. Sedangkan di port 80 sudah ada aplikasi lain (PHP, Rails, whatever) yang dihosting di atas Apache HTTPD</p>
</li>
<li>
<p>Kita ingin melayani static page dengan Apache HTTPD, dan dynamic page dengan Tomcat</p>
</li>
<li>
<p>Dan masih banyak alasan lain.</p>
</li>
</ul>
<p>Konfigurasi ini saya lakukan di Ubuntu Feisty 7.04. Langkah-langkahnya relatif mirip untuk distro lain, yang kalau disimpulkan terdiri dari:</p>
<ol>
<li>
<p>Install Apache</p>
</li>
<li>
<p>Install Tomcat</p>
</li>
<li>
<p>Deploy Aplikasi Java di Tomcat</p>
</li>
<li>
<p>Install mod_jk</p>
</li>
<li>
<p>Konfigurasi worker</p>
</li>
<li>
<p>Konfigurasi virtual host</p>
</li>
</ol>
<p>Baiklah, mari kita mulai saja.</p>
<h3 id="instalasi-apache">Instalasi Apache</h3>
<p>Saya gunakan Apache 2.2 yang disediakan oleh repository Ubuntu. Instalasi sangat mudah:
<code class="language-plaintext highlighter-rouge">sudo apt-get install apache2</code></p>
<p>Setelah instalasi selesai pastikan webserver sudah berjalan dengan baik dengan cara mengakses <code class="language-plaintext highlighter-rouge">http://localhost</code></p>
<h3 id="instalasi-tomcat">Instalasi Tomcat</h3>
<p>Kita bisa menginstal Tomcat melalui apt-get, tetapi saya lebih suka menggunakan cara manual.
Unduh dari <a href="http://tomcat.apache.org">situsnya</a>, kemudian ekstrak di folder /opt.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /opt
sudo tar xvzf apache-tomcat-xx.tar.gz
</code></pre></div></div>
<p>Setelah itu, jalankan Tomcat.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /opt/tomcat/bin/startup.sh
</code></pre></div></div>
<p>Jangan lupa mengatur environment variable <code class="language-plaintext highlighter-rouge">JAVA_HOME</code> ke tempat instalasi Java SDK Anda sebelum menjalankan Tomcat.</p>
<p>Periksa keberhasilan instalasi Tomcat dengan cara browse ke <code class="language-plaintext highlighter-rouge">http://localhost:8080</code></p>
<h3 id="deploy-aplikasi-java">Deploy Aplikasi Java</h3>
<p>Pada saat ini, kita sudah bisa mendeploy aplikasi Java ke Tomcat, tepatnya di folder <code class="language-plaintext highlighter-rouge">[TOMCAT_HOME]/webapps</code>.
Untuk mudahnya, kita tidak akan instal aplikasi, tapi menggunakan tomcat-docs yang sudah disediakan Tomcat.</p>
<h3 id="install-mod_jk">Install mod_jk</h3>
<p>Instalasi mod_jk di Ubuntu sangat mudah, cukup lakukan <code class="language-plaintext highlighter-rouge">sudo apt-get install libapache2-mod-jk</code></p>
<p><code class="language-plaintext highlighter-rouge">mod_jk</code> akan diinstal dan langsung diaktifkan.</p>
<p>Sebelum <code class="language-plaintext highlighter-rouge">mod_jk</code> bisa bekerja dengan benar, kita harus melakukan sedikit konfigurasi. Saya membuat file konfigurasi di <code class="language-plaintext highlighter-rouge">/etc/apache2/mods-available/jk.conf</code>.
Letak file konfigurasi tidak penting, yang penting adalah file tersebut dibaca oleh Apache. Silahkan baca-baca manual konfigurasi untuk distro Anda</p>
<p>Berikut adalah isi file <code class="language-plaintext highlighter-rouge">/etc/apache2/mods-available/jk.conf</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JkWorkersFile /etc/apache2/mods-available/workers.properties
JkShmFile /var/log/apache2/mod_jk.shm
JkLogFile /var/log/apache2/mod_jk.log
JkLogLevel info
JkLogStampFormat "[%a %b %d %H:%M:$S %Y] "
</code></pre></div></div>
<p>Seperti kita lihat di baris pertama, ada referensi ke file <code class="language-plaintext highlighter-rouge">workers.properties</code>. Kita akan bahas file ini di bagian selanjutnya.</p>
<p>Sisa file ini mendefinisikan konfigurasi log file.</p>
<h3 id="konfigurasi-workers">Konfigurasi Workers</h3>
<p>File <code class="language-plaintext highlighter-rouge">workers.properties</code> mendefinisikan semua worker yang tersedia. Worker adalah Tomcat yang akan melayani request ke aplikasi Java. Untuk mengaktifkan clustering, kita bisa mengkonfigurasi banyak worker sekaligus.
Saat ini, sebagai contoh kita akan mengkonfigurasi satu saja dulu.</p>
<p>Berikut adalah isi file <code class="language-plaintext highlighter-rouge">workers.properties</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>worker.list=worker1
worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=8009
</code></pre></div></div>
<h3 id="konfigurasi-virtual-host">Konfigurasi Virtual Host</h3>
<p>Terakhir, kita akan mengkonfigurasi virtual host agar Apache HTTPD dapat melakukan forwarding dengan benar. Pada contoh ini, kita akan mengarahkan request ke <code class="language-plaintext highlighter-rouge">http://localhost/tomcat-docs</code> agar dilayani oleh Tomcat. Berikut konfigurasi Virtual Hostnya.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><VirtualHost></span>
JkMount /tomcat-docs/* worker1
<span class="nt"></VirtualHost></span>
</code></pre></div></div>
<p>Jika ingin mempublikasikan banyak aplikasi Java sekaligus, duplikasi saja baris <code class="language-plaintext highlighter-rouge">JkMount</code> di atas agar mengarah ke masing-masing aplikasi kita yang sudah dideploy di dalam folder <code class="language-plaintext highlighter-rouge">[TOMCAT_HOME]/webapps</code>.</p>
<p>Konfigurasi virtual host di atas bisa digabungkan dengan konfigurasi lain seperti <a href="http://endy.artivisi.com/blog/aplikasi/instalasi-subversion/">repository Subversion</a>, Ruby on Rails, dan sebagainya. Cukup selipkan baris <code class="language-plaintext highlighter-rouge">JkMount</code> di dalam blok <code class="language-plaintext highlighter-rouge">VirtualHost</code>.</p>
<p>Letak konfigurasi Virtual Host berbeda antar distro. Di keluarga Debian, konfigurasinya ada di folder <code class="language-plaintext highlighter-rouge">/etc/apache2/sites-available</code>.</p>
<p>Setelah selesai, coba restart Apache HTTPD. Kalau semua berjalan lancar, dokumentasi Tomcat yang tadinya hanya bisa diakses di <code class="language-plaintext highlighter-rouge">http://localhost:8080/tomcat-docs/</code> sekarang juga bisa diakses di <code class="language-plaintext highlighter-rouge">http://localhost/tomcat-docs/</code>. Perhatikan perbedaan portnya, yang belakangan dilayani oleh Apache HTTPD.</p>
<p>Selamat mencoba :D</p>
Continuous Integration dengan Luntbuild2007-08-13T20:44:57+07:00https://software.endy.muhardin.com/java/luntbuild<p>Setelah pada artikel sebelumnya kita menggunakan CruiseControl –sempat dikomentari sebagai XML Sit Ups–
kali ini kita akan menggunakan <a href="http://luntbuild.javaforge.com/proj/summary.do?proj_id=70">Luntbuild</a>,
Continuous Integration Tools yang konfigurasinya tidak menggunakan XML sama sekali.</p>
<p>Sama seperti CruiseControl, untuk menjalankan Continuous Integration, kita memerlukan:</p>
<ul>
<li>
<p>Repository Subversion yang berisi source code, lengkap dengan folder trunk dan release</p>
</li>
<li>
<p>Code Review, Unit Test, Integration Test, Coverage Test yang lengkap</p>
</li>
<li>
<p>File build.xml yang memiliki target untuk menjalankan semua test</p>
</li>
</ul>
<p>Struktur folder repository juga masih sama seperti artikel sebelumnya. Kita tidak membuat baru,
melainkan langsung menggunakan repository pada artikel sebelumnya.</p>
<p>Untuk menjalankan Luntbuild, kita memerlukan:</p>
<ul>
<li>
<p>Luntbuild installer</p>
</li>
<li>
<p>Apache Ant 1.7.0</p>
</li>
<li>
<p>Apache Tomcat 5.5</p>
</li>
<li>
<p>Java SDK 6.0</p>
</li>
</ul>
<p>Installer Luntbuild dapat diunduh <a href="http://luntbuild.javaforge.com/proj/doc.do?doc_id=35767">di sini</a>.</p>
<p>Cara instalasinya tidak sulit, begitu kita dapatkan file instalasinya, buka command prompt, dan masuk ke folder tempat installer tersebut berada.
Kemudian jalankan installernya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd Downloads
$ java -jar luntbuild-1.4.2-installer.jar
</code></pre></div></div>
<p>Tampilan instalasi Luntbuild akan segera tampil. Klik saja Next untuk melewati Release Note dan License Agreement,
sampai pada pertanyaan lokasi instalasi.</p>
<p><a href="/images/uploads/2007/08/luntbuild-install-dir.png"><img src="/images/uploads/2007/08/luntbuild-install-dir.png" alt="Lokasi Instalasi Luntbuild " /></a></p>
<p>Di komputer saya, saya pilih lokasi <code class="language-plaintext highlighter-rouge">/opt/luntbuild-1.4.2</code> sebagai lokasi instalasi.
Klik Next, dan lanjutkan ke konfigurasi database.</p>
<p><a href="/images/uploads/2007/08/luntbuild-install-db.png"><img src="/images/uploads/2007/08/luntbuild-install-db.png" alt="Instalasi Database " /></a></p>
<p>Di sini saya tidak mengubah setting apa-apa. Langsung saja lanjutkan sampai menemui pertanyaan username dan password administrator.</p>
<p><a href="/images/uploads/2007/08/luntbuild-install-password.png"><img src="/images/uploads/2007/08/luntbuild-install-password.png" alt="Setup Administrator Password " /></a></p>
<p>Saya masukkan <code class="language-plaintext highlighter-rouge">luntbuild123</code> sebagai passwordnya.
Kita juga akan ditanyai lokasi instalasi Tomcat agar Luntbuild bisa segera menginstal aplikasi webnya di sana.
Entah apa yang terjadi, di komputer saya pilihan ini tidak berpengaruh apa-apa. Seharusnya installer akan membuat folder <code class="language-plaintext highlighter-rouge">luntbuild</code> di dalam <code class="language-plaintext highlighter-rouge">[TOMCAT_INSTALL]/webapps</code>.
Setelah instalasi selesai saya tetap harus membuat folder <code class="language-plaintext highlighter-rouge">luntbuild</code> di dalam <code class="language-plaintext highlighter-rouge">[TOMCAT_INSTALL]/webapps</code> secara manual
dan mengkopi semua isi <code class="language-plaintext highlighter-rouge">[LUNTBUILD_INSTALL]/web</code> ke dalamnya.</p>
<p>Setelah semua layar berhasil dilalui, instalasi selesai. Kita bisa langsung jalankan Tomcat, dan browse ke http://localhost:8080/luntbuild.
Jika instalasi berjalan benar, maka kita akan disambut oleh Luntbuild</p>
<p><a href="/images/uploads/2007/08/luntbuild-frontpage.png"><img src="/images/uploads/2007/08/luntbuild-frontpage.png" alt="Halaman Depan Luntbuild " /></a></p>
<p>Secara default, kita login secara anonymous. Untuk bisa mengkonfigurasi project, kita harus logout dulu.
Kita akan segera melihat halaman login.</p>
<p><a href="/images/uploads/2007/08/luntbuild-login.png"><img src="/images/uploads/2007/08/luntbuild-login.png" alt="Login Page " /></a></p>
<p>Masukkan username luntbuild dan password luntbuild123, sesuai yang kita isikan pada waktu instalasi.
Kita akan melihat daftar builder yang masih kosong.</p>
<p>Segera klik tab Project, dan klik tombol New Project di kanan atas.
User interface Luntbuild kurang intuitif, sehingga saya harus berusaha keras mencari tombol tersebut.
Halaman New Project segera tampil.</p>
<p><a href="/images/uploads/2007/08/luntbuild-new-project.png"><img src="/images/uploads/2007/08/luntbuild-new-project.png" alt="Create New Project " /></a></p>
<p>Masukkan nama project dan berikan keterangan, lalu Save.</p>
<p>Selanjutnya, kita klik tab VCS.</p>
<p><a href="/images/uploads/2007/08/luntbuild-no-vc.png"><img src="/images/uploads/2007/08/luntbuild-no-vc.png" alt="No VCS Yet " /></a></p>
<p>Di tab ini kita melakukan konfigurasi untuk version control.
Luntbuild mendukung berbagai merek version control.
Pilih Subversion pada drop down yang tersedia.</p>
<p><a href="/images/uploads/2007/08/luntbuild-vcs-svn.png"><img src="/images/uploads/2007/08/luntbuild-vcs-svn.png" alt="Add New Subversion Repo " /></a></p>
<p>Untuk konfigurasi di komputer saya, isinya adalah sebagai berikut:</p>
<ol>
<li>
<p>Repository url base : svn://localhost</p>
</li>
<li>
<p>Directory for trunk : trunk</p>
</li>
<li>
<p>Directory for branches : kosongkan saja, karena project contoh ini tidak memiliki branch</p>
</li>
<li>
<p>Directory for releases : releases/daily-build</p>
</li>
<li>
<p>Username : endy. Ini adalah username yang digunakan Luntbuild untuk checkout dan membuat tag.
Idealnya kita buatkan user khusus untuk Luntbuild</p>
</li>
<li>
<p>Password : 123</p>
</li>
<li>
<p>Quiet Period : kosongkan saja, ini tidak dibutuhkan untuk Subversion yang sudah mendukung atomic commit.</p>
</li>
</ol>
<p>Ada sedikit keanehan Luntbuild. Dia mengharuskan kita membuat modul.
Kalau dia tidak menemukan modul, maka akan keluar pesan error.
Oleh karena itu, kita klik New Module, lalu langsung saja save tanpa mengisi apa-apa.</p>
<p>Hasil akhir dari tab VCS kita akan nampak seperti ini.</p>
<p><a href="/images/uploads/2007/08/luntbuild-vcs-svn-summary.png"><img src="/images/uploads/2007/08/luntbuild-vcs-svn-summary.png" alt="VCS Subversion " /></a></p>
<p>Berikutnya, kita masuk ke tab berikutnya untuk konfigurasi Builder.</p>
<p><a href="/images/uploads/2007/08/luntbuild-no-builders.png"><img src="/images/uploads/2007/08/luntbuild-no-builders.png" alt="No Builder Yet " /></a></p>
<p>Satu project bisa memiliki banyak builder.
Dengan builder, kita bisa mengatur sejauh mana kita ingin melakukan build.
Contoh beberapa build yang mungkin digunakan adalah:</p>
<ul>
<li>
<p>Unit test: hanya menjalankan unit test saja, mungkin dijalankan setiap 1 jam sekali.</p>
</li>
<li>
<p>Full test: menjalankan semua jenis test, dijalankan sebelum jam makan siang dan sore sebelum pulang</p>
</li>
<li>
<p>Nightly/Daily build : dijalankan sekali sehari, selain menjalankan semua test, juga menghasilkan *jar atau *war.
Jenis build ini biasanya dikonfigurasi untuk menghasilkan tag di version control apabila sukses.</p>
</li>
</ul>
<p>Klik tombol New Builder untuk mengkonfigurasi build. Karena kita menggunakan Ant, pilih Ant pada dropdown yang tersedia.</p>
<p><a href="/images/uploads/2007/08/luntbuild-ant-builder.png"><img src="/images/uploads/2007/08/luntbuild-ant-builder.png" alt="Ant Builder " /></a></p>
<p>Pada contoh ini, kita akan mengkonfigurasi Build Jar.
Sekali sudah paham konsepnya, tidak sulit untuk mengkonfigurasi jenis build yang lain.
Berikut adalah nilai yang saya isikan:</p>
<ol>
<li>
<p>Name : Build Jar</p>
</li>
<li>
<p>Command for Ant : /opt/apache-ant-1.7.0/bin/ant. Ini adalah path menuju Ant kita.</p>
</li>
<li>
<p>Build script path : build.xml. Ini adalah nama file build.xml yang ada di project kita</p>
</li>
<li>
<p>Build target : build-jar. Target Ant yang dipanggil pada saat build berjalan.
Saya pilih target build-jar yang mengeksekusi semua test, kemudian membuat *.jar bila test tidak ada yang gagal.</p>
</li>
<li>
<p>Field sisanya, biarkan saja sesuai default.</p>
</li>
</ol>
<p>Terakhir, kita mengkonfigurasi Schedule, alias jadwal. Ini menentukan kapan build dieksekusi.
Untuk Build Jar yang sudah kita konfigurasi, kita ingin dia dijalankan setiap jam 01 dini hari, setiap hari.
Berikut isian konfigurasinya.</p>
<ol>
<li>
<p>Name : Nightly Build</p>
</li>
<li>
<p>Description : build setiap jam 01 dini hari</p>
</li>
<li>
<p>Next build version : <code class="language-plaintext highlighter-rouge">nightly-${#currentDay=system.(year+"-"+numericMonth+"-"+dayOfMonth), #lastDay=project.var["day"].setValue(#currentDay), #dayIterator=project.var["dayIterator"].intValue, project.var["dayIterator"].setIntValue(#currentDay==#lastDay?#dayIterator+1:1), #currentDay}.${project.var["dayIterator"]}</code>.
Ini adalah label yang akan diberikan untuk setiap build.
String di atas akan menghasilkan label nightly-2007-08-17.1 bila dijalankan pada tanggal 17 Agustus 2007.</p>
</li>
<li>
<p>Trigger Type: cron. Luntbuild menyediakan dua jenis trigger. Pertama, simple trigger yang berbasis interval.</p>
</li>
</ol>
<p>Dengan simple trigger, kita dapat menjadwalkan build untuk berjalan misalnya setiap dua jam, atau setiap 30 menit.
Kedua, cron trigger, yang berbasis waktu. Dengan cron trigger, kita dapat menjalankan build setiap waktu tertentu,
misalnya setiap Sabtu, atau setiap hari jam 1 pagi. Untuk menjalankan build setiap jam satu pagi, gunakan cron trigger
dengan konfigurasi <code class="language-plaintext highlighter-rouge">0 0 1 * * ?</code>. Luntbuild menggunakan pustaka <a href="http://www.opensymphony.com/quartz/">Quartz</a> untuk melakukan penjadwalan ini.
Lebih lanjut tentang konfigurasi jadwal Quartz dapat dilihat di <a href="http://www.opensymphony.com/quartz/wikidocs/TutorialLesson6.html">websitenya</a>.</p>
<ol>
<li>
<p>Build necessary condition: vcsModified or dependencyNewer. Buat apa melakukan build kalau belum ada perubahan
sejak terakhir kali build berjalan? Inilah maksud dari field ini. Dengan menyebutkan vcsModified, kita menjelaskan
bahwa kalau tidak ada perubahan di dalam version control, tidak perlu lakukan build. Selain nilai ini, kita juga
bisa menggunakan nilai always, yang artinya tetap build walaupun tidak ada yang commit.</p>
</li>
<li>
<p>Associated builders. Di sini kita memilih builder mana yang ingin dijalankan.</p>
</li>
<li>
<p>Associated post builder. Misalnya setelah build sukses dijalankan, kita ingin membuat installer yang siap diunduh.
Kita bisa sebutkan hal tersebut di sini.</p>
</li>
<li>
<p>Label strategy: Label if success. Hanya buat tag di version control bila build sukses.</p>
</li>
<li>
<p>Notify strategy: notify if status change. Artinya, kirim pemberitahuan apabila status build berbeda dari sebelumnya,
misalnya tadinya gagal menjadi sukses, atau sebaliknya.</p>
</li>
</ol>
<p>Demikian sebagian nilai yang harus diisikan. Untuk field yang tidak saya jelaskan, silahkan baca keterangannya atau ikuti saja default.</p>
<p>Setelah semua konfigurasi selesai, kembali ke halaman depan untuk menyaksikan hasil pekerjaan kita.
Di sana akan terpampang semua build yang sudah dikonfigurasi.</p>
<p><a href="/images/uploads/2007/08/luntbuild-builds.png"><img src="/images/uploads/2007/08/luntbuild-builds.png" alt="Daftar build " /></a></p>
<p>Kita bisa menjalankan build tersebut secara manual tanpa harus
menunggu jatuh temponya. Biasanya kita lakukan build manual untuk mengetes konfigurasi.
Build yang sedang berjalan ditandai dengan ikon roda gigi.</p>
<p><a href="/images/uploads/2007/08/luntbuild-building.png"><img src="/images/uploads/2007/08/luntbuild-building.png" alt="Building " /></a></p>
<p>Build yang gagal akan ditandai dengan warna merah, sedangkan build yang berhasil diwarnai hijau.</p>
<p><a href="/images/uploads/2007/08/luntbuild-build-success.png"><img src="/images/uploads/2007/08/luntbuild-build-success.png" alt="Build Successful " /></a></p>
<p>Bila build gagal, tentunya kita ingin melihat apa yang terjadi. Klik build info untuk menampilkan informasinya.</p>
<p><a href="/images/uploads/2007/08/luntbuild-build-info.png"><img src="/images/uploads/2007/08/luntbuild-build-info.png" alt="Build Info " /></a></p>
<p>Kurang detail? Buka saja lognya. Di sini akan terlihat di langkah yang mana build tersebut berhenti.</p>
<p><a href="/images/uploads/2007/08/luntbuild-build-log.png"><img src="/images/uploads/2007/08/luntbuild-build-log.png" alt="Build Log " /></a></p>
<p>Demikianlah cara penggunaan Luntbuild.
Mudah-mudahan dengan menggunakan perangkat ini proyek software Anda bisa dikelola dengan baik dan teratur.</p>
Cruise Control2007-07-25T15:51:17+07:00https://software.endy.muhardin.com/aplikasi/cruise-control<p>Kalau Anda programmer Java, tentunya sudah tidak asing lagi dengan Tomcat atau Eclipse. Yang satu adalah application server, satunya lagi adalah Integrated Development Environment (IDE). Ada satu kesamaan dalam kedua produk ini, keduanya adalah proyek open source yang dikelola secara profesional.</p>
<p>Coba buka halaman download di website masing-masing proyek tersebut. Di sana bisa kita temukan beberapa jenis download, yaitu Nightly Builds dan Stable Builds. Stable builds adalah aplikasi yang sudah melalui berbagai fase testing dan dinyatakan lulus uji. Nightly builds, seperti namanya, adalah build yang dibuat setiap malam dan dirilis kepada publik agar bisa diuji coba beramai-ramai. Dengan besarnya jumlah penguji, diharapkan bug yang ada dalam aplikasi bisa ditemukan semuanya.</p>
<p>Build, dalam dunia Java, terdiri dari serangkaian aktifitas, diantaranya sebagai berikut:</p>
<ol>
<li>
<p>Menjalankan review kode otomatis</p>
</li>
<li>
<p>Kompilasi kode program</p>
</li>
<li>
<p>Menjalankan unit test</p>
</li>
<li>
<p>Menjalankan integration test</p>
</li>
<li>
<p>Menjalankan coverage test (kalau ada)</p>
</li>
<li>
<p>Menjalankan platform test, untuk aplikasi yang berbeda antar OS (seperti Eclipse)</p>
</li>
<li>
<p>Bila lulus semua ujian di atas, buat tag di version control</p>
</li>
<li>
<p>Siapkan executable (*.jar, *.war, *exe, dan sebagainya)</p>
</li>
<li>
<p>Publish executable di website supaya bisa didownload orang banyak</p>
</li>
</ol>
<p>Wow, daftar aktifitasnya cukup banyak. Kalau ini dikerjakan setiap malam, betapa membosankan. Saya tidak dapat membayangkan ada satu orang yang ditugaskan khusus untuk melakukan hal ini. Pasti setelah beberapa hari saja dia akan bosan dan mengundurkan diri. Apalagi dalam project open source di mana sebagian besar kontributornya tidak dibayar.</p>
<p>Untuk itulah ada tools khusus untuk melakukan hal membosankan ini. Toolsnya disebut Continuous Integration Tools, berdasarkan tulisan Martin Fowler tentang Continuous Integration. Ada beberapa tools yang tersedia di pasaran, diantaranya:</p>
<ul>
<li>
<p><a href="http://cruisecontrol.sf.net">Cruise Control</a></p>
</li>
<li>
<p><a href="http://www.luntbuild.org">Luntbuild</a></p>
</li>
<li>
<p>Anthill</p>
</li>
<li>
<p>Bamboo</p>
</li>
</ul>
<p>Dalam tulisan kali ini, kita akan bahas tentang penggunaan CruiseControl, salah satu Continuous Integration Tool yang populer dan open source.</p>
<p>CI Tool yang baik haruslah memiliki fitur sebagai berikut:</p>
<ul>
<li>
<p>Mendukung berbagai jenis version control, minimal Subversion dan CVS</p>
</li>
<li>
<p>Dapat menggunakan Ant atau Maven</p>
</li>
<li>
<p>Mendukung banyak project</p>
</li>
<li>
<p>Dapat mengelola artifact (hasil build)</p>
</li>
<li>
<p>Dapat memberitahu orang tentang hasil build (gagal atau sukses) melalui berbagai cara (email, instant message, sms, dan sebagainya)</p>
</li>
<li>
<p>Memiliki aplikasi pelaporan yang mudah diakses melalui web</p>
</li>
</ul>
<p>Yang dilakukan oleh CI Tools adalah sebagai berikut:</p>
<ol>
<li>
<p>Periksa repository source code, apakah ada perubahan terbaru</p>
</li>
<li>
<p>Bila ada, ambil perubahan terbaru tersebut</p>
</li>
<li>
<p>Lakukan build (compile, testing, dsb)</p>
</li>
<li>
<p>Laporkan hasilnya (gagal atau berhasil) melalui email atau media lainnya</p>
</li>
<li>
<p>Buat juga laporan di website</p>
</li>
<li>
<p>Bila sukses, publish artifact yang dihasilkan</p>
</li>
</ol>
<p>Cruise Control membutuhkan beberapa file konfigurasi:</p>
<ul>
<li>
<p>config.xml: Hanya satu, digunakan untuk mendaftarkan project yang akan dikelola</p>
</li>
<li>
<p>build-namaproject.xml : Satu per project. Bila kita punya tiga project, maka akan ada tiga build-namaproject.xml, dengan nama file disesuaikan dengan masing-masing project.Buildfile ini bertugas untuk sinkronisasi dengan version control dan membuat tag di version control</p>
</li>
<li>
<p>build.xml : Satu per project. Ini adalah buildfile yang biasa kita gunakan di project untuk melakukan kompilasi, menjalankan test, dan menghasilkan *jar atau *war.</p>
</li>
</ul>
<p>Kita butuh satu folder kerja untuk CruiseControl melakukan tugasnya. Sebaiknya folder ini dipisahkan dari folder instalasi CruiseControl agar memudahkan kita pada waktu melakukan upgrade CruiseControl. Untuk kepentingan ilustrasi, folder instalasi CruiseControl akan ditulis [CC_INSTALL] dan folder kerja akan ditulis [CC_WORK].</p>
<p>Berikut adalah struktur folder [CC_INSTALL]:</p>
<p><a href="/images/uploads/2007/07/cruise-install.png"><img src="/images/uploads/2007/07/cruise-install.png" alt="Struktur Folder Instalasi CruiseControl " /></a></p>
<p>dan folder kerja [CC_WORK] akan berisi seperti ini:</p>
<p><a href="/images/uploads/2007/07/cruise-work.png"><img src="/images/uploads/2007/07/cruise-work.png" alt="Folder Kerja CruiseControl " /></a></p>
<p>Struktur folder di dalam repository Subversion kita seperti ini:</p>
<p><a href="/images/uploads/2007/07/repo-content.png"><img src="/images/uploads/2007/07/repo-content.png" alt="Struktur Folder Subversion " /></a></p>
<p>Pada contoh ini, kita akan menjalankan build untuk satu project <code class="language-plaintext highlighter-rouge">TestingTraining</code> dengan konfigurasi sebagai berikut:</p>
<ol>
<li>
<p>Memiliki dua build, yang satu berjalan setiap satu menit, satu lagi berjalan setiap jam 18.00 sore</p>
</li>
<li>
<p>Version control yang digunakan adalah Subversion</p>
</li>
<li>
<p>Bila build sukses, buat tag di folder releases/daily-build/build-NN-yyyyMMddhhmmss</p>
</li>
</ol>
<p>Konfigurasi tersebut ditulis di <code class="language-plaintext highlighter-rouge">config.xml</code> sebagai berikut.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><cruisecontrol></span>
<span class="nt"><project</span> <span class="na">name=</span><span class="s">"TestingTraining"</span> <span class="na">buildafterfailed=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><listeners></span>
<span class="nt"><currentbuildstatuslistener</span> <span class="na">file=</span><span class="s">"logs/${project.name}/status.txt"</span><span class="nt">/></span>
<span class="nt"></listeners></span>
<span class="nt"><modificationset</span> <span class="na">quietperiod=</span><span class="s">"10"</span><span class="nt">></span>
<span class="nt"><svn</span> <span class="na">RepositoryLocation=</span><span class="s">"svn://172.16.2.252/trunk"</span> <span class="nt">/></span>
<span class="nt"></modificationset></span>
<span class="nt"><schedule</span> <span class="na">interval=</span><span class="s">"60"</span><span class="nt">></span>
<span class="nt"><ant</span> <span class="na">anthome=</span><span class="s">"/opt/apache-ant-1.6.5"</span>
<span class="na">antWorkingDir=</span><span class="s">"/home/endy/tmp/cruise-work/projects/${project.name}"</span>
<span class="na">buildfile=</span><span class="s">"/home/endy/tmp/cruise-work/build-${project.name}.xml"</span>
<span class="na">target=</span><span class="s">"build"</span>
<span class="na">uselogger=</span><span class="s">"true"</span>
<span class="na">usedebug=</span><span class="s">"false"</span>
<span class="nt">/></span>
<span class="nt"></schedule></span>
<span class="nt"><log></span>
<span class="nt"><merge</span> <span class="na">dir=</span><span class="s">"/home/endy/tmp/cruise-work/projects/${project.name}/build/report/junit"</span><span class="nt">/></span>
<span class="nt"></log></span>
<span class="nt"><publishers></span>
<span class="nt"><onsuccess></span>
<span class="nt"><antpublisher</span> <span class="na">anthome=</span><span class="s">"/opt/apache-ant-1.6.5"</span>
<span class="na">antWorkingDir=</span><span class="s">"/home/endy/tmp/cruise-work/projects/${project.name}"</span>
<span class="na">buildfile=</span><span class="s">"/home/endy/tmp/cruise-work/build-${project.name}.xml"</span>
<span class="na">target=</span><span class="s">"tag"</span>
<span class="na">uselogger=</span><span class="s">"true"</span>
<span class="na">usedebug=</span><span class="s">"false"</span>
<span class="nt">/></span>
<span class="nt"><artifactspublisher</span>
<span class="na">dir=</span><span class="s">"/home/endy/tmp/cruise-work/projects/${project.name}/dist"</span>
<span class="na">dest=</span><span class="s">"artifacts/${project.name}"</span><span class="nt">/></span>
<span class="nt"></onsuccess></span>
<span class="nt"></publishers></span>
<span class="nt"></project></span>
<span class="nt"></cruisecontrol></span>
</code></pre></div></div>
<p>Berikut adalah penjelasannya.</p>
<ul>
<li>
<p>Konfigurasi CruiseControl dibuat dengan format XML. Tag paling luar adalah <code class="language-plaintext highlighter-rouge"><cruisecontrol></code></p>
</li>
<li>
<p>Satu file konfigurasi bisa memuat banyak project. Masing-masing project dikonfigurasi dalam tag <code class="language-plaintext highlighter-rouge"><project></code></p>
</li>
<li>
<p>Di halaman depan CruiseControl, akan ada status untuk masing-masing project. Status ini akan disimpan di file status.txt</p>
</li>
<li>
<p>Modification Set: Beberapa produk version control tidak mengenal atomic commit, misalnya CVS. Karena itu, bila kita commit (misalnya) 7 file sekaligus, bisa saja CruiseControl terbangun pada saat baru 3 file yang tercommit. Bila CruiseControl melakukan build pada saat itu juga, bisa dipastikan akan terjadi error. Oleh karena itu, kita suruh CruiseControl membatalkan build bila kurang dari 10 detik yang lalu ada orang yang commit. Tentu saja bila kita menggunakan Subversion, hal ini tidak relevan.</p>
</li>
<li>
<p>Schedule: Ini adalah daftar build schedule yang ingin kita daftarkan. Tag ini bisa memuat banyak build. Kita menggunakan interval 60 detik, yang artinya setiap menit CruiseControl akan melakukan build. Selain schedule yang berbasis interval, kita juga bisa mendaftarkan schedule yang menggunakan waktu tertentu, misalnya berjalan setiap jam 12 malam.</p>
</li>
<li>
<p>Merge log yang dihasilkan JUnit, tersimpan di folder <code class="language-plaintext highlighter-rouge">build/report/junit</code> ke dalam tampilan report CruiseControl.</p>
</li>
<li>
<p>Publisher : pada bagian ini kita daftarkan Ant Publisher pada kategori onsuccess. Artinya, kalau build sukses, jalankan target tag untuk membuat tag di dalam repository. Selain itu, kita juga menggunakan Artifact Publisher, yang berguna untuk mengkopi file *.jar yang dihasilkan ke dalam folder CruiseControl sehingga bisa didownload melalui website.</p>
</li>
</ul>
<p>Di file konfigurasi tersebut, kita memanggil file <code class="language-plaintext highlighter-rouge">build-TestingTraining.xml</code>. Ini adalah isi dari file tersebut.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><project</span> <span class="na">name=</span><span class="s">"CruiseControl Builder"</span>
<span class="na">default=</span><span class="s">"build"</span>
<span class="na">basedir=</span><span class="s">"projects/TestingTraining"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"repository.url"</span> <span class="na">value=</span><span class="s">"svn://localhost"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"branch"</span> <span class="na">value=</span><span class="s">"trunk"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"release.folder"</span> <span class="na">value=</span><span class="s">"releases/daily-build"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"svn.username"</span> <span class="na">value=</span><span class="s">"endy"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"svn.password"</span> <span class="na">value=</span><span class="s">"123"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"message"</span> <span class="na">value=</span><span class="s">"Created automatically by CruiseControl"</span> <span class="nt">/></span>
<span class="nt"><path</span> <span class="na">id=</span><span class="s">"cc-classpath"</span><span class="nt">></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"../../lib"</span> <span class="na">includes=</span><span class="s">"**/*jar"</span><span class="nt">/></span>
<span class="nt"></path></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"update"</span><span class="nt">></span>
<span class="nt"><java</span> <span class="na">classname=</span><span class="s">"org.tmatesoft.svn.cli.SVN"</span> <span class="na">fork=</span><span class="s">"true"</span>
<span class="na">classpathref=</span><span class="s">"cc-classpath"</span><span class="nt">></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"update"</span><span class="nt">/></span>
<span class="nt"></java></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"build"</span> <span class="na">depends=</span><span class="s">"update"</span><span class="nt">></span>
<span class="nt"><ant</span> <span class="na">inheritAll=</span><span class="s">"false"</span> <span class="na">target=</span><span class="s">"build-jar"</span><span class="nt">/></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"tag"</span><span class="nt">></span>
<span class="nt"><java</span> <span class="na">classname=</span><span class="s">"org.tmatesoft.svn.cli.SVN"</span>
<span class="na">fork=</span><span class="s">"true"</span> <span class="na">classpathref=</span><span class="s">"cc-classpath"</span><span class="nt">></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"--username"</span><span class="nt">/></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"${svn.username}"</span><span class="nt">/></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"--password"</span><span class="nt">/></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"${svn.password}"</span><span class="nt">/></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"cp"</span><span class="nt">/></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"${repository.url}/${branch}"</span><span class="nt">/></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"${repository.url}/${release.folder}/${label}-${cctimestamp}"</span><span class="nt">/></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"-m"</span><span class="nt">/></span>
<span class="nt"><arg</span> <span class="na">value=</span><span class="s">"${message}"</span><span class="nt">/></span>
<span class="nt"></java></span>
<span class="nt"></target></span>
<span class="nt"></project></span>
</code></pre></div></div>
<p>Penjelasannya sebagai berikut.</p>
<ul>
<li>
<p>Kita menggunakan pustaka yang disediakan <a href="http://svnkit.com/">SVNKit (dulunya JavaSVN) </a>agar bisa menggunakan Subversion melalui Ant. Sebetulnya tanpa SVNKit juga bisa, yaitu dengan target <code class="language-plaintext highlighter-rouge">exec</code> yang dimiliki Ant untuk memanggil perintah di command line. Dengan menggunakan SVNKit, kita tidak perlu menginstal Subversion command line di build server kita.</p>
</li>
<li>
<p>Ada tiga target utama dalam buildfile ini: update, build, dan tag. Target update berguna untuk mengambil perubahan terbaru dari repository ke folder kerja CruiseControl. Target build memanggil target build-jar di dalam build.xml yang dimiliki project. Target build-jar ini hanya bisa berhasil kalau semua test sudah berjalan dengan sempurna. Target tag berguna untuk membuat tag di repository. Kita bisa mengirim variabel yang dihasilkan CruiseControl, dalam hal ini <code class="language-plaintext highlighter-rouge">${label}</code> dan <code class="language-plaintext highlighter-rouge">${cctimestamp}</code>.</p>
</li>
</ul>
<p>Agar file ini bisa dijalankan, kita harus memiliki folder projects/TestingTraining yang berisi hasil checkout dari repository. Mari kita checkout dulu.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd projects
$ svn co svn://localhost/trunk TestingTraining
</code></pre></div></div>
<p>Untuk menguji apakah file build-TestingTraining.xml ini bisa dijalankan dengan baik, masuk ke folder projects/TestingTraining dan panggil file tersebut dari folder ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ant -f ../../build-TestingTraining.xml build
Buildfile: ../../build-TestingTraining.xml
update:
[java] At revision 71.
build:
clean:
[delete] Deleting directory /home/endy/tmp/cruise-work/projects/TestingTraining/build/bin
[delete] Deleting directory /home/endy/tmp/cruise-work/projects/TestingTraining/build/debug
[delete] Deleting directory /home/endy/tmp/cruise-work/projects/TestingTraining/build/cobertura
[delete] Deleting directory /home/endy/tmp/cruise-work/projects/TestingTraining/build/report/junit
[delete] Deleting directory /home/endy/tmp/cruise-work/projects/TestingTraining/build/report/pmd
[delete] Deleting directory /home/endy/tmp/cruise-work/projects/TestingTraining/build/report/cobertura
[delete] Deleting directory /home/endy/tmp/cruise-work/projects/TestingTraining/dist
prepare:
[mkdir] Created dir: /home/endy/tmp/cruise-work/projects/TestingTraining/build/bin
[mkdir] Created dir: /home/endy/tmp/cruise-work/projects/TestingTraining/build/debug
[mkdir] Created dir: /home/endy/tmp/cruise-work/projects/TestingTraining/build/cobertura
[mkdir] Created dir: /home/endy/tmp/cruise-work/projects/TestingTraining/build/report/junit
[mkdir] Created dir: /home/endy/tmp/cruise-work/projects/TestingTraining/build/report/pmd
[mkdir] Created dir: /home/endy/tmp/cruise-work/projects/TestingTraining/build/report/cobertura
[mkdir] Created dir: /home/endy/tmp/cruise-work/projects/TestingTraining/dist
code-review:
compile:
[javac] Compiling 11 source files to /home/endy/tmp/cruise-work/projects/TestingTraining/build/bin
compile-cobertura:
[javac] Compiling 11 source files to /home/endy/tmp/cruise-work/projects/TestingTraining/build/debug
[delete] Deleting: /home/endy/tmp/cruise-work/projects/TestingTraining/cobertura.ser
[delete] Deleting directory /home/endy/tmp/cruise-work/projects/TestingTraining/build/cobertura
[cobertura-instrument] Cobertura 1.8 - GNU GPL License (NO WARRANTY) - See COPYRIGHT file
[cobertura-instrument] Instrumenting 7 files to /home/endy/tmp/cruise-work/projects/TestingTraining/build/cobertura
[cobertura-instrument] Cobertura: Saved information on 7 classes.
[cobertura-instrument] Instrument time: 110ms
unit-test:
[junit] Running tutorial.testing.CalculatorTest
[junit] Tests run: 2, Failures: 0, Errors: 0, Time elapsed: 0.047 sec
[junit] Cobertura: Loaded information on 7 classes.
[junit] Cobertura: Saved information on 7 classes.
[junit] Running tutorial.testing.DayCounterTest
[junit] Tests run: 2, Failures: 0, Errors: 0, Time elapsed: 0.055 sec
[junit] Cobertura: Loaded information on 7 classes.
[junit] Cobertura: Saved information on 7 classes.
[junit] Running tutorial.testing.LoginServletTest
[junit] Tests run: 2, Failures: 0, Errors: 0, Time elapsed: 0.159 sec
[junit] Cobertura: Loaded information on 7 classes.
[junit] Cobertura: Saved information on 7 classes.
[junit] Running tutorial.testing.dbunit.PersonDaoMySQLTest
[junit] Tests run: 3, Failures: 0, Errors: 0, Time elapsed: 0.153 sec
[junit] Cobertura: Loaded information on 7 classes.
[junit] Cobertura: Saved information on 7 classes.
[junitreport] Transform time: 564ms
[cobertura-report] Cobertura 1.8 - GNU GPL License (NO WARRANTY) - See COPYRIGHT file
[cobertura-report] Cobertura: Loaded information on 7 classes.
[cobertura-report] Report time: 231ms
coverage-test:
[cobertura-check] Cobertura 1.8 - GNU GPL License (NO WARRANTY) - See COPYRIGHT file
[cobertura-check] Cobertura: Loaded information on 7 classes.
[cobertura-check] All checks passed.
full-test:
build-jar:
[jar] Building jar: /home/endy/tmp/cruise-work/projects/TestingTraining/dist/TutorialTesting.jar
BUILD SUCCESSFUL
Total time: 12 seconds
</code></pre></div></div>
<p>File <code class="language-plaintext highlighter-rouge">build-TestingTraining.xml</code> akan memanggil <code class="language-plaintext highlighter-rouge">build.xml</code> yang ada di dalam project kita. Berikut isi dari file ini.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><project</span> <span class="na">name=</span><span class="s">"TestingTraining"</span> <span class="na">default=</span><span class="s">"full-test"</span> <span class="na">basedir=</span><span class="s">"."</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"source.java"</span> <span class="na">value=</span><span class="s">"src/java"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"compile.normal"</span> <span class="na">value=</span><span class="s">"build/bin"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"compile.debug"</span> <span class="na">value=</span><span class="s">"build/debug"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"compile.cobertura"</span> <span class="na">value=</span><span class="s">"build/cobertura"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"distribution"</span> <span class="na">value=</span><span class="s">"dist"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"report.junit"</span> <span class="na">value=</span><span class="s">"build/report/junit"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"report.pmd"</span> <span class="na">value=</span><span class="s">"build/report/pmd"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"report.cobertura"</span> <span class="na">value=</span><span class="s">"build/report/cobertura"</span><span class="nt">/></span>
<span class="nt"><path</span> <span class="na">id=</span><span class="s">"devel.classpath"</span><span class="nt">></span>
<span class="nt"><pathelement</span> <span class="na">location=</span><span class="s">"${source.java}"</span><span class="nt">/></span>
<span class="nt"><pathelement</span> <span class="na">location=</span><span class="s">"${compile.debug}"</span><span class="nt">/></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"lib"</span> <span class="na">includes=</span><span class="s">"**/*jar"</span> <span class="nt">/></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"ext"</span> <span class="na">includes=</span><span class="s">"**/*jar"</span> <span class="nt">/></span>
<span class="nt"></path></span>
<span class="nt"><taskdef</span> <span class="na">classpathref=</span><span class="s">"devel.classpath"</span> <span class="na">resource=</span><span class="s">"tasks.properties"</span><span class="nt">/></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"prepare"</span><span class="nt">></span>
<span class="nt"><mkdir</span> <span class="na">dir=</span><span class="s">"${compile.normal}"</span><span class="nt">/></span>
<span class="nt"><mkdir</span> <span class="na">dir=</span><span class="s">"${compile.debug}"</span><span class="nt">/></span>
<span class="nt"><mkdir</span> <span class="na">dir=</span><span class="s">"${compile.cobertura}"</span><span class="nt">/></span>
<span class="nt"><mkdir</span> <span class="na">dir=</span><span class="s">"${report.junit}"</span><span class="nt">/></span>
<span class="nt"><mkdir</span> <span class="na">dir=</span><span class="s">"${report.pmd}"</span><span class="nt">/></span>
<span class="nt"><mkdir</span> <span class="na">dir=</span><span class="s">"${report.cobertura}"</span><span class="nt">/></span>
<span class="nt"><mkdir</span> <span class="na">dir=</span><span class="s">"${distribution}"</span> <span class="nt">/></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"clean"</span><span class="nt">></span>
<span class="nt"><delete</span> <span class="na">dir=</span><span class="s">"${compile.normal}"</span><span class="nt">></delete></span>
<span class="nt"><delete</span> <span class="na">dir=</span><span class="s">"${compile.debug}"</span><span class="nt">></delete></span>
<span class="nt"><delete</span> <span class="na">dir=</span><span class="s">"${compile.cobertura}"</span><span class="nt">></delete></span>
<span class="nt"><delete</span> <span class="na">dir=</span><span class="s">"${report.junit}"</span><span class="nt">></delete></span>
<span class="nt"><delete</span> <span class="na">dir=</span><span class="s">"${report.pmd}"</span><span class="nt">></delete></span>
<span class="nt"><delete</span> <span class="na">dir=</span><span class="s">"${report.cobertura}"</span><span class="nt">></delete></span>
<span class="nt"><delete</span> <span class="na">dir=</span><span class="s">"${distribution}"</span><span class="nt">></delete></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"code-review"</span> <span class="na">depends=</span><span class="s">"prepare"</span><span class="nt">></span>
<span class="nt"><taskdef</span> <span class="na">name=</span><span class="s">"pmd"</span> <span class="na">classname=</span><span class="s">"net.sourceforge.pmd.ant.PMDTask"</span> <span class="na">classpathref=</span><span class="s">"devel.classpath"</span><span class="nt">/></span>
<span class="nt"><pmd</span> <span class="na">targetjdk=</span><span class="s">"1.5"</span> <span class="na">shortFilenames=</span><span class="s">"true"</span> <span class="na">failonerror=</span><span class="s">"true"</span> <span class="na">failonruleviolation=</span><span class="s">"true"</span> <span class="na">rulesetfiles=</span><span class="s">"pmd-ruleset.xml"</span><span class="nt">></span>
<span class="nt"><formatter</span> <span class="na">type=</span><span class="s">"net.sourceforge.pmd.renderers.HTMLRenderer"</span>
<span class="na">toFile=</span><span class="s">"${report.pmd}/pmd.html"</span>
<span class="nt">/></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"${source.java}"</span><span class="nt">></span>
<span class="nt"><include</span> <span class="na">name=</span><span class="s">"**/*.java"</span><span class="nt">/></span>
<span class="nt"><exclude</span> <span class="na">name=</span><span class="s">"**/*Test.java"</span><span class="nt">/></span>
<span class="nt"></fileset></span>
<span class="nt"></pmd></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"compile"</span> <span class="na">depends=</span><span class="s">"code-review"</span><span class="nt">></span>
<span class="nt"><javac</span>
<span class="na">srcdir=</span><span class="s">"${source.java}"</span>
<span class="na">destdir=</span><span class="s">"${compile.normal}"</span>
<span class="na">classpathref=</span><span class="s">"devel.classpath"</span><span class="nt">></span>
<span class="nt"></javac></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"unit-test"</span> <span class="na">depends=</span><span class="s">"compile,compile-cobertura"</span><span class="nt">></span>
<span class="nt"><junit</span> <span class="na">haltonfailure=</span><span class="s">"true"</span> <span class="na">fork=</span><span class="s">"true"</span> <span class="na">printsummary=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><classpath</span> <span class="na">location=</span><span class="s">"${compile.cobertura}"</span><span class="nt">></classpath></span>
<span class="nt"><classpath</span> <span class="na">refid=</span><span class="s">"devel.classpath"</span><span class="nt">/></span>
<span class="nt"><formatter</span> <span class="na">type=</span><span class="s">"xml"</span><span class="nt">/></span>
<span class="nt"><batchtest</span> <span class="na">todir=</span><span class="s">"${report.junit}"</span><span class="nt">></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"${compile.debug}"</span> <span class="na">includes=</span><span class="s">"**/*Test.class"</span> <span class="na">excludes=</span><span class="s">"**/*Abstract*.class"</span><span class="nt">/></span>
<span class="nt"></batchtest></span>
<span class="nt"></junit></span>
<span class="nt"><junitreport</span> <span class="na">todir=</span><span class="s">"${report.junit}"</span><span class="nt">></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"${report.junit}"</span><span class="nt">></span>
<span class="nt"><include</span> <span class="na">name=</span><span class="s">"TEST-*.xml"</span><span class="nt">/></span>
<span class="nt"></fileset></span>
<span class="nt"><report</span> <span class="na">format=</span><span class="s">"frames"</span> <span class="na">todir=</span><span class="s">"${report.junit}/html"</span><span class="nt">/></span>
<span class="nt"></junitreport></span>
<span class="nt"><cobertura-report</span>
<span class="na">datafile=</span><span class="s">"cobertura.ser"</span>
<span class="na">srcdir=</span><span class="s">"${source.java}"</span>
<span class="na">destdir=</span><span class="s">"${report.cobertura}"</span>
<span class="nt">/></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"compile-cobertura"</span> <span class="na">depends=</span><span class="s">"prepare"</span><span class="nt">></span>
<span class="nt"><javac</span>
<span class="na">srcdir=</span><span class="s">"${source.java}"</span>
<span class="na">destdir=</span><span class="s">"${compile.debug}"</span>
<span class="na">classpathref=</span><span class="s">"devel.classpath"</span>
<span class="na">fork=</span><span class="s">"true"</span>
<span class="na">debug=</span><span class="s">"true"</span>
<span class="nt">/></span>
<span class="nt"><delete</span> <span class="na">file=</span><span class="s">"cobertura.ser"</span><span class="nt">/></span>
<span class="nt"><delete</span> <span class="na">dir=</span><span class="s">"${compile.cobertura}"</span> <span class="nt">/></span>
<span class="nt"><cobertura-instrument</span> <span class="na">todir=</span><span class="s">"${compile.cobertura}"</span><span class="nt">></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"${compile.debug}"</span><span class="nt">></span>
<span class="nt"><include</span> <span class="na">name=</span><span class="s">"**/*.class"</span><span class="nt">/></span>
<span class="nt"><exclude</span> <span class="na">name=</span><span class="s">"**/*Test*.class"</span><span class="nt">/></span>
<span class="nt"></fileset></span>
<span class="nt"></cobertura-instrument></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"coverage-test"</span> <span class="na">depends=</span><span class="s">"unit-test"</span><span class="nt">></span>
<span class="nt"><cobertura-check</span> <span class="na">datafile=</span><span class="s">"cobertura.ser"</span>
<span class="na">branchrate=</span><span class="s">"75"</span>
<span class="na">linerate=</span><span class="s">"75"</span>
<span class="na">haltonfailure=</span><span class="s">"true"</span>
<span class="nt">/></span>
<span class="nt"></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"full-test"</span> <span class="na">depends=</span><span class="s">"code-review,coverage-test"</span><span class="nt">></target></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"build-jar"</span> <span class="na">depends=</span><span class="s">"clean,full-test"</span><span class="nt">></span>
<span class="nt"><jar</span> <span class="na">destfile=</span><span class="s">"${distribution}/${ant.project.name}.jar"</span>
<span class="na">basedir=</span><span class="s">"${compile.normal}"</span>
<span class="na">excludes=</span><span class="s">"**/*Test.class"</span>
<span class="nt">/></span>
<span class="nt"></target></span>
<span class="nt"></project></span>
</code></pre></div></div>
<p>Seperti kita lihat di atas, file tersebut memiliki target build-jar yang hanya akan dilakukan apabila target code-review, compile, unit-test, coverage-test dilakukan dengan sukses.</p>
<p>Baiklah, persiapan sudah selesai, sekarang jalankan CruiseControl, dan lihat hasilnya.
CruiseControl dijalankan melalui command prompt dengan mensuplai config.xml yang kita miliki.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd [CC_INSTALL]
$ ./cruisecontrol.sh -configfile [CC_WORK]/config.xml
</code></pre></div></div>
<p>Bila tidak ada error, maka kita bisa browse ke http://localhost:8080</p>
<p>Ini adalah halaman depan. Memuat status masing-masing project.</p>
<p><a href="/images/uploads/2007/07/halaman-depan.png"><img src="/images/uploads/2007/07/halaman-depan.png" alt="Halaman Depan CruiseControl " /></a></p>
<p>Ini adalah halaman untuk TestingTraining.</p>
<p><a href="/images/uploads/2007/07/build-detail.png"><img src="/images/uploads/2007/07/build-detail.png" alt="Hasil Build " /></a></p>
<p>Seperti kita lihat, hasilnya build failed, karena database MySQL yang diperlukan untuk test belum dijalankan. Mari kita nyalakan dan buat test ini menjadi sukses. Setelah sukses, tampilannya seperti ini.</p>
<p><a href="/images/uploads/2007/07/build-success.png"><img src="/images/uploads/2007/07/build-success.png" alt="Hasil Build yang sukses " /></a></p>
<p>Kita bisa melihat hasil unit test di dalam tab Test Result.</p>
<p><a href="/images/uploads/2007/07/test-result.png"><img src="/images/uploads/2007/07/test-result.png" alt="Hasil JUnit " /></a></p>
<p>Berapa kali gagal, berapa kali sukses? Kita bisa lihat di tab Metric.</p>
<p><a href="/images/uploads/2007/07/metric.png"><img src="/images/uploads/2007/07/metric.png" alt="Metric tentang hasil build " /></a></p>
<p>Bila kita sudah menjalankan daily build ini selama beberapa waktu, di repository kita akan dihasilkan tag sesuai dengan build yang sukses. Berikut contohnya.</p>
<p><a href="/images/uploads/2007/07/build-result.png"><img src="/images/uploads/2007/07/build-result.png" alt="Daftar Tag yang dibuat CruiseControl " /></a></p>
<p>Demikianlah penggunaan CruiseControl. Dengan menggunakan CruiseControl ini, para programmer bisa dibebaskan dari tugas membosankan, sehingga waktunya bisa digunakan untuk hal lain yang lebih bermanfaat, seperti misalnya menulis blog atau menambah teman baru di Friendster.</p>
Dual Head Ubuntu2007-06-28T17:00:00+07:00https://software.endy.muhardin.com/linux/ubuntu-dual-head<p>Pertanyaan : Kalau kita punya komputer, upgrade hardware apa yang paling meningkatkan produktifitas ??</p>
<p>Jawaban 1 : Prosesor</p>
<p>Jawaban 2 : Memori</p>
<p>Saya tidak sependapat dengan kedua jawaban di atas. Prosesor lebih kencang atau memori memang meningkatkan kemampuan komputer. Tapi ada upgrade lain yang lebih signifikan efeknya, yaitu tambah monitor.</p>
<p>Anda pernah menonton film Swordfish? Film ini bercerita tentang Stanley Jobson, pensiunan hacker yang dipaksa membobol bank demi menyelamatkan anaknya. Di film ini, Stanley diberikan komputer super canggih agar bisa membobol jaringan bank. Komputer tersebut memiliki enam screen.</p>
<p>Mereka yang sudah pernah merasakan bekerja dengan dua (atau lebih) monitor pasti mengerti maksud saya. Bayangkan coding di satu screen dan melihat Java API di screen yang satu lagi. Atau membuat desain software (UML, ERD, dsb) di screen kanan sambil melihat dokumen analisa di screen kiri. Dan masih banyak kemungkinan lain yang bisa dilakukan. Bahkan <a href="http://money.cnn.com/2006/03/30/news/newsmakers/gates_howiwork_fortune/">Bill Gates bekerja dengan tiga monitor sekaligus</a>.</p>
<p>Pada artikel ini, saya akan membahas tentang cara setup dua monitor di Ubuntu. Komputer yang saya gunakan adalah NEC Versa 3100. Walaupun berbeda di sisi teknis, cara ini juga bisa diterapkan di komputer lain yang menggunakan X.Org sebagai X servernya.</p>
<p>Sebelum mulai, mari kita kenali dulu istilah-istilah yang ada di dalam konfigurasi X.Org.</p>
<ul>
<li>
<p>InputDevice : Ini adalah segala alat input seperti mouse, keyboard, touchscreen, pulpen elektronik, dan teman-temannya. Dalam satu file konfigurasi bisa dimasukkan banyak device.</p>
</li>
<li>
<p>Device: Yang dimaksud device di sini adalah display adapter kita, atau lebih dikenal dengan istilah VGA Card. Bila kita memasang lebih dari satu VGA Card, maka harus ada konfigurasi Device yang sesuai agar VGA Card tersebut bisa berfungsi dengan baik.</p>
</li>
<li>
<p>Monitor: Self Explanatory. Monitor adalah yang menampilkan output dari komputer.</p>
</li>
</ul>
<p>Untuk mengaktifkan dual head, kita membutuhkan dua monitor. Perlu diperhatikan bahwa kita belum tentu butuh lebih dari satu VGA Card, tergantung tipe yang kita gunakan. Di PC saya yang lainnya, saya menggunakan NVidia GForce yang memiliki tiga output: VGA, DVI, dan TV. Yang biasa kita gunakan di monitor CRT adalah VGA port, sedangkan beberapa tipe monitor LCD biasanya menyediakan kabel VGA dan juga DVI. TV Out tentunya kita sudah tahu bentuknya, seperti colokan dari DVD player itu lho ..</p>
<p>Karena saya menggunakan notebook, maka saya memiliki dua output dari VGA, yang satu adalah LCD bawaan notebook tersebut, dan satu lagi VGA port yang biasa kita gunakan untuk presentasi.</p>
<p>Jumlah VGA dan output port yang kita miliki sangat penting diketahui agar bisa mengkonfigurasi dual head dengan benar. Untuk mereka yang VGA outputnya cuma satu port terpaksa pasang satu card lagi agar bisa menggunakan dual head.</p>
<p>Baiklah, mari kita masuk ke konfigurasi. Sebagai titik awal, berikut konfigurasi X.Org saya sebelum ada modifikasi apa-apa. Ini merupakan konfigurasi yang dibuatkan Ubuntu.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /etc/X11/xorg.conf (xorg X Window System server configuration file)
#
# This file was generated by dexconf, the Debian X Configuration tool, using
# values from the debconf database.
#
# Edit this file with caution, and see the xorg.conf(5) manual page.
# (Type "man xorg.conf" at the shell prompt.)
#
# This file is automatically updated on xserver-xorg package upgrades *only*
# if it has not been modified since the last upgrade of the xserver-xorg
# package.
#
# If you have edited this file but would like it to be automatically updated
# again, run the following command:
# sudo dpkg-reconfigure -phigh xserver-xorg
Section "Files"
FontPath "/usr/share/fonts/X11/misc"
FontPath "/usr/share/fonts/X11/cyrillic"
FontPath "/usr/share/fonts/X11/100dpi/:unscaled"
FontPath "/usr/share/fonts/X11/75dpi/:unscaled"
FontPath "/usr/share/fonts/X11/Type1"
FontPath "/usr/share/fonts/X11/100dpi"
FontPath "/usr/share/fonts/X11/75dpi"
# path to defoma fonts
FontPath "/var/lib/defoma/x-ttcidfont-conf.d/dirs/TrueType"
EndSection
Section "Module"
Load "i2c"
Load "bitmap"
Load "ddc"
Load "dri"
Load "extmod"
Load "freetype"
Load "glx"
Load "int10"
Load "vbe"
EndSection
Section "InputDevice"
Identifier "Generic Keyboard"
Driver "kbd"
Option "CoreKeyboard"
Option "XkbRules" "xorg"
Option "XkbModel" "pc105"
Option "XkbLayout" "us"
EndSection
Section "InputDevice"
Identifier "Configured Mouse"
Driver "mouse"
Option "CorePointer"
Option "Device" "/dev/input/mice"
Option "Protocol" "ImPS/2"
Option "ZAxisMapping" "4 5"
Option "Emulate3Buttons" "true"
EndSection
Section "InputDevice"
Identifier "Synaptics Touchpad"
Driver "synaptics"
Option "SendCoreEvents" "true"
Option "Device" "/dev/psaux"
Option "Protocol" "auto-dev"
Option "HorizScrollDelta" "0"
EndSection
Section "InputDevice"
Driver "wacom"
Identifier "stylus"
Option "Device" "/dev/input/wacom"
Option "Type" "stylus"
Option "ForceDevice" "ISDV4" # Tablet PC ONLY
EndSection
Section "InputDevice"
Driver "wacom"
Identifier "eraser"
Option "Device" "/dev/input/wacom"
Option "Type" "eraser"
Option "ForceDevice" "ISDV4" # Tablet PC ONLY
EndSection
Section "InputDevice"
Driver "wacom"
Identifier "cursor"
Option "Device" "/dev/input/wacom"
Option "Type" "cursor"
Option "ForceDevice" "ISDV4" # Tablet PC ONLY
EndSection
Section "Device"
Identifier "Intel Corporation Mobile 915GM/GMS/910GML Express Graphics Controller"
Driver "i810"
BusID "PCI:0:2:0"
# Option "ForceBIOS" "1920x1440=1280x768"
EndSection
Section "Monitor"
Identifier "Generic Monitor"
Option "DPMS"
EndSection
Section "Screen"
Identifier "Default Screen"
Device "Intel Corporation Mobile 915GM/GMS/910GML Express Graphics Controller"
Monitor "Generic Monitor"
DefaultDepth 24
SubSection "Display"
Depth 1
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 4
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 8
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 15
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 16
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 24
Modes "1280x768"
EndSubSection
EndSection
Section "ServerLayout"
Identifier "Default Layout"
Screen "Default Screen"
InputDevice "Generic Keyboard"
InputDevice "Configured Mouse"
InputDevice "stylus" "SendCoreEvents"
InputDevice "cursor" "SendCoreEvents"
InputDevice "eraser" "SendCoreEvents"
InputDevice "Synaptics Touchpad"
EndSection
Section "DRI"
Mode 0666
EndSection
</code></pre></div></div>
<p>Perlu saya ingatkan untuk SELALU BACKUP konfigurasi awal Anda sebelum mengubah konfigurasi. Jadi bila terjadi kegagalan, kita selalu bisa kembali ke setting awal.</p>
<h4 id="penggantian-nama">Penggantian Nama</h4>
<p>Pertama, mari kita ganti dulu beberapa nama pada file konfigurasi di atas agar lebih mudah dimengerti. Yang perlu diganti adalah bagian Monitor dan Device. Saya ganti menjadi seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Section "Device"
Identifier "Intel i915 LCD Output"
Driver "i810"
BusID "PCI:0:2:0"
# Option "ForceBIOS" "1920x1440=1280x768"
EndSection
Section "Monitor"
Identifier "Default LCD"
Option "DPMS"
EndSection
</code></pre></div></div>
<p>Artinya, device di atas mengacu pada output yang menuju LCD screen saya. Nama monitor juga diganti menjadi LCD agar lebih jelas.</p>
<h4 id="menambah-monitor">Menambah Monitor</h4>
<p>Sekarang, kita tambah monitor dan juga port VGA output. Konfigurasinya adalah sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Section "Device"
Identifier "Intel i915 External Output"
Driver "i810"
BusID "PCI:0:2:0"
Option "ForceBIOS" "1920x1440=1280x768"
EndSection
Section "Monitor"
Identifier "External Monitor"
Option "DPMS"
EndSection
</code></pre></div></div>
<p>Karena ada tambahan output, kita perlu menjelaskan pada X.Org mana screen utama kita. Kalau kita tidak lakukan ini, maka bisa saja pada saat booting LCD screen saya blank, karena outputnya dikeluarkan ke monitor CRT yang belum tentu dipasang. Tambahkan nomor screen pada konfigurasi VGA Card seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Section "Device"
Identifier "Intel i915 LCD Output"
Driver "i810"
BusID "PCI:0:2:0"
# Option "ForceBIOS" "1920x1440=1280x768"
Option "MonitorLayout" "CRT,LFP"
Screen 0
EndSection
Section "Device"
Identifier "Intel i915 External Output"
Driver "i810"
BusID "PCI:0:2:0"
Option "ForceBIOS" "1920x1440=1280x768"
Screen 1
EndSection
</code></pre></div></div>
<p>Dari konfigurasi di atas dapat terlihat bahwa screen utama (screen 0) adalah LCD Output. Di atas juga ada tambahan konfigurasi <code class="language-plaintext highlighter-rouge">MonitorLayout</code>. Ini ditambahkan agar secara default kedua output langsung aktif. Biasanya kalau kita pakai laptop, ada kombinasi tombol untuk mengaktifkan VGA Output, misalnya Fn+F5 atau Fn+F3. Nah, dengan opsi ini, VGA output langsung aktif tanpa harus menekan Fn+Something.</p>
<h4 id="konfigurasi-screen">Konfigurasi Screen</h4>
<p>Selanjutnya, kita akan mengkonfigurasi Screen. Screen adalah kombinasi antara Device dan Monitor. Artinya, konfigurasi ini akan menentukan VGA Output mana yang terhubung ke Monitor mana, berikut resolusi tampilannya. Karena tadi kita mengganti nama monitor dan VGA Output, maka kita harus sesuaikan konfigurasi Screen yang sudah ada. Berikut hasilnya.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Section "Screen"
Identifier "Default Screen"
Device "Intel i915 LCD Output"
Monitor "Default LCD"
DefaultDepth 24
SubSection "Display"
Depth 1
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 4
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 8
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 15
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 16
Modes "1280x768"
EndSubSection
SubSection "Display"
Depth 24
Modes "1280x768"
EndSubSection
EndSection
</code></pre></div></div>
<p>dan selanjutnya, ini adalah tambahan untuk screen kedua kita.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Section "Screen"
Identifier "External Screen"
Device "Intel i915 External Output"
Monitor "External Monitor"
DefaultDepth 24
SubSection "Display"
Depth 1
Modes "1024x768" "800x600"
EndSubSection
SubSection "Display"
Depth 4
Modes "1024x768" "800x600"
EndSubSection
SubSection "Display"
Depth 8
Modes "1024x768" "800x600"
EndSubSection
SubSection "Display"
Depth 16
Modes "1024x768" "800x600"
EndSubSection
SubSection "Display"
Depth 16
Modes "1024x768" "800x600"
EndSubSection
SubSection "Display"
Depth 24
Modes "1024x768" "800x600"
EndSubSection
EndSection
</code></pre></div></div>
<p>Kedua screen siap digunakan. Sekarang tinggal satu langkah terakhir.</p>
<h4 id="serverlayout">ServerLayout</h4>
<p>Kita perlu memberitahu X.Org screen mana yang ada di kiri, dan mana yang sebelah kanan. Gunanya agar mouse pointer kita bisa ‘menembus batas’ dengan benar. Misalnya monitor CRT kita pasang di sebelah kiri laptop, maka seharusnya kalau mouse pointer digerakkan ke pinggir kiri LCD, dia akan muncul di pinggir kanan CRT.</p>
<p>Berikut adalah konfigurasi ServerLayout.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Section "ServerLayout"
Identifier "Multihead Layout"
Screen 0 "Default Screen" 0 0
Screen 1 "External Screen" LeftOf "Default Screen"
InputDevice "Generic Keyboard"
InputDevice "Configured Mouse"
InputDevice "stylus" "SendCoreEvents"
InputDevice "cursor" "SendCoreEvents"
InputDevice "eraser" "SendCoreEvents"
InputDevice "Synaptics Touchpad"
# Option "Xinerama" "true"
EndSection
</code></pre></div></div>
<p>Pada konfigurasi di atas, saya menon-aktifkan opsi Xinerama. Kalau opsi ini dijalankan, kita akan memiliki desktop super lebar. Kita bisa drag satu window menembus batas. Tapi kalau opsi ini dimatikan, seolah-olah ada dua desktop yang terpisah, mouse pointer bisa tembus, tapi window aplikasi tidak.</p>
<p>Kalau Anda menonton Swordfish, Stanley Jobson mendapatkan konfigurasi Xinerama. Ini dapat dilihat dari screensaver komet yang berjalan mengelilingi keenam screen yang tersedia. Tergantung kebutuhan, opsi ini bisa dinyalakan atau dimatikan.</p>
<p>Setelah selesai, logout dari desktop. Untuk memastikan, restart X server dengan kombinasi tombol Ctrl+Alt+Backspace. Kalau segalanya berjalan lancar, dual head akan tampil di hadapan kita. Perlu diperhatikan, kadang resolusi layar tidak berjalan sesuai harapan. Kadang kita perlu reboot agar resolusinya benar.</p>
<p>Demikian artikel kali ini, semoga bermanfaat.</p>
Konfigurasi SVN-HTTP di OpenSuSE 10.22007-06-27T21:24:53+07:00https://software.endy.muhardin.com/linux/konfigurasi-svn-http-di-opensuse-102<p>Artikel ini akan menjelaskan tentang konfigurasi Apache dan OpenLDAP di OpenSuSE 10.2 agar Subversion Repository yang kita miliki bisa diakses melalui protokol HTTP.</p>
<p>Pertama, instal software yang dibutuhkan. Jalankan Yast dan instal paket-paket berikut:</p>
<ul>
<li>
<p>subversion</p>
</li>
<li>
<p>apache2</p>
</li>
<li>
<p>subversion-server</p>
</li>
<li>
<p>yast2-http-server</p>
</li>
</ul>
<p>Setelah itu, modifikasi executable Subversion agar repository yang dihasilkan memiliki nilai umask yang tepat. Caranya dapat dilihat <a href="http://endy.artivisi.com/blog/aplikasi/instalasi-subversion/">di sini</a>.</p>
<p>Lalu buat repository untuk percobaan, sebagai contoh saya akan membuat repository di folder <code class="language-plaintext highlighter-rouge">/opt/repository/repo-percobaan</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>svnadmin create --fs-type fsfs /opt/repository/repo-percobaan</code>
</code></pre></div></div>
<p>Selanjutnya, kita akan mengkonfigurasi Apache agar membaca folder tersebut dan memetakannya ke URL /svn. Jadi, bila kita mengakses <code class="language-plaintext highlighter-rouge">http://localhost/svn/repo-percobaan</code> di browser, Apache akan menampilkan isi repository kita.</p>
<p>Caranya, buka Yast, kemudian masuk ke Network Services > HTTP Server.
<a href="/images/uploads/2007/06/yast-http-server.png"><img src="/images/uploads/2007/06/yast-http-server.png" alt="Yast Control Panel " /></a></p>
<p>Ikuti wizard tanpa perubahan sampai layar terakhir. Setelah itu, klik Expert Configuration.</p>
<p><a href="/images/uploads/2007/06/yast-http-server5.png"><img src="/images/uploads/2007/06/yast-http-server5.png" alt="Expert Configuration " /></a></p>
<p>Layar pertama adalah pilihan port yang dilayani Apache. Tambahkan port 443 untuk mengaktifkan SSL.</p>
<p><a href="/images/uploads/2007/06/yast-http-server7.png"><img src="/images/uploads/2007/06/yast-http-server7.png" alt="Port SSL " /></a></p>
<p>Setelah itu, masuk ke tab Server Modules. Aktifkan module <code class="language-plaintext highlighter-rouge">dav</code>, <code class="language-plaintext highlighter-rouge">dav_fs</code>.</p>
<p><a href="/images/uploads/2007/06/yast-http-server8.png"><img src="/images/uploads/2007/06/yast-http-server8.png" alt="Apache Module " /></a></p>
<p>Kita juga butuh modul tambahan untuk Subversion. Klik Add Module, tambahkan modul <code class="language-plaintext highlighter-rouge">dav_svn</code> dan <code class="language-plaintext highlighter-rouge">authz_svn</code>.</p>
<p><a href="/images/uploads/2007/06/yast-http-server9.png"><img src="/images/uploads/2007/06/yast-http-server9.png" alt="Add Subversion Module " /></a></p>
<p>Selesai dengan Yast. Klik OK untuk menyimpan perubahan. Sekarang kita akan mengedit konfigurasi modul Subversion agar membaca folder repository kita.</p>
<p>Buka file /etc/apache2/conf.d/subversion.conf. Di sana sudah disediakan template yang siap diedit sesuai kebutuhan. Untuk tahap pertama ini, ubah isi file tersebut menjadi seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><IfModule mod_dav_svn.c>
<Location /svn>
DAV svn
SVNParentPath /opt/repository
</Location>
</IfModule></code>
</code></pre></div></div>
<p>Server ini akan digunakan untuk beberapa project sekaligus. Untuk setiap repository baru yang dibuat di kemudian hari, tidak perlu setting ulang Apache. Cukup buat folder repository baru di dalam <code class="language-plaintext highlighter-rouge">/opt/repository</code>.</p>
<p>Save file tersebut, dan restart Apache melalui Yast. Repository sudah bisa dibaca. Arahkan browser Anda ke <code class="language-plaintext highlighter-rouge">http://localhost/svn/repo-percobaan</code>. Instalasi yang sukses akan menghasilkan tampilan seperti ini.</p>
<p><a href="/images/uploads/2007/06/yast-http-server11.png"><img src="/images/uploads/2007/06/yast-http-server11.png" alt="Browse Repository Content " /></a></p>
<p>Silahkan baca <a href="http://endy.artivisi.com/blog/aplikasi/otentikasi-apache-menggunakan-ldap/">artikel ini</a> untuk mengaktifkan otentikasi melalui OpenLDAP.</p>
MP3 di Linux2007-06-25T18:57:16+07:00https://software.endy.muhardin.com/linux/mp3-di-linux<p>Distro Linux yang gratis umumnya tidak menyertakan dukungan MP3, Flash, DVD, dan berbagai format file non-open-source yang lainnya. Ini disebabkan karena format tersebut dilarang di beberapa negara, walaupun di Indonesia diperbolehkan. Ini merupakan masalah yang sudah banyak jawabannya. Bahkan beberapa distro populer sudah memudahkan cara instalasinya.</p>
<p>Mari kita lihat dua distro populer, Ubuntu dan OpenSuSE.
Pada Ubuntu Feisty, ada paket yang khusus untuk mengatasi masalah ini, namanya ubuntu-restricted-extras. Cukup buka command prompt, dan ketikkan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install ubuntu-restricted-extras
</code></pre></div></div>
<p>Paket ini ada di repository <code class="language-plaintext highlighter-rouge">multiverse</code>, jadi pastikan komputer Anda memiliki database dari repository tersebut.</p>
<p>Paket ubuntu-restricted-extras ini memungkinkan kita untuk:</p>
<ul>
<li>
<p>Memainkan MP3</p>
</li>
<li>
<p>Memainkan DVD</p>
</li>
<li>
<p>Membuka website ber-Flash</p>
</li>
<li>
<p>Membuka website ber-Java Applet</p>
</li>
<li>
<p>Menggunakan font Arial, Trebuchet, dan font Windows lainnya</p>
</li>
</ul>
<p>Untuk OpenSuSE 10.2, kita harus tambahkan repository packman. Buka Yast, klik Installation Source. Nanti kita akan disuguhi berbagai pilihan protokol. Pilih protokol HTTP.</p>
<p>Selanjutnya, kita akan ditanyai dua hal, nama server repository, dan folder tempat database paket berada. Masukkan ftp.uni-erlangen.de pada kolom nama server, dan /pub/mirrors/packman/suse/10.2 pada nama folder.</p>
<p>Tidak, saya tidak salah ketik, protokolnya memang HTTP dan alamat servernya diawali dengan FTP. Kalau tidak percaya coba saja sendiri, atau lihat <a href="http://amarok.kde.org/wiki/MP3_on_openSUSE_10.2">di sini</a>.</p>
<p>Setelah repository ditambah, Yast akan mendownload database aplikasi yang disediakan, untuk kemudian mengupdate database internalnya. Bila koneksi internet Anda lambat, bersabar sedikit.</p>
<p>Begitu repository selesai ditambah, kita bisa buka menu Software Management, dan Search dengan keyword <code class="language-plaintext highlighter-rouge">libxine1</code>. Ini adalah library yang dibutuhkan agar Amarok bisa memainkan MP3. Amarok adalah aplikasi pemain musik (music player) di KDE.</p>
<p>Instal libxine1, dan Amarok akan segera bisa menyanyikan MP3.</p>
<p>Selamat mencoba.</p>
NTFS Write di OpenSuSE 10.22007-06-22T01:39:23+07:00https://software.endy.muhardin.com/linux/ntfs-write-di-opensuse-102<p>Di Ubuntu, menulis ke partisi NTFS sangat mudah. Cukup lakukan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install ntfs-config
</code></pre></div></div>
<p>dan partisi NTFS siap ditulisi.</p>
<p>Di OpenSuSE 10.2, tidak semudah ini. Secara default, repository OpenSuSE tidak mengandung <code class="language-plaintext highlighter-rouge">ntfs-3g</code>, paket yang dibutuhkan untuk menulis ke NTFS, yang siap digunakan. Oleh karena itu, beberapa orang memilih untuk mengkompilasi sendiri, salah satunya <a href="http://sugandi.blogspot.com/2007/01/how-to-write-to-ntfs-partition-on-linux.html">Oom Andi Sugandi</a>.</p>
<p>Tetapi akhirnya saya menemukan cara yang mudah, tidak melibatkan kompilasi ulang. Berikut caranya.</p>
<h3 id="tambah-repository">Tambah Repository</h3>
<p>Pertama, kita harus menggunakan repository milik Jan Engel***. Masuk ke Yast, dan pilih Installation Source. Kita akan menggunakan protokol FTP. Alamat servernya adalah <code class="language-plaintext highlighter-rouge">ftp-1.gwdg.de</code> dan folder repositorynya adalah <code class="language-plaintext highlighter-rouge">/pub/linux/misc/suser-jengelh/SUSE-10.2/</code>. Silahkan tambahkan entri tersebut dan klik OK. Yast akan segera memperbaharui database aplikasinya.</p>
<h3 id="instalasi-paket">Instalasi Paket</h3>
<p>Selanjutnya, kita bisa langsung menginstal paketnya. Dari Yast, buka Software Management, dan cari dengan keyword <code class="language-plaintext highlighter-rouge">ntfs</code>. Kita akan menemukan paket <code class="language-plaintext highlighter-rouge">ntfs-3g</code>. Instal paket tersebut.
Yast akan meminta paket tambahan, yaitu <code class="language-plaintext highlighter-rouge">fuse</code>. Instal saja dua-duanya.</p>
<h3 id="edit-fstab">Edit fstab</h3>
<p>Sebelum mengubah isi <code class="language-plaintext highlighter-rouge">/etc/fstab</code>, jangan lupa untuk melakukan umount pada partisi windows yang kita miliki.
Agar partisi windows kita dimount secara otomatis pada saat boot, kita perlu mengedit file <code class="language-plaintext highlighter-rouge">/etc/fstab</code>. Temukan entri seperti ini,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/dev/sda1 /media/windows/C ntfs ro,users,gid=users,umask=0002,nls=utf8 0 0
</code></pre></div></div>
<p>dan ubah menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/dev/sda1 /media/windows/C ntfs-3g silent,unmask=0,locale=en_US.utf8 0 0
</code></pre></div></div>
<p>Setelah itu, kita bisa menguji coba dengan perintah <code class="language-plaintext highlighter-rouge">mount -a</code>. Di komputer saya muncul warning sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WARNING: Deficient Linux kernel detected. Some driver features are
not available (swap file on NTFS, boot from NTFS by LILO), and
unmount is not safe unless it's made sure the ntfs-3g process
naturally terminates after calling 'umount'. If you wish this
message to disappear then you should upgrade to at least kernel
version 2.6.20, or request help from your distribution to fix
the kernel problem. The below web page has more information:
http://ntfs-3g.org/support.html#fuse26
</code></pre></div></div>
<p>Tapi warning ini tidak berbahaya.</p>
<h3 id="edit-boot-config">Edit Boot Config</h3>
<p>Selain fstab, ada satu file lagi yang harus diedit agar mounting otomatis berjalan lancar pada saat booting. Edit file <code class="language-plaintext highlighter-rouge">/etc/sysconfig/kernel</code>. Cari baris berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MODULES_LOADED_ON_BOOT=""
</code></pre></div></div>
<p>dan ganti menjadi seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MODULES_LOADED_ON_BOOT="fuse"
</code></pre></div></div>
<p>Partisi NTFS siap digunakan. Selamat mencoba.</p>
Migrasi Linux2007-06-21T18:32:05+07:00https://software.endy.muhardin.com/linux/migrasi-linux<p>Di kantor saya sekarang sedang terjadi euforia Linux. Hmm…. bukan euforia. Tampaknya histeria lebih tepat. Karena semua orang tampak histeris dan ingin sampai di kantor besok harinya dan menemukan semua Windows sudah tidak ada, dan semua orang bekerja normal dan gembira.</p>
<p>Ini semua dipicu oleh razia software bajakan yang marak terjadi beberapa bulan ini. Beberapa perusahaan bahkan menerima surat dari Pak Sutanto yang gambar sampulnya saja sudah menakutkan. Bukannya kantor saya pakai bajakan, tapi dengan sekian ratus orang karyawan, masing-masingnya menggunakan paling tidak satu komputer, siapa yang tau kalau mereka instal ulang komputernya dengan aplikasi GPL (Glodok Public License) ??</p>
<p>Jadilah akhirnya job desc saya bertambah satu, konsultan migrasi Linux, karena kebetulan di komputer dan notebook saya sudah tidak ada Windowsnya lagi. Berikut beberapa pengalaman yang bisa dibagi berkaitan dengan histeria migrasi ini. Siapa tau berikutnya kantor Anda yang dilanda demam pinguin.</p>
<h3 id="siapkan-install-server">Siapkan Install Server</h3>
<p>Satu kali diminta instal, no problem. Dua kali, yah mau gimana lagi. Tiga kali?? That’s enough. Ubuntu memang mudah diinstal, tapi distro lain seperti OpenSuSE tidak senyaman Ubuntu. Untuk OpenSuSE, kita harus menunggui proses instalasi, dan mengganti CD pada waktunya.</p>
<p>Bila Anda mengantisipasi akan menginstal lebih dari tiga komputer, segera deploy fitur instalasi melalui jaringan. Ini adalah investasi beberapa jam yang akan menyelamatkan beberapa puluh jam dari masa remaja Anda di kemudian hari.</p>
<p>Semua distro Linux mendukung instalasi melalui jaringan, walaupun caranya berbeda-beda. Saya menyiapkan repository di jaringan lokal untuk beberapa distro utama, untuk kasus saya, Ubuntu dan OpenSuSE.</p>
<p>Repository Ubuntu dapat dibuat dengan mudah menggunakan DVD Repository yang bisa didonlod di <a href="http://anak.kambing.vlsm.org/">kandang anak kambing</a>. Segera setelah DVD repository selesai didownload, aktifkan webserver Apache dan mount ISOnya ke folder yang dapat diakses orang banyak. Cara yang sama bisa dilakukan untuk distro OpenSuSE.</p>
<p>Sebagai contoh, semua ISO tersebut saya mount ke folder <code class="language-plaintext highlighter-rouge">/home/endy/tmp/UbuntuRepo</code> dan <code class="language-plaintext highlighter-rouge">/home/endy/tmp/OpenSuSE</code>. Kemudian kedua folder tersebut saya publish melalui Apache. Berikut adalah konfigurasi <code class="language-plaintext highlighter-rouge">/etc/fstab</code> agar ISO tersebut dimount pada saat booting.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Ubuntu Repository DVDs
/media/sdb2/ISO/Ubuntu/UbuntuDVD/ubuntu-7.04-repository-i386-1_contrib.iso /home/endy/tmp/UbuntuRepo/DVD1 iso9660 loop,auto 0 0
/media/sdb2/ISO/Ubuntu/UbuntuDVD/ubuntu-7.04-repository-i386-2_contrib.iso /home/endy/tmp/UbuntuRepo/DVD2 iso9660 loop,auto 0 0
/media/sdb2/ISO/Ubuntu/UbuntuDVD/ubuntu-7.04-repository-i386-3_contrib.iso /home/endy/tmp/UbuntuRepo/DVD3 iso9660 loop,auto 0 0
/media/sdb2/ISO/Ubuntu/UbuntuDVD/ubuntu-7.04-repository-i386-4_contrib.iso /home/endy/tmp/UbuntuRepo/DVD4 iso9660 loop,auto 0 0
# OpenSuSE CD
/media/sdb2/ISO/openSUSE-10.2-GM-i386-CD1.iso /home/endy/tmp/OpenSuSE/CD1 iso9660 loop,auto 0 0
/media/sdb2/ISO/openSUSE-10.2-GM-i386-CD2.iso /home/endy/tmp/OpenSuSE/CD2 iso9660 loop,auto 0 0
/media/sdb2/ISO/openSUSE-10.2-GM-i386-CD3.iso /home/endy/tmp/OpenSuSE/CD3 iso9660 loop,auto 0 0
/media/sdb2/ISO/openSUSE-10.2-GM-i386-CD4.iso /home/endy/tmp/OpenSuSE/CD4 iso9660 loop,auto 0 0
/media/sdb2/ISO/openSUSE-10.2-GM-i386-CD5.iso /home/endy/tmp/OpenSuSE/CD5 iso9660 loop,auto 0 0
</code></pre></div></div>
<p>Semakin banyak ISO yang dimount, semakin banyak juga loop device yang dibutuhkan. Instalasi Ubuntu standar cuma menyediakan 8 device loop, sehingga tidak cukup untuk memuat 4 ISO Ubuntu dan 5 ISO OpenSuSE.
Untuk memeriksa berapa device loop yang tersedia, lihat di dalam <code class="language-plaintext highlighter-rouge">/dev/</code></p>
<p>Untuk itu, device loop perlu ditambahkan. Di Ubuntu, tambahkan baris berikut di file <code class="language-plaintext highlighter-rouge">/etc/modprobe.d/options</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>options loop max_loop=32
</code></pre></div></div>
<p>agar pada saat booting kernel menyediakan 32 device loop.</p>
<p>Selanjutnya, kita lakukan mapping agar folder /home/endy/tmp tersebut dipublish di http server. Cara termudah adalah dengan membuat symlink ke folder DocumentRoot (untuk keluarga Debian adalah /var/www).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ln -s /var/www/UbuntuRepo /home/endy/tmp/UbuntuRepo
ln -s /var/www/OpenSuSE /home/endy/tmp/OpenSuSE
</code></pre></div></div>
<p>Berikutnya, buat boot CD atau boot USB agar pada saat instalasi kita cukup mengarahkan ke jaringan. Saya biasanya memulai instalasi pada sore hari agar bisa ditinggal pulang.</p>
<h3 id="migrasi-email">Migrasi Email</h3>
<p>Arsip email merupakan harta yang sangat penting di kehidupan perkantoran modern. Dia bisa digunakan sebagai amunisi audit, bahan berkelit, ataupun mencari referensi. Oleh karena itu, setelah migrasi biasanya user akan menanyakan, “Email lama saya mana??”.</p>
<p>Email dari Outlook Express bukanlah masalah besar. Hampir semua mail client Linux seperti KMail, Thunderbird, dan Evolution sudah menyediakan fitur import. Yang paling bermasalah adalah Outlook 2003. Native import belum ada di semua mail client Linux pada saat tulisan ini dibuat. Jadi, kita harus lebih dalam menggali isi perut Google.</p>
<p>Saya menemukan tiga metode yang bisa digunakan:</p>
<ol>
<li>
<p>Menggunakan Thunderbird</p>
</li>
<li>
<p>Menggunakan File Sharing</p>
</li>
<li>
<p>Menggunakan IMAP Server</p>
</li>
</ol>
<p>Cara mana yang dipilih tergantung dari ketersediaan ahli Linux di sekitar Anda. Kalau tidak ada ahli Linux sama sekali, gunakan cara pertama. Berikut adalah langkah-langkahnya.</p>
<h4 id="kebutuhan-software">Kebutuhan Software</h4>
<ul>
<li>
<p>MS Windows. Ya Anda membutuhkan komputer yang masih ada Windowsnya</p>
</li>
<li>
<p>Mozilla Thunderbird untuk Windows</p>
</li>
<li>
<p>Linux. Kalau Anda sudah menjadi korban migrasi, harusnya komputer Anda sudah terinstal Linux pada saat ini.</p>
</li>
<li>
<p>Mozilla Thunderbird untuk Linux</p>
</li>
</ul>
<h4 id="cara-migrasi">Cara migrasi</h4>
<p>Cara memindahkan email sangat mudah. Pembaca yang teliti tentunya sudah bisa menebak langkah-langkahnya hanya dengan melihat kebutuhan software di atas.</p>
<ol>
<li>
<p>Buka Thunderbird di Windows</p>
</li>
<li>
<p>Import file *.pst Anda</p>
</li>
<li>
<p>Buka setting Thunderbird, cari tahu di mana dia menyimpan emailnya</p>
</li>
<li>
<p>Copy folder tersebut ke komputer Linux Anda</p>
</li>
<li>
<p>Jalankan Thunderbird Linux, arahkan folder penyimpanan email ke folder hasil copy</p>
</li>
<li>
<p>Restart Thunderbird</p>
</li>
<li>
<p>Setelah ini, Anda bisa import hasilnya ke Evolution atau KMail, sesuai dengan pilihan</p>
</li>
</ol>
<p>Cara kedua melibatkan pembuatan folder sharing di suatu tempat. Pada dasarnya cara ini adalah otomasi dari cara pertama di atas, sehingga bisa digunakan untuk memigrasi banyak user sekaligus. Saya kurang merekomendasikan cara ini karena relatif sulit dan tidak intuitif. Bagi yang ingin tahu bisa langsung <a href="http://www.neotek.hu/en/o2e_en.html">lihat di websitenya</a>.</p>
<p>Buat saya, instalasi Thunderbird berkali-kali kurang menyenangkan. Lagipula, terkesan kurang hi-tech. Berikut adalah cara ketiga. Untuk melakukan ini, Anda butuh ahli Linux yang mampu mengkonfigurasi email server berprotokol IMAP. Sebenarnya IMAP server ini bisa di mana saja, termasuk di Windows. Yang penting IMAP. Cara setting IMAP server tidak dibahas pada artikel ini.</p>
<p>Berikut adalah langkah-langkah migrasi.</p>
<ol>
<li>
<p>Sebelum komputer Windows diformat, jalankan dulu Outlook 2003 untuk terakhir kalinya</p>
</li>
<li>
<p>Buat akun email baru di Outlook, menggunakan akun yang sudah dibuatkan di IMAP server</p>
</li>
<li>
<p>Copy email dari Local Folder Outlook ke folder IMAP</p>
</li>
<li>
<p>Silahkan format Windowsnya dan install Linux</p>
</li>
<li>
<p>Setelah berada di Linux, jalankan mail client apapun yang Anda sukai</p>
</li>
<li>
<p>Buat akun email menggunakan akun di IMAP server</p>
</li>
<li>
<p>Email Anda akan hadir tanpa suatu cacat apapun. Silahkan pindahkan ke folder lokal</p>
</li>
</ol>
<p>Saya sangat menyukai cara ketiga ini. Bisa memigrasi email orang banyak dengan singkat, dan user bisa diajari untuk melakukannya sendiri.</p>
<p>Demikianlah beberapa tips singkat tentang migrasi Linux. Semoga bermanfaat.</p>
vnStat2007-04-19T17:50:52+07:00https://software.endy.muhardin.com/aplikasi/vnstat<p>Sekarang sedang marak layanan internet berbasis paket. Artinya, kita dikenakan biaya berdasarkan besar data yang kita konsumsi, baik kirim (uplink) maupun terima (downlink).</p>
<p>Perbedaan cara penagihan tentunya juga mempengaruhi perilaku berinternet kita. Dengan layanan berbasis waktu, kita dapat membatasi pengeluaran dengan cara membatasi durasi berinternet. Nah, kalau berbasis paket, bagaimana cara mengendalikan pemakaian?</p>
<p>Berbagai aplikasi open source dapat digunakan untuk memonitor pemakaian internet, salah satunya adalah <a href="http://humdi.net/vnstat/">vnStat</a>. Aplikasi kecil ini sangat mudah digunakan. Cukup instal, inisialisasi databasenya, dan lihat laporannya.</p>
<h3 id="instalasi-vnstat">Instalasi vnStat</h3>
<p>Instalasi, seperti biasa, sangat mudah. Di Ubuntu, cukup dengan satu baris.
<code class="language-plaintext highlighter-rouge">sudo apt-get install vnstat</code></p>
<p>Setelah terinstal, kita harus menginisialisasi database tempat data monitoring diletakkan. Di komputer saya, ada dua antarmuka jaringan, WiFi (eth1) dan kabel (eth2). Nantinya bila menggunakan modem ADSL, biasanya saya akan menggunakan antarmuka kabel (eth2) yang terhubung ke modem ADSL.</p>
<p>Untuk menginisialisasi database, lakukan perintah berikut, sesuai dengan nama antarmuka yang akan dimonitor.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>sudo vnstat -u -i eth1
sudo vnstat -u -i eth2</code>
</code></pre></div></div>
<p>vnStat akan mengeluarkan pesan error tentang file database yang tidak ditemukan. Biarkan saja, karena filenya belum ada.</p>
<p>Setelah itu, vnStat sudah bekerja di belakang layar. Untuk melihat pemakaian internet, kita harus menginstal satu aplikasi lagi, yaitu <a href="http://www.sqweek.com/sqweek/index.php?p=1">vnStat Front End</a>. Aplikasi ini kerjanya membaca database yang dihasilkan vnStat dan menampilkannya di web. Karena berbasis PHP, seharusnya tidak sulit untuk diinstal.</p>
<h3 id="instalasi-vnstat-frontend">Instalasi vnStat FrontEnd</h3>
<p>Instalasinya tidak sulit. Cukup download dari websitenya, dan extract di folder yang dikenali oleh Apache HTTPD. Di komputer saya, saya gunakan folder <code class="language-plaintext highlighter-rouge">/opt/lampp/htdocs/vnstat</code>.</p>
<p>Edit file config.php sehingga menjadi seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>
// list of network interfaces monitored by vnStat
$iface_list = array('eth1', 'eth2');
$iface_title['eth1'] = 'Wireless';
$iface_title['eth2'] = 'Wired';
</code>
</code></pre></div></div>
<p>Selanjutnya, browse ke http://localhost/vnstat. Kita akan mendapati data penggunaan jaringan kita seperti gambar di bawah.
<a href="/images/uploads/2007/04/vnstat.png"><img src="/images/uploads/2007/04/vnstat.png" alt="vnStat Front End Screenshot " /></a></p>
<p>Selamat mencoba</p>
Lowongan Programmer2007-04-18T23:31:01+07:00https://software.endy.muhardin.com/java/lowongan-programmer<p>Di tempat saya bekerja saat ini (BaliCamp), saya sedang membutuhkan anggota tim. Tidak butuh yang terlalu canggih, saya justru cari fresh graduate. Nantinya kandidat yang lolos akan ditempatkan di tim saya, mengembangkan aplikasi untuk mengelola software development process.</p>
<p>Ada banyak nilai tambah yang bisa diperoleh dari pekerjaan ini, selain tentunya gaji dan fasilitas standar seperti layaknya karyawan lainnya. Biar saya jelaskan sedikit.</p>
<p>Pertama, yang akan kita buat di sini adalah aplikasi manajemen proyek yang spesifik untuk software development. Artinya, kita nanti akan melihat secara langsung bagaimana proyek software dikelola dengan mengikuti kaidah <a href="http://www.sei.cmu.edu/cmmi/">CMMI</a>. Pengetahuan yang selama ini hanya dipelajari (sambil ngantuk) di kelas tentang RUP, XP, Waterfall, dsb akan dilihat langsung implementasinya di lapangan. Dan dibuatkan softwarenya. Jadi, dengan membuat aplikasi ini, kita jadi memahami bagaimana best-practices dalam pengelolaan proyek.</p>
<p>Kedua, kita akan bekerja dengan menggunakan <a href="http://www.springframework.org">Spring Framework</a> dan <a href="http://www.hibernate.org">Hibernate</a>. Ini adalah dua framework utama dalam dunia Java. Anggota tim (yang nota bene adalah fresh graduate) akan mengikuti training tentang kedua teknologi ini. Saya sendiri yang akan memberikan trainingnya.</p>
<p>Ketiga, dalam proses pembuatan aplikasi ini, kita akan menggunakan teknologi tercanggih yang tersedia di dunia open source. Kita akan <a href="http://endy.artivisi.com/blog/lain/presentasi-subversion/">menyimpan source-code dan dokumentasi proyek dalam Subversion</a>. Semua best-practices yang saya tulis di <a href="http://endy.artivisi.com/blog/lain/pesan-buku-subversion/">buku Subversion</a> akan diimplementasikan dalam proyek. Issue atau bug akan dikelola dengan bug tracker. Testing akan dilakukan di segala lini, sesuai dengan artikel yang pernah saya tulis di <a href="http://endy.artivisi.com/blog/java/ruthless-testing-1/">sini</a>, <a href="http://endy.artivisi.com/blog/java/ruthless-testing-2/">sini</a>, <a href="http://endy.artivisi.com/blog/java/ruthless-testing-3/">sini</a>, dan <a href="http://endy.artivisi.com/blog/java/ruthless-testing-4/">sini</a>. Secara harian, <a href="http://cruisecontrol.sourceforge.net/">CruiseControl</a> akan mengambil semua source code terbaru dan melakukan daily build. Singkatnya, <a href="http://endy.artivisi.com/blog/manajemen/starter-kit/">semua persenjataan proyek</a> akan kita gunakan secara optimal.</p>
<p>Jangan khawatir kalau merasa asing dengan berbagai teknologi tersebut. Akan ada sesi training khusus untuk itu. Lagi-lagi saya yang akan memberikan trainingnya.</p>
<p>Baiklah, sekarang apa persyaratannya?</p>
<p>Pelanggan blog ini tentunya sudah tahu kalau saya <a href="http://endy.artivisi.com/blog/life/pengetahuan-wajib-buat-programmer/">sangat pemilih dalam menentukan anggota tim</a>. Jadi, persiapkan diri dengan baik.</p>
<p>Tidak seperti lowongan lainnya yang biasa Anda lihat di milis atau portal tenaga kerja, saya tidak mempermasalahkan latar belakang pendidikan. SMU ke atas boleh daftar. <a href="http://endy.artivisi.com/blog/life/ipk-tiarap/">IPK Anda Tiarap?? </a> Jangan khawatir, silahkan layangkan lamaran Anda. Tidak akan didiskriminasi.</p>
<p>Persyaratan yang wajib dimiliki oleh peminat adalah:</p>
<ol>
<li>
<p>Konsep dasar OOP</p>
</li>
<li>
<p>Dasar-dasar Java</p>
</li>
<li>
<p>Pernah menggunakan minimal 2 (dua) bahasa pemrograman</p>
</li>
<li>
<p>Konsep struktur data, misalnya tree, list</p>
</li>
<li>
<p>Konsep relasional termasuk join dan subquery</p>
</li>
<li>
<p>Mengerti SQL untuk minimal satu produk database</p>
</li>
<li>
<p>HTML dan CSS</p>
</li>
<li>
<p>Dasar-dasar jaringan komputer</p>
</li>
<li>
<p>Bahasa Inggris (minimal membaca)</p>
</li>
<li>
<p>Pernah membuat minimal satu aplikasi utuh (dari tampilan depan sampai ke database, lengkap dengan validasi) dengan minimal 4 (empat) tabel yang berelasi.</p>
</li>
</ol>
<p>Seperti layaknya lowongan pekerjaan yang lain, ada beberapa hal yang masuk kategori <em>lebih diutamakan bila menguasai</em> atau dalam istilah programmer, <em>nice to have</em>. Berikut daftarnya:</p>
<ul>
<li>
<p>Bisa menulis Ant buildfile</p>
</li>
<li>
<p>Memiliki personal website (blog)</p>
</li>
<li>
<p>Sehari-hari menggunakan Linux</p>
</li>
</ul>
<p>Lokasi kerja saat ini di Menara Dea, Kawasan Mega Kuningan, 10 menit berjalan kaki dari Mal Ambassador. Ada <strong>sedikit</strong> kemungkinan tim akan pindah ke Desa Sigma di German Centre, Bumi Serpong Damai.</p>
<p>Berminat?? Silahkan klik Compose New Mail di aplikasi email Anda, dan attach resume dalam format PDF. Kemudian kirimkan ke endy [at] artivisi [dot] com atau emuhardin [at] balicamp [dot] com.</p>
<p>Bila resume Anda meyakinkan, saya akan panggil untuk interview. Siapkan aplikasi yang pernah dibuat dalam CD, paket sedemikian rupa sehingga mudah diinstal. Sertakan semua file yang dibutuhkan. Nantinya saya akan sediakan PC kosong yang hanya berisi sistem operasi agar Anda bisa mendemokan aplikasi tersebut.</p>
<p>Kalau ada pertanyaan, silahkan langsung posting komentar di bawah tulisan ini.</p>
Remoting dengan Spring2007-04-17T00:49:19+07:00https://software.endy.muhardin.com/java/remoting-dengan-spring<p>Aplikasi yang kita buat, tidak hanya digunakan oleh manusia. Ada kalanya aplikasi yang kita buat akan diakses oleh aplikasi orang lain. Jadi, user atau pengguna aplikasi kita bukanlah orang, melainkan mesin. Dalam hal ini, aplikasi kita dapat disebut dengan aplikasi server yang menyediakan service bagi aplikasi client.</p>
<p>Bagaimana caranya agar aplikasi kita dapat digunakan oleh aplikasi lain? Caranya tentunya dengan menyediakan antarmuka yang sesuai bagi masing-masing aplikasi client. Ada banyak bahasa yang dijadikan patokan dalam komunikasi antar aplikasi. Beberapa protokol yang umum digunakan adalah:</p>
<ul>
<li>
<p>CORBA</p>
</li>
<li>
<p>RMI (khusus Java)</p>
</li>
<li>
<p>SOAP</p>
</li>
<li>
<p>XML-RPC</p>
</li>
<li>
<p>Hessian</p>
</li>
<li>
<p>Burlap</p>
</li>
<li>
<p>dan masih banyak yang lainnya</p>
</li>
</ul>
<p>Dengan cara tradisional, menyediakan antarmuka untuk masing-masing protokol ini cukup rumit. Selain harus mengerti protokolnya, kita juga harus menulis banyak kode untuk memformat data kita mengikuti aturan yang ditetapkan protokol tersebut. Kita juga harus menulis kode program agar dapat menerima data dalam protokol yang kita ingin sediakan.</p>
<p><a href="http://www.springframework.org">Spring Framework</a> menyediakan library pembantu bagi kita agar kita dapat menyediakan akses dalam protokol tersebut dengan mudah. Bagaimana caranya, mari kita bahas dalam artikel ini.</p>
<p>Sebagai contoh sederhana, misalnya kita ingin menyediakan layanan untuk mengambil data Person. Untuk menyederhakan masalah, Person hanya mengandung data nama dan email saja.</p>
<h3 id="personjava">Person.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.remoting;
public class Person implements java.io.Serializable {
private String name;
private String email;
public Person() {
this("", "");
}
public Person(String name, String email){
this.name = name;
this.email = email;
}
public String getName(){
return name;
}
public String getEmail(){
return email;
}
public String toString(){
return name + " : " + email;
}
}
</code></pre></div></div>
<p>Perhatikan bahwa kita harus mendefinisikan Person sebagai object yang Serializable. Ini penting agar object ini bisa ditransfer melalui jaringan.</p>
<p>Kemudian, kita sediakan interface yang mendefinisikan layanan yang ingin kita sediakan bagi aplikasi lain. Kita sebut saja interface ini sebagai PersonService.</p>
<h3 id="personservicejava">PersonService.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.remoting;
import java.util.List;
public interface PersonService {
public Person get(int id);
public List getAllPerson();
}
</code></pre></div></div>
<p>PersonService ini nantinya bisa kita implementasi sesuai dengan kebutuhan. Kita bisa mengambil datanya dari database, dari phonebook handphone, atau bahkan dari aplikasi lain. Agar contoh kodenya lebih sederhana, kita buat saja implementasinya dengan ArrayList. Para pembaca nanti bisa mencoba dengan implementasi lain yang lebih hi-tech seperti Hibernate atau OpenJPA.</p>
<p>Berikut adalah implementasi dari PersonService, kita sebut saja PersonServiceImpl.</p>
<h3 id="personserviceimpljava">PersonServiceImpl.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.remoting.remote;
import java.util.List;
import java.util.ArrayList;
import tutorial.remoting.Person;
import tutorial.remoting.PersonService;
public class PersonServiceImpl implements PersonService {
private List allPerson = new ArrayList();
public PersonServiceImpl(){
Person p = new Person("Endy Muhardin", "endy@artivisi.com");
Person p1 = new Person("Maya Mujawati", "moedja@yahoo.com");
allPerson.add(p);
allPerson.add(p1);
}
public Person get(int id){
if (id >= allPerson.size()) return null;
return (Person) allPerson.get(id);
}
public List getAllPerson(){
return allPerson;
}
}
</code></pre></div></div>
<p>Seperti kita lihat di atas, tidak ada yang istimewa dengan kode program di atas. Bahkan validasi untuk field id pun tidak kita sediakan. Tentu saja kalau kita berikan masukan angka 2 atau lebih pada method public Person get(int id) akan terjadi ArrayIndexOutOfBoundException.</p>
<p>Selesai sudah … sekarang kode tersebut dapat diakses oleh aplikasi lain.</p>
<p>Apa?? Saya mendengar ada suara-suara kurang puas dari penonton.. Coba lebih keras??</p>
<blockquote>
<p>Bagaimana cara mengaksesnya dari aplikasi client? Nampaknya belum ada kode program apa-apa untuk menyediakan layanan webservice. Mana bisa dibilang selesai?</p>
</blockquote>
<p>Ok .. baiklah, memang ada satu langkah lagi. Yaitu mempublikasikan interface tersebut agar bisa diakses orang banyak. Kita akan sediakan akses melalui tiga protokol, yaitu:</p>
<ol>
<li>
<p>RMI</p>
</li>
<li>
<p>Hessian</p>
</li>
<li>
<p>Burlap</p>
</li>
</ol>
<blockquote>
<p>Tiga protokol?? Apa tidak terlalu ambisius? Nanti saya pusing baca kode sekian banyak.</p>
</blockquote>
<p>Demikian terdengar komentar dari penonton. Hmm … penonton Indonesia memang terlalu banyak protes.</p>
<p>Tenang saja. Kita akan gunakan Spring untuk mempublikasikan aplikasi kecil kita ini. Saya akan buktikan bahwa kodenya sedikit dan mudah dipahami. Sekarang, mari kita mulai dengan mempublikasikan protokol RMI. Di masa lalu, untuk menyediakan layanan ini, paling tidak kita harus:</p>
<ol>
<li>
<p>mengkompilasi stub</p>
</li>
<li>
<p>mengkompilasi skeleton</p>
</li>
<li>
<p>mengkonfigurasi Security setting</p>
</li>
<li>
<p>menjalankan RMIRegistry</p>
</li>
<li>
<p>mendaftarkan objek PersonServiceImpl ke RMIRegistry</p>
</li>
<li>
<p>memberikan stub ke aplikasi client</p>
</li>
</ol>
<p>Sekarang tidak lagi. Kita cukup mendaftarkan interface PersonService, implementasi PersonServiceImpl di konfigurasi Spring. Berikut adalah isi file remote-ctx.xml</p>
<h3 id="remote-ctxxml">remote-ctx.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="personServiceTarget" class="tutorial.remoting.remote.PersonServiceImpl"/>
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="PersonRmiService"/>
<property name="service" ref="personServiceTarget"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
<property name="registryPort" value="4321"/>
</bean>
</beans>
</code></pre></div></div>
<p>Hanya dengan membaca file XML di atas, kita sudah bisa menduga apa yang terjadi. Kita mempublikasikan layanan yang didefinisikan pada interface PersonService pada port 4321 dengan nama layanan PersonRmiService. Di sisi server, layanan ini akan disediakan oleh objek personServiceTarget yang merupakan instan dari class PersonServiceImpl. Kita memanfaatkan class RmiServiceExporter yang dimiliki Spring.</p>
<p>Untuk menjalankan aplikasi kecil kita, buat sebuah class sederhana untuk mengaktifkan Spring Framework. Berikut adalah kode programnya.</p>
<h3 id="personrmiservicejava">PersonRmiService.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.remoting.server;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class PersonRmiService {
public static void main(String[] xx) {
ApplicationContext ctx =
new FileSystemXmlApplicationContext("webapp/WEB-INF/remote-ctx.xml");
}
}
</code></pre></div></div>
<p>Silahkan jalankan class di atas. Asal classpath sudah disetting dengan benar, kita siap mengaksesnya dari client.
Kita juga akan menggunakan bantuan dari Spring Framework untuk menemukan dan menginisialisasi layanan RMI yang berada di port 4321. Berikut adalah konfigurasi Spring di sisi client.</p>
<h3 id="client-ctxxml">client-ctx.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="personServiceRmi" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:4321/PersonRmiService"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
</bean>
</beans>
</code></pre></div></div>
<p>Berikut adalah kode program untuk mengakses RMI service kita.</p>
<h3 id="mainjava">Main.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.remoting.client;
import java.util.List;
import tutorial.remoting.PersonService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Main {
public static void main(String[] args){
// initialize application context
ApplicationContext ctx = new FileSystemXmlApplicationContext("client-ctx.xml");
// accessing RMI service
System.out.println("======= Accessing RMI Service ==========");
PersonService ps1 = (PersonService) ctx.getBean("personServiceRmi");
List all1 = ps1.getAllPerson();
for(int i=0; i<all1.size(); i++) {
System.out.println(all1.get(i));
}
}
</code></pre></div></div>
<p>Perhatikan konsol, dan saksikan RMI service kita sudah dieksekusi dengan sukses.</p>
<p>Sekarang, kita publikasikan melalui protokol <a href="http://www.caucho.com/resin-3.0/protocols/hessian.xtp">Hessian</a> dan <a href="http://www.caucho.com/resin-3.0/protocols/burlap.xtp">Burlap</a>. Protokol ini adalah hasil karya Caucho untuk mempublikasikan service di atas protokol HTTP. Hessian menggunakan format binary, sedangkan Burlap menggunakan format XML.</p>
<p>Karena berbasis HTTP, maka kita perlu menggunakan HTTP Server yang mendukung Java (khususnya Servlet). Kita bisa gunakan Tomcat atau Jetty. Yang jelas, kita perlu mendeklarasikan servlet untuk melayani client. Deklarasi dibuat di dalam file web.xml sebagai berikut.</p>
<h3 id="webxml">web.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/remote-ctx.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>10</session-timeout>
</session-config>
</web-app>
</code></pre></div></div>
<p>Kita membutuhkan satu file konfigurasi untuk mengkonfigurasi layanan Hessian dan Burlap. Berikut adalah isi file remoting-servlet.xml.</p>
<h3 id="remoting-servletxml">remoting-servlet.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="/PersonHessianService" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="personServiceTarget"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
</bean>
<bean name="/PersonBurlapService" class="org.springframework.remoting.caucho.BurlapServiceExporter">
<property name="service" ref="personServiceTarget"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
</bean>
<bean name="/PersonHttpInvokerService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="personServiceTarget"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
</bean>
</beans>
</code></pre></div></div>
<p>Letakkan file remoting-servlet.xml ini di sebelah web.xml. Kita juga perlu mengikutkan file remote-ctx.xml yang kita buat untuk RMI di atas, karena deklarasi objek PersonServiceImpl ada di sana. Bila kita tidak ingin mengikutkan file ini, kita bisa memindahkan deklarasi objek tersebut ke dalam file remoting-servlet.xml.</p>
<p>Sekarang kita bisa mengkompilasi dan mendeploy aplikasi webnya. Untuk mengaksesnya, ubah sedikit kode program di aplikasi client menjadi sebagai berikut.</p>
<h3 id="mainjava-1">Main.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.remoting.client;
import java.util.List;
import tutorial.remoting.PersonService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Main {
public static void main(String[] args){
// initialize application context
ApplicationContext ctx = new FileSystemXmlApplicationContext("client-ctx.xml");
// accessing RMI service
System.out.println("======= Accessing RMI Service ==========");
PersonService ps1 = (PersonService) ctx.getBean("personServiceRmi");
List all1 = ps1.getAllPerson();
for(int i=0; i<all1.size(); i++) {
System.out.println(all1.get(i));
}
// accessing Hessian Service
System.out.println("======= Accessing Hessian Service ==========");
PersonService ps2 = (PersonService) ctx.getBean("personServiceHessian");
List all2 = ps2.getAllPerson();
for(int i=0; i<all2.size(); i++) {
System.out.println(all2.get(i));
}
// accessing Burlap Service
System.out.println("======= Accessing Burlap Service ==========");
PersonService ps3 = (PersonService) ctx.getBean("personServiceBurlap");
List all3 = ps3.getAllPerson();
for(int i=0; i<all3.size(); i++) {
System.out.println(all3.get(i));
}
// accessing Http Invoker Service
System.out.println("======= Accessing Http Invoker Service ==========");
PersonService ps4 = (PersonService) ctx.getBean("personServiceHttpInvoker");
List all4 = ps4.getAllPerson();
for(int i=0; i<all4.size(); i++) {
System.out.println(all4.get(i));
}
}
}
</code></pre></div></div>
<p>Demikian juga dengan konfigurasi Spring di sisi client. Kita perlu menambahkan beberapa objek tambahan untuk mengakses layanan di berbagai protokol tersebut. Berikut adalah isi lengkap dari client-ctx.xml.</p>
<h3 id="client-ctxxml-1">client-ctx.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="personServiceRmi" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:4321/PersonRmiService"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
</bean>
<bean id="personServiceHessian" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:9090/remoting/PersonHessianService"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
</bean>
<bean id="personServiceBurlap" class="org.springframework.remoting.caucho.BurlapProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:9090/remoting/PersonBurlapService"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
</bean>
<bean id="personServiceHttpInvoker" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:9090/remoting/PersonHttpInvokerService"/>
<property name="serviceInterface" value="tutorial.remoting.PersonService"/>
</bean>
</beans>
</code></pre></div></div>
<p>Aplikasi kecil kita sudah selesai. Setelah mendeploy aplikasi tersebut ke Servlet Container (Tomcat atau Jetty), kita dapat mengaksesnya melalui aplikasi client. Dengan tetap menggunakan remote-ctx.xml tanpa perubahan, selain menyediakan protokol Hessian dan Burlap, protokol RMI akan tetap tersedia.</p>
<h3 id="bonus">Bonus</h3>
<p>Pembaca yang teliti akan menemukan satu protokol tambahan pada contoh di atas, yaitu HttpInvoker. Ini adalah protokol khusus yang disediakan oleh Spring.</p>
<p>Protokol Hessian dan Burlap tidak mendukung struktur objek secara sempurna. Dia akan mengalami kesulitan apabila objek yang kita kirim tidak sesederhana Person, misalnya memiliki List yang berisi objek lain. Agar bisa mengirim struktur objek yang sempurna, kita harus menggunakan RMI.</p>
<p>Tetapi sayangnya, protokol RMI kurang lazim. Dia membutuhkan port tersendiri yang mungkin saja diblok oleh administrator jaringan, sehingga tidak sefleksibel HTTP.</p>
<p>SpringHttpInvoker menjembatani keterbatasan ini. Kita bisa mengirim objek kompleks secara utuh di atas protokol HTTP dengan menggunakan SpringHttpInvoker.</p>
<p>Demikian … semoga bermanfaat.</p>
Nyambi, bolehkah??2007-04-03T00:52:44+07:00https://software.endy.muhardin.com/life/nyambi-bolehkah<p>Posting ini saya tulis karena di milis JUG sedang rame membahas tentang boleh tidaknya karyawan nyambi. Sepanjang karir saya, urusan nyambi telah menjadi bagian hidup tak terpisahkan dari perkembangan pribadi saya. Jadi baiklah mari kita bahas sedikit tentang urusan nyambi ini.</p>
<blockquote>
<p>Disclaimer : Pernyataan di bawah ini merupakan opini pribadi dan bukan merupakan sikap resmi perusahaan yang saat ini mempekerjakan saya.</p>
</blockquote>
<p>Mari pertama kita bedakan antara masalah legal dan masalah etis.</p>
<p>Yang menurut saya tidak legal (haram) :</p>
<ul>
<li>
<p>Nyambi di jam kantor (ngerjain project lain2 di kantor)</p>
</li>
<li>
<p>Pakai resource kantor untuk project lain (telp, internet, komputer, dsb), <strong>tanpa sepengetahuan atasan</strong></p>
</li>
</ul>
<p>Yang menurut saya legal :</p>
<ul>
<li>Pagi datang ontime, sore tenggo, sampe rumah ngerjain project lain.</li>
</ul>
<p>Nah, kalo urusan etis, ini rada sulit juga, soalnya urusan kepantasan ini tergantung pendidikan yang diberikan orangtua masing-masing.</p>
<p>Yang menurut saya kurang etis:</p>
<ul>
<li>
<p>Berebut pasar dengan kantor. Misal: kantornya terima project dengan kisaran nilai 100-500 juta, eh kita juga terima project dengan nilai 150jt. Lebih tidak etis lagi kalo ikut tendernya saingan.</p>
</li>
<li>
<p>Ngerjain project sampingan sampe begadang, akibatnya pas ngantor ngantuk dan gak konsen.</p>
</li>
<li>
<p>Yang ini dari sisi kantor. Mempekerjakan karyawan lembur sampai nginap, tanpa uang lembur. Padahal kalo si karyawan pulang, dia bisa dapat tambahan uang dari waktu miliknya.</p>
</li>
</ul>
<p>Yang menurut saya etis:</p>
<ul>
<li>
<p>Mengerjakan produk/project yang sama, tapi beda segmen. Misalnya kantor terima project Java 100-500 jt. Kalo dibawah itu dia gak terima karena gak nutup biaya. Nah, kita terima project 10 jt, yang kalo dikasi ke kantor pun gak diterima. It’s fine … asal tidak ilegal.</p>
</li>
<li>
<p>Mengkomersilkan sisi lain teknologi, misalnya di kantor coding, di luar ngasi training atau nulis buku.</p>
</li>
</ul>
<p>Sebenarnya kalo kita tanpa ada unsur paranoid, karyawan nyambi ada juga untungnya bagi perusahaan.</p>
<p>Misalnya, di kampus tempat saya ngajar dulu, dosen itu dianjurkan untuk nulis buku, walaupun dikerjakan menggunakan resource kantor (waktu, komputer, internet). Saya tanya kenapa? Teman saya sesama dosen bilang,</p>
<blockquote>
<p>kalo ada buku yang ditulis dosen, maka itu bisa meningkatkan image kampusnya.</p>
</blockquote>
<p>Demikian juga apabila kita sering nulis blog, aktif di milis, banyak ngarang buku, sering masuk e-lifestyle, dsb. Perusahaan yang berpikiran maju akan dapat melihat manfaat dari ini, misalnya menggunakan resume karyawan tersebut untuk tender, atau diiklankan pada waktu pitching project.</p>
<p>Nah, menurut saya, selain yang legal/ilegal, tidak ada hitam putih di sini.
Yang paling penting adalah saling keterbukaan dan kesepakatan.</p>
<p>Saya sendiri selama ini berterus terang pada perusahaan tentang kegiatan komersil lain yang saya lakukan. Bila ada konflik kepentingan ya kita rundingkan.
Bila setelah dirundingkan tidak ada titik temu, ya mungkin sudah saatnya berpisah untuk menjalani hidup masing-masing.</p>
<p>Untungnya selama ini saya tidak mengalami masalah dengan keterbukaan ini. Pada perusahaan sebelumnya, bahkan saya bisa nyambi ngajar di kampus di jam kantor, dengan dispensasi khusus.</p>
<p>Jadi, jangan dulu pesimis bahwa nyambi itu haram. Coba saja diskusikan dengan kantor Anda. Siapa tau boleh, dengan kompromi tertentu :D.</p>
<p>Demikian pendapat saya …</p>
Intro JSF2007-03-31T00:05:58+07:00https://software.endy.muhardin.com/java/intro-jsf<p>Pada <a href="http://endy.artivisi.com/blog/java/intro-icefaces/">postingan kemarin</a>, kita telah melihat bagaimana membuat aplikasi web dengan ICEFaces, yang notabene dijalankan di atas framework JSF. Tapi JSF sendiri itu apa?</p>
<p>Java Server Faces (JSF) adalah salah satu teknologi terbaru dalam pengembangan aplikasi web. Teknologi ini distandarisasi oleh Sun sehingga dukungan terhadapnya akan disediakan para vendor server.</p>
<p>Sebenarnya apa itu JSF? Berdasarkan <a href="http://java.sun.com/j2ee/1.4/docs/tutorial/doc/JSFIntro3.html">penjelasan dari Sun</a>, JSF terdiri dari:</p>
<ul>
<li>
<p>Kumpulan komponen UI (tentunya berbasis web)</p>
</li>
<li>
<p>Pengaturan flow navigasi</p>
</li>
<li>
<p>Mekanisme event handling (seperti layaknya aplikasi desktop)</p>
</li>
<li>
<p>Halaman web</p>
</li>
<li>
<p>Server side objects</p>
</li>
</ul>
<p>Karena sifatnya yang merupakan spesifikasi resmi dari Sun, di masa depan akan banyak vendor yang menyediakan dukungan terhadap framework ini, sehingga kita bisa mengharapkan adanya:</p>
<ul>
<li>
<p>Visual Editor untuk halaman web</p>
</li>
<li>
<p>Visual Editor untuk mengatur aliran navigasi</p>
</li>
<li>
<p>Komponen siap pakai yang sudah AJAX-enabled</p>
</li>
</ul>
<p>Saat tulisan ini dibuat, sepertinya editor visual yang terbaik untuk JSF masih dipegang Netbeans. Sayangnya berdasarkan pengalaman sekilas saya, editor visual ini sangat ‘menjajah’. Agar kita bisa mengedit halaman web dan navigasi secara visual, kita harus banyak <em>extends</em> class Netbeans.</p>
<p>Mudah-mudahan di masa depan dukungan tools akan semakin banyak, terutama dari Eclipse atau IDEA.</p>
<p>Baiklah, para pembaca tentunya sudah tidak sabar ingin melihat, seperti apa kodenya. Pada artikel ini, kita hanya akan membahas tentang:</p>
<ol>
<li>
<p>konfigurasi awal</p>
</li>
<li>
<p>membuat dua halaman, dan</p>
</li>
<li>
<p>menghubungkan keduanya dengan konfigurasi navigasi</p>
</li>
</ol>
<p>Kita akan menggunakan <a href="https://javaserverfaces.dev.java.net/">implementasi JSF yang disediakan Sun</a>, saat artikel ini ditulis, rilis terbaru adalah 1.2.</p>
<p>Selain hasil karya Sun, kita juga bisa menggunakan <a href="http://myfaces.apache.org">JSF terbitan Apache</a>.</p>
<p>Mari langsung coding.</p>
<p>Kita akan hanya akan membuat dua halaman:</p>
<ol>
<li>
<p>Registrasi User</p>
</li>
<li>
<p>Konfirmasi Registrasi</p>
</li>
</ol>
<p>tanpa business logic sama sekali. Halaman pertama berisi form registrasi, yang bila di-klik tombol Register-nya, user akan diarahkan ke halaman kedua. Untuk validasi dan akses database akan dibahas dalam artikel terpisah.</p>
<p>Halaman pertama tampak seperti ini.</p>
<p><a href="/images/uploads/2007/03/jsf-register.png"><img src="/images/uploads/2007/03/jsf-register.png" alt="Halaman Registrasi " /></a></p>
<p>Sedangkan halaman kedua tampak seperti ini.</p>
<p><a href="/images/uploads/2007/03/jsf-confirm.png"><img src="/images/uploads/2007/03/jsf-confirm.png" alt="Halaman Konfirmasi " /></a></p>
<p>Kode untuk halaman pertama adalah sebagai berikut.</p>
<h3 id="registerjsp">register.jsp</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>:: Pendaftaran User ::</title>
</head>
<body>
<h1>Silahkan masukkan data Anda di sini</h1>
<h:form>
<table border="0">
<tbody>
<tr>
<td>Nama Lengkap</td>
<td><h:inputText value="name" /></td>
</tr>
<tr>
<td>Username</td>
<td><h:inputText value="name" /></td>
</tr>
<tr>
<td>Email</td>
<td><h:inputText value="name" /></td>
</tr>
<tr>
<td>Password</td>
<td><h:inputSecret value="password"/></td>
</tr>
<tr>
<td>Password (ulangi)</td>
<td><h:inputSecret value="password2"/></td>
</tr>
<tr>
<td> </td>
<td><h:commandButton type="submit" value="Daftar" action="register"/></td>
</tr>
</tbody>
</table>
</h:form>
</body>
</html>
</code>
</code></pre></div></div>
<p>Perhatikan kode program untuk menampilkan tombol. Di sana ada atribut <code class="language-plaintext highlighter-rouge">action</code> yang bernilai <code class="language-plaintext highlighter-rouge">register</code>. Atribut <code class="language-plaintext highlighter-rouge">action</code> ini nantinya akan digunakan di konfigurasi navigasi di bawah.</p>
<p>Sedangkan halaman kedua isinya HTML biasa, sebagai berikut.</p>
<h3 id="confirmationjsp">confirmation.jsp</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>:: Konfirmasi Pendaftaran ::</title>
</head>
<body>
<h1>Registrasi Berhasil !!</h1>
</body>
</html>
</code>
</code></pre></div></div>
<p>Kedua halaman tersebut akan diakses dengan URL :</p>
<ol>
<li>
<p>http://localhost:8080/HelloJSF/register.jsf</p>
</li>
<li>
<p>http://localhost:8080/HelloJSF/confirmation.jsf</p>
</li>
</ol>
<p>Perhatikan bahwa kita mengakses halaman tersebut dengan URL *.jsf, padahal nama file sebenarnya adalah *.jsp.</p>
<p>Jadi, yang terjadi di sini adalah, kita menyerahkan pemrosesan semua URL berakhiran jsf kepada servlet JSF.</p>
<p>Oleh karena itu, kita perlu mendefinisikan Servlet JSF dan URL Mapping tersebut pada <code class="language-plaintext highlighter-rouge">web.xml</code>.</p>
<h3 id="webxml">web.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>com.sun.faces.verifyObjects</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
</code>
</code></pre></div></div>
<p>Isi <code class="language-plaintext highlighter-rouge">web.xml</code> sudah dijelaskan pada <a href="http://endy.artivisi.com/blog/java/intro-icefaces/">artikel sebelumnya tentang ICEFaces</a>.</p>
<p>Selanjutnya, mari kita lihat konfigurasi navigasinya, yang menyatakan:</p>
<blockquote>
<p>bila tombol Daftar ditekan, bukalah halaman <code class="language-plaintext highlighter-rouge">confirmation.jsp</code></p>
</blockquote>
<p>Konfigurasi tersebut ditulis di dalam file <code class="language-plaintext highlighter-rouge">faces-config.xml</code> sebagai berikut.</p>
<h3 id="faces-configxml">faces-config.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<navigation-rule>
<from-view-id>/register.jsp</from-view-id>
<navigation-case>
<from-outcome>register</from-outcome>
<to-view-id>/confirmation.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
</code>
</code></pre></div></div>
<p>Pada konfigurasi di atas, kita mendefinisikan view bernama <code class="language-plaintext highlighter-rouge">register.jsp</code>, yaitu halaman registrasi kita. Kemudian ada navigation case, yang merupakan aturan navigasi untuk halaman <code class="language-plaintext highlighter-rouge">register.jsp</code> tersebut. Bila dihasilkan action <code class="language-plaintext highlighter-rouge">register</code>, tampilkan halaman <code class="language-plaintext highlighter-rouge">confirmation.jsp</code>.</p>
<p>Action register ini didapat dari nilai atribut action pada komponen tombol Daftar di dalam file <code class="language-plaintext highlighter-rouge">register.jsp</code>.</p>
<p>Di masa yang akan datang, nantinya konfigurasi navigasi ini akan diedit dengan visual editor. Sehingga kita bisa langsung drag-and-drop halaman, kemudian menggambar panah dari halaman <code class="language-plaintext highlighter-rouge">register.jsp</code> ke <code class="language-plaintext highlighter-rouge">confirmation.jsp</code> dengan tulisan ‘register’.</p>
<p>Aplikasi HelloJSF siap dideploy. Struktur folder lengkapnya tampak seperti ini.</p>
<p><a href="/images/uploads/2007/03/hellojsf-folder.png"><img src="/images/uploads/2007/03/hellojsf-folder.png" alt="Struktur Folder Aplikasi HelloJSF " /></a></p>
<p>Pembaca yang teliti akan segera bertanya,</p>
<blockquote>
<p>Apa tidak kurang? Mana *.jar nya? Tidak ada *.jar apa-apa di sana!</p>
</blockquote>
<p>Itu karena saya mendeploy ke Sun Application Server yang terbundle bersama Netbeans Enterprise Pack. Kalau saja kita deploy ke Tomcat atau Jetty, tentu lain ceritanya. Kita harus melihat dokumentasi masing-masing merek server.</p>
<p>Demikian perkenalan kita dengan JSF. Lain waktu kita akan sambung dengan kemampuan JSF yang lainnya.</p>
Intro ICEFaces2007-03-29T18:50:14+07:00https://software.endy.muhardin.com/java/intro-icefaces<p>Beberapa waktu terakhir ini, framework Java Server Faces terlihat semakin matang. Orang-orang sudah mulai menggunakan, memberi feedback, dan menemukan best-practices cara penggunaannya. Oleh karena itu, saya pikir sudah tiba waktunya untuk saya mempelajari framework tersebut.</p>
<p>Berdasarkan pengalaman rekan <a href="http://www.jroller.com/page/sfranklyn/">Samuel</a>, dia menganjurkan penggunaan JSF dengan menggunakan stack:</p>
<ul>
<li>
<p><a href="https://facelets.dev.java.net">Facelets</a></p>
</li>
<li>
<p><a href="http://www.icefaces.org">IceFaces</a></p>
</li>
<li>
<p><a href="http://www.springframework.org">Spring Framework</a></p>
</li>
<li>
<p><a href="http://www.hibernate.org">Hibernate</a></p>
</li>
</ul>
<p>Spring dan Hibernate sudah sering saya gunakan. Integrasinya dengan JSF juga tidak terlalu sulit. Hanya butuh registrasi bean di dalam konfigurasi JSF.</p>
<p>Facelets adalah template engine untuk JSF. Dalam dunia JSF, template engine sering disebut juga dengan istilah View Renderer. Sebenarnya View Renderer default JSF adalah JSP, tapi saya tidak suka JSP. Jadilah akhirnya kita pakai JSF dengan Facelets sebagai ViewRenderer-nya.
Facelets ini mirip dengan Freemarker atau Velocity. Kalau di dunia PHP kira-kira padanannya adalah Smarty.</p>
<p>Keuntungan menggunakan Facelets dibanding JSP adalah dengan Facelets, desain tampilan dapat dikerjakan oleh web designer. JSF dengan JSP akan banyak sekali menggunakan taglib yang tidak akan muncul di Macromedia Dreamweaver atau editor HTML lainnya. Facelets menggunakan pendekatan yang mirip dengan Tapestry, yaitu memasukkan komponen ke dalam elemen HTML biasa, sehingga dapat dilihat secara normal oleh editor HTML.</p>
<p>ICEFaces adalah kumpulan komponen JSF. Dia memiliki teknologi Direct to DOM Rendering sehingga kita dapat membuat tampilan ber-AJAX dengan mudah.</p>
<p>Seperti halnya teknologi lainnya di dunia Java, JSF hanyalah berupa spesifikasi. Kita dapat menggunakan implementasi yang dikeluarkan Sun (sering disebut dengan Reference Implementation - RI), atau menggunakan implementasi orang lain. Selain Sun, Apache mengeluarkan MyFaces dan Oracle mengeluarkan ADF sebagai implementasi JSF.</p>
<p>Pada artikel ini, kita akan lihat Hello World dengan menggunakan JSF, Facelets, dan ICEFaces. Karena ini hanya perkenalan saja, requirementnya tidak rumit. Saya hanya ingin membuktikan kecanggihan teknologi Direct to DOM Rendering yang dimiliki ICEFaces. Konon teknologi ini memungkinkan kita mengupdate tampilan di sisi client dari server dengan mudah.</p>
<p>Aplikasi yang ingin kita buat sangat sederhana, tapi sering menjadi pertanyaan di berbagai milis pemrograman web. Kita akan membuat satu halaman yang menampilkan waktu server. Waktu server ini harus berjalan secara real-time.</p>
<p>Dalam bahasa pemrograman lain, menampilkan jam server secara real time di browser bukanlah suatu hal yang mudah.
Programmer harus memilih satu di antara beberapa teknik berikut:</p>
<ul>
<li>
<p>Menggunakan meta refresh untuk request ke server setiap beberapa detik</p>
</li>
<li>
<p>Mengambil jam server sekali saja di awal loading page, kemudian increment nilainya di sisi client dengan JavaScript</p>
</li>
<li>
<p>Menggunakan streaming HTML</p>
</li>
<li>
<p>Menggunakan AJAX Push</p>
</li>
</ul>
<p>Atau berbagai teknik lainnya. Yang jelas tidak mudah untuk melakukan hal ini.</p>
<p>Dengan menggunakan JSF + ICEFaces, hal ini menjadi mudah. ICEFaces adalah salah satu implementasi JSF yang menggunakan teknologi Direct to DOM Rendering. Artinya, server bisa seenaknya mengubah HTML yang ada di sisi client (browser). Dengan teknik ini, menampilkan jam server secara real time menjadi mudah.</p>
<p>Berikut adalah langkah-langkahnya:</p>
<ol>
<li>
<p>Buat halaman HTML yang akan menampilkan waktu server</p>
</li>
<li>
<p>Buat kode Java yang bertugas menyediakan waktu server</p>
</li>
<li>
<p>Gunakan JSF + ICEFaces untuk menghubungkan keduanya</p>
</li>
<li>
<p>Aktifkan partial rendering agar tidak menimbulkan flicker</p>
</li>
</ol>
<p>Mari kita bahas satu persatu.</p>
<h2 id="halaman-html">Halaman HTML</h2>
<p>Halaman ini adalah halaman dinamis yang berisi nilai yang dikeluarkan oleh server. Bedakan dengan halaman dinamis yang dikendalikan oleh JavaScript, di mana pada kasus tersebut tidak dibutuhkan sisi server yang menyediakan data.</p>
<p>Kode tampilan ini sederhana saja. Dibuat dalam format XHTML, karena begitulah keinginan Facelets.</p>
<h3 id="whattimeisitxhtml">whattimeisit.xhtml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><!-- <?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>
<title>:: Menampilkan Jam Server ::</title>
</head>
<body>
<p>
<b>Waktu Server Saat Ini : #{serverClock.currentTime}</b>
</p>
</body>
</html>
</code>
</code></pre></div></div>
<p>Perhatikan bahwa satu-satunya dynamic code di atas adalah <code class="language-plaintext highlighter-rouge">#{serverClock.currentTime}</code>. Sisanya HTML biasa. Bagi yang belum tahu, dynamic code tersebut ditulis dalam Expression Language (EL) yang didukung oleh JSF. Kalau di dalam Java, kode tersebut sama dengan
<code class="language-plaintext highlighter-rouge">serverClock.getCurrentTime()</code>.
Sekarang mari kita lihat kode Java yang membekingi template whattimeisit, yaitu ServerClock.java.</p>
<h3 id="serverclockjava">ServerClock.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>package tutorial.icefaces;
import com.icesoft.faces.async.render.IntervalRenderer;
import com.icesoft.faces.async.render.RenderManager;
import com.icesoft.faces.async.render.Renderable;
import com.icesoft.faces.webapp.xmlhttp.PersistentFacesState;
import com.icesoft.faces.webapp.xmlhttp.RenderingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class ServerClock implements Renderable {
private DateFormat formatter;
private PersistentFacesState state;
private IntervalRenderer renderer;
private static final int renderInterval = 1000; // render setiap 1 detik
public ServerClock() {
formatter = new SimpleDateFormat("EEE, HH:mm:ss");
state = PersistentFacesState.getInstance();
}
public String getCurrentTime() {
return formatter.format(Calendar.getInstance().getTime());
}
public PersistentFacesState getState() {
return state;
}
public void renderingException(RenderingException renderingException) {
if(renderer != null) {
renderer.remove(this);
renderer = null;
}
}
public void setRenderManager(RenderManager manager) {
renderer = manager.getIntervalRenderer("whattimeisit");
renderer.setInterval(renderInterval);
renderer.add(this);
renderer.requestRender();
}
}
</code>
</code></pre></div></div>
<p>Constructor dan method getCurrentTime tidak terlalu istimewa. Isinya hanya inisialisasi DateFormat dan kegiatan memformat waktu saat ini menjadi String.</p>
<p>Selain constructor, setter, dan getter, kode lainnya berkaitan dengan pengelolaan internalnya ICEFaces. Dalam method setRenderManager, kita menyuruh ICEFaces untuk mengupdate browser setiap 1 detik. RenderManager adalah object yang bertugas merender tampilan. Dia beroperasi ke sekumpulan Renderable, yang salah satunya adalah class kita ServerClock.</p>
<p>Bila kita mengimplementasikan Renderable, ada dua method yang harus kita sediakan,
yaitu renderingException dan getState. Method ini dengan mudah dapat kita buat.
Selain kedua method ini, ICEFaces tidak butuh tambahan kode lagi.</p>
<p>Saatnya kita siapkan konfigurasi. JSF membutuhkan dua konfigurasi, yaitu web.xml (seperti aplikasi web biasa) dan faces-config.xml. Mari kita lihat web.xml sedikit demi sedikit.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code> <context-param>
<param-name>com.sun.faces.verifyObjects</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value>
</context-param>
</code>
</code></pre></div></div>
<p>Kode di atas adalah konfigurasi untuk JSF. Konfigurasi ini berlaku umum, terlepas dari kita menggunakan Facelets dan ICEFaces ataupun library lainnya. Selanjutnya adalah konfigurasi Facelets.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code> <context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>true</param-value>
</context-param>
</code>
</code></pre></div></div>
<p>Pada konfigurasi di atas, kita mengganti default ekstensi file template, yang tadinya jsp atau jspx menjadi xhtml. Kita juga memberitahu Facelets bahwa kita sedang dalam tahap development. Dengan demikian, pesan error yang dikeluarkan akan lebih lengkap sehingga memudahkan debugging.</p>
<p>Selanjutnya, mari kita lihat konfigurasi ICEFaces.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code> <context-param>
<param-name>com.icesoft.faces.uploadDirectory</param-name>
<param-value>upload</param-value>
</context-param>
<context-param>
<param-name>com.icesoft.faces.uploadMaxFileSize</param-name>
<param-value>4048576</param-value>
</context-param>
<context-param>
<param-name>com.icesoft.faces.concurrentDOMViews</param-name>
<param-value>true</param-value>
</context-param>
</code>
</code></pre></div></div>
<p>Dua konfigurasi pertama sudah cukup jelas, mengatur tentang di mana harus meletakkan file hasil upload dan berapa maksimal ukuran file yang dapat diterima. Konfigurasi ketiga menyuruh ICEFaces berhati-hati agar dapat melayani request berbarengan ke satu halaman yang sama.</p>
<p>Setelah selesai dengan konfigurasi context param, mari kita masuk ke bagian servlet. JSF membutuhkan satu servlet yang harus dijalankan pada saat aplikasi diaktifkan.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code> <servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
</code>
</code></pre></div></div>
<p>Servlet ini tidak perlu dimapping ke URL, karena nantinya kita akan menggunakan servlet ICEFaces.</p>
<p>ICEFaces membutuhkan beberapa servlet, terlihat pada konfigurasi berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code> <servlet>
<servlet-name>Persistent Faces Servlet</servlet-name>
<servlet-class>com.icesoft.faces.webapp.xmlhttp.PersistentFacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Blocking Servlet</servlet-name>
<servlet-class>com.icesoft.faces.webapp.xmlhttp.BlockingServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>uploadServlet</servlet-name>
<servlet-class>com.icesoft.faces.component.inputfile.FileUploadServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
</code>
</code></pre></div></div>
<p>Selain itu, ICEFaces juga membutuhkan satu listener, dideklarasikan <strong>diatas</strong> deklarasi servlet.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code> <listener>
<listener-class>com.icesoft.faces.util.event.servlet.ContextEventRepeater</listener-class>
</listener>
</code>
</code></pre></div></div>
<p>Servlet ICEFaces dimapping sebagai berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code> <servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>/xmlhttp/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>*.iface</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Blocking Servlet</servlet-name>
<url-pattern>/block/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>uploadServlet</servlet-name>
<url-pattern>/uploadHtml</url-pattern>
</servlet-mapping>
</code>
</code></pre></div></div>
<p>Terakhir untuk web.xml, kita perlu mendefinisikan session timeout.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code> <session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</code>
</code></pre></div></div>
<p>Hasil akhirnya adalah seperti ini.</p>
<h3 id="webxml">web.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>com.sun.faces.verifyObjects</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value>
</context-param>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.icesoft.faces.uploadDirectory</param-name>
<param-value>upload</param-value>
</context-param>
<context-param>
<param-name>com.icesoft.faces.uploadMaxFileSize</param-name>
<param-value>4048576</param-value>
</context-param>
<context-param>
<param-name>com.icesoft.faces.concurrentDOMViews</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>com.icesoft.faces.util.event.servlet.ContextEventRepeater</listener-class>
</listener>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Persistent Faces Servlet</servlet-name>
<servlet-class>com.icesoft.faces.webapp.xmlhttp.PersistentFacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Blocking Servlet</servlet-name>
<servlet-class>com.icesoft.faces.webapp.xmlhttp.BlockingServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>uploadServlet</servlet-name>
<servlet-class>com.icesoft.faces.component.inputfile.FileUploadServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>/xmlhttp/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>*.iface</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Blocking Servlet</servlet-name>
<url-pattern>/block/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>uploadServlet</servlet-name>
<url-pattern>/uploadHtml</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
</code>
</code></pre></div></div>
<p>Selanjutnya, mari kita buat konfigurasi JSF. File konfigurasinya bernama faces-config.xml. Ini diletakkan di dalam folder WEB-INF, di sebelah web.xml. Berikut adalah kodenya.</p>
<h3 id="faces-configxml">faces-config.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<application>
<view-handler>com.icesoft.faces.facelets.D2DFaceletViewHandler</view-handler>
</application>
<managed-bean>
<managed-bean-name>renderManager</managed-bean-name>
<managed-bean-class>com.icesoft.faces.async.render.RenderManager</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>serverClock</managed-bean-name>
<managed-bean-class>tutorial.icefaces.ServerClock</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>renderManager</property-name>
<value>#{renderManager}</value>
</managed-property>
</managed-bean>
</faces-config>
</code>
</code></pre></div></div>
<p>Pada blok paling atas, kita mengkonfigurasi JSF agar menggunakan View Renderer Facelets yang sudah dilengkapi dengan ICEFaces. Di blok kedua, kita mendeklarasikan bean renderManager yang dibutuhkan oleh object kita serverClock pada blok ketiga.</p>
<p>Bagi yang sudah biasa menggunakan Spring, konfigurasi ini tidak jauh berbeda dengan konfigurasi Dependency Injection Spring. Nantinya bila kita menggunakan Spring, kita juga bisa mendeklarasikan Spring bean kita di dalam faces-config.xml tersebut.</p>
<p>Terakhir, jangan lupa lengkapi jar yang dibutuhkan agar kode kita bisa dikompilasi dan dideploy dengan mulus. Kombinasi jar yang dibutuhkan relatif bervariasi tergantung application server yang kita gunakan. Kadangkala ada jar yang sudah disediakan oleh application server tertentu, sehingga kita tidak perlu lagi menyediakan. Sebagai contoh, bila dideploy di Sun Application Server (Glassfish) yang terbundle bersama Netbeans 5.5, daftar jar yang dibutuhkan adalah:</p>
<ul>
<li>
<p>backport-util-concurrent.jar</p>
</li>
<li>
<p>commons-collections.jar</p>
</li>
<li>
<p>commons-digester.jar</p>
</li>
<li>
<p>commons-fileupload.jar</p>
</li>
<li>
<p>commons-logging-1.0.4.jar</p>
</li>
<li>
<p>el-api.jar</p>
</li>
<li>
<p>icefaces-comps.jar</p>
</li>
<li>
<p>icefaces-facelets.jar</p>
</li>
<li>
<p>icefaces.jar</p>
</li>
<li>
<p>krysalis-jCharts-1.0.0-alpha-1.jar</p>
</li>
<li>
<p>xercesImpl.jar</p>
</li>
<li>
<p>xml-apis.jar</p>
</li>
</ul>
<p>Struktur folder akhir dari aplikasi ini adalah sebagai berikut.</p>
<p><a href="/images/uploads/2007/03/folder-jsf.png"><img src="/images/uploads/2007/03/folder-jsf.png" alt="Struktur Folder Aplikasi JSF " /></a></p>
<p>Untuk Eclipse, daftar pustaka yang dibutuhkan sedikit berbeda. Demikian gambarnya.
<a href="/images/uploads/2007/07/ice-eclipse-lib.png"><img src="/images/uploads/2007/07/ice-eclipse-lib.png" alt="Daftar pustaka ICEFaces untuk Eclipse " /></a></p>
<p>Setelah semua file dibuat dan dipaket sesuai dengan aturan aplikasi web Java, kita bisa mendeploynya ke application server. Lalu, silahkan mengakses http://localhost:8080/HelloICE/whattimeisit.iface atau URL lain sesuai setting deployment.</p>
<p>Hasilnya akan tampak seperti ini.</p>
<p><a href="/images/uploads/2007/03/result-jsf.png"><img src="/images/uploads/2007/03/result-jsf.png" alt="Tampilan Akhir Server Time " /></a></p>
<p>Namun demikian, ternyata halaman tersebut masih melakukan refresh setiap satu detik sehingga mengganggu pandangan. Oleh karena itu, mari kita aktifkan fitur partial submit yang dimiliki ICEFaces. Dengan fitur ini, halaman HTML akan diupdate secara parsial sehingga tidak perlu me-reload seluruh halaman.</p>
<p>Buka file whattimeisit.xhtml dan cari baris berikut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><body>
<p>
<b>Waktu Server Saat Ini : #{serverClock.currentTime}</b>
</p>
</body>
</code>
</code></pre></div></div>
<p>Ubah menjadi seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><body>
<p>
<ice:form partialSubmit="true">
<b>Waktu Server Saat Ini : #{serverClock.currentTime}</b>
</ice:form>
</p>
</body>
</code>
</code></pre></div></div>
<p>Jangan lupa untuk mendeklarasikan komponen <ice:form> di awal halaman, dengan menggunakan tag <f:view> sebagai berikut.</f:view></ice:form></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><f:view xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ice="http://www.icesoft.com/icefaces/component"></code>
</code></pre></div></div>
<p>Keseluruhan file whattimeisit.xhtml akan terlihat seperti ini.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code><?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">
<f:view xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ice="http://www.icesoft.com/icefaces/component">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>What time is it</title>
</head>
<body>
<ice:form>
<p>
<b>Waktu Server Saat Ini : #{serverClock.currentTime}</b>
</p>
</ice:form>
</body>
</html>
</f:view></code>
</code></pre></div></div>
<p>Redeploy aplikasi, dan lihat hasilnya. Tidak ada lagi flicker (kedipan).</p>
<p>Eclipse project untuk contoh di atas dapat didownload <a href="http://endy.artivisi.com/downloads/code/ICEStarter-20070726.tar.bz2">di sini</a>.</p>
<p>Selamat mencoba, semoga bermanfaat.</p>
Virtual Box2007-03-05T17:46:56+07:00https://software.endy.muhardin.com/aplikasi/virtual-box<p>Oom Sindu beberapa hari ini berisik sekali membahas <a href="http://www.virtualbox.org">Virtual Box</a>. <a href="http://sindu.web.id/blog/2007/02/21/virtualbox-ubuntu-edgy/">Sudah</a> <a href="http://sindu.web.id/blog/2007/02/22/winxp-on-ubuntuvirtualbox/">beberapa</a> <a href="http://sindu.web.id/blog/2007/02/22/static-ip-in-virtualbox/">artikel</a> dia tulis, sehingga akhirnya saya tertarik untuk mencoba.</p>
<p><a href="http://www.virtualbox.org">Virtual Box</a> adalah aplikasi virtualization. Gunanya untuk menjalankan OS (Operating System) di dalam OS. Misalnya komputer kita berisi Linux, kemudian kita ingin mencoba berbagai ‘distro’ Windows seperti Vista atau 2003 server. Agar kita tidak repot, gunakan aplikasi virtualization sehingga kita bisa menjalankan OS lain seperti layaknya menjalankan aplikasi biasa. Dalam satu virtual machine, kita bisa menjalankan banyak OS lain.</p>
<p>Saya adalah penggemar berat aplikasi virtualization. Alasannya, saya menggunakan Linux sebagai OS utama, tapi masih harus menggunakan Windows untuk membuat dokumen untuk orang lain. Selain itu, saya juga butuh beberapa versi browser untuk mengetes aplikasi web yang saya buat.</p>
<p>Aplikasi virtualization juga sangat bermanfaat untuk project software development. Kita bisa buat satu virtual machine untuk menghosting Subversion repository server, bugtracker, aplikasi project management, dan build server. Backupnya juga mudah. Cukup copy file virtual machine tersebut ke DVD. Biasanya saya gunakan VM berukuran 8 GB, sehingga butuh 2 DVD sekali backup. When disaster strike, cukup copy file backup, server langsung up kembali tanpa butuh waktu lama untuk instal dan konfigurasi.</p>
<p>Dalam dunia virtualization, dikenal istilah host OS (OS tuan rumah) dan guest OS (OS tamu). Host OS adalah sistem operasi utama kita, sedangkan guest OS adalah OS yang berjalan di atas virtual machine.</p>
<p>Sebelum ini, saya sudah mencoba beberapa aplikasi lain, seperti <a href="http://www.vmware.com/">VMWare</a>, <a href="http://fabrice.bellard.free.fr/qemu/">Qemu</a>, dan <a href="http://www.xensource.com/">Xen</a>. Notebook saya memiliki spesifikasi Centrino 1.8GHz dan 512 (shared) RAM. Hasilnya, Xen tidak berhasil diinstal dengan pesan error ‘something about unsupported kernel and processor’. <a href="http://www.vmware.com/products/free_virtualization.html">VMWare Server (gratis)</a> dan Qemu (open source) berhasil diinstal dengan mulus. Pada waktu dijalankan, Qemu sedikit lebih ringan daripada VMWare. Dengan VMWare, pada saat guest OS Windows 2000 saya nyalakan, guest OS berjalan seperti keong racun abis kena garam. Sangat pelan. Padahal belum ada aplikasi yang dijalankan di guest OS. Praktis guest OS berikut host OS (Ubuntu Dapper) tidak dapat digunakan, karena ke-lemot-annya sudah mencapai taraf unusable.</p>
<p>Dengan Qemu, guest OS berjalan lambat, tapi host OS masih bisa digunakan dengan nyaman.</p>
<p>Ok, sekarang kita bahas virtualbox. Dari sisi instalasi, tidak ada masalah berarti. Ada beberapa petunjuk instalasi di <a href="http://ubuntulinux.or.id/blog/?p=271">blog Ubuntu Indonesia</a> dan <a href="http://www.sindu.web.id">blog Oom Sindu</a> yang bisa diikuti.</p>
<p>Khusus untuk saya, setelah langkah-langkah pada tutorial di atas dilakukan, virtualbox masih belum jalan.
Saya harus menjalankan perintah berikut</p>
<p><code class="language-plaintext highlighter-rouge">$ sudo make -C /opt/VirtualBox-1.3.6/src install</code></p>
<p>karena ada sedikit masalah dengan kernel module.</p>
<p>Setelah itu, Virtual Box dapat dijalankan dengan sempurna.</p>
<p>Setelah saya instal Windows 2000 Professional (tahu diri gak berani install Vista dengan spec pas-pasan ;p), saya sangat terkesan. Guest OS berjalan dengan mulus dan cukup responsif tanpa mengganggu Host OS. Saat mengetik artikel ini, saya sedang menginstal Service Pack 4 di Guest OS. Di Host OS, saya sedang mendengarkan <a href="http://www.davidmaister.com/podcasts/">podcast David Maister</a>, browsing Firefox dengan 6 tab terbuka, 2 diantaranya GMail dan Google Calendar -dua aplikasi web yang terkenal rakus memori karena terlalu ‘ajax’.</p>
<p>Hebatnya, tidak patah sedikitpun. Baik guest OS dan host OS dapat dijalankan dengan nyaman.
Good luck trying the same using VMWare. Hmm .. saya jadi teringat iklan DVD player di TV yang diperankan Basuki.</p>
<p>Bravo Virtual Box. Best things in the world available for free. :D</p>
Membuat aggregator dengan MagpieRSS2007-02-08T23:54:54+07:00https://software.endy.muhardin.com/php/aggregator-magpierss<p>Pada artikel ini, kita akan belajar cara menambahkan feed aggregator di website kita. Untuk mudahnya, kita gunakan pustaka <a href="magpierss.sourceforge.net">Magpie RSS</a>. Dengan library ini, kita bisa membuat website yang berisi rangkuman dari website-website lain (web-based aggregator).</p>
<p>Penjelasan tentang apa itu RSS dapat dilihat di <a href="http://endy.artivisi.com/blog/lain/apa-itu-rss/">artikel ini</a>.</p>
<h2 id="cara-penggunaan-magpie-rss">Cara penggunaan Magpie RSS</h2>
<p>Setelah donlod dan extract, copy empat file magpie:</p>
<ul>
<li>
<p>rss_cache.inc</p>
</li>
<li>
<p>rss_fetch.inc</p>
</li>
<li>
<p>rss_parse.inc</p>
</li>
<li>
<p>rss_utils.inc</p>
</li>
</ul>
<p>Berikut file dependensinya yang ada di folder extlib.</p>
<p>Selanjutnya, untuk mengolah RSS feed, hanya dibutuhkan tiga baris kode:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">require_once</span><span class="p">(</span><span class="s1">'inc/rss_fetch.inc'</span><span class="p">);</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="s2">"http://www.php.net/news.rss"</span><span class="p">;</span>
<span class="nv">$rss</span> <span class="o">=</span> <span class="nf">fetch_rss</span><span class="p">(</span><span class="nv">$url</span><span class="p">);</span>
</code></pre></div></div>
<p>Variabel $rss tinggal dilooping dan ditampilkan sesuai keinginan. Sebagai contoh, saya tampilkan isi feed dalam tabel.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><table</span> <span class="na">border=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><tr><th></span>No<span class="nt"></th><th></span>Judul<span class="nt"></th><th></span>Ringkasan<span class="nt"></th></tr></span>
<span class="cp"><? $i=0; foreach ($rss->items as $item) { $i++; ?></span>
<span class="nt"><tr></span>
<span class="nt"><td></span><span class="cp"><?=$i ?></span><span class="nt"></td></span>
<span class="nt"><td><a</span> <span class="na">href=</span><span class="s">"<? echo($item[link]); ?>"</span><span class="nt">></span><span class="cp"><? echo($item[title]); ?></span><span class="nt"></a></td></span>
<span class="nt"><td></span><span class="cp"><? echo($item[description]); ?></span><span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="cp"><? } ?></span>
<span class="nt"></table></span>
</code></pre></div></div>
<p>Hasilnya dapat dilihat <a href="http://endy.artivisi.com/downloads/code/magpie-rss/magpie.php">di sini</a>. Sedangkan source codenya dapat didownload <a href="http://endy.artivisi.com/downloads/code/magpie-rss.zip">di sini</a>.</p>
<p>Demikian … cukup mudah bukan?
Selamat menampilkan rangkuman website orang di website anda sendiri. Jangan lupa memperhatikan etika dan hak cipta.</p>
Apa itu RSS ?2007-02-08T23:43:52+07:00https://software.endy.muhardin.com/aplikasi/apa-itu-rss<p>Sudah tau RSS?</p>
<p>Jika Anda bukan programmer, wajar jika tidak tahu. Tapi jika Anda programmer … jangan sampai tidak tahu. Apalagi bilang,</p>
<blockquote>
<p>RSS itu kan sama dengan CSS …</p>
</blockquote>
<p>RSS itu banyak kepanjangannya, beberapa diantaranya antara lain:</p>
<ul>
<li>
<p>Really Simple Syndication</p>
</li>
<li>
<p>Rich Site Summary</p>
</li>
<li>
<p>RDF Site Summary</p>
</li>
</ul>
<p>RSS berisi rangkuman dari isi suatu website. Misalnya kita mengunjungi website yang berisi banyak artikel. Kita bisa mengambil data RSSnya, yang berisi :</p>
<ul>
<li>
<p>Judul Artikel</p>
</li>
<li>
<p>Tanggal Publish</p>
</li>
<li>
<p>Pengarang</p>
</li>
<li>
<p>Potongan isinya</p>
</li>
</ul>
<p>Sebagai contoh, misalnya kita ingin selalu mengikuti perkembangan terbaru di dunia PHP. Cukup masukkan URL http://www.php.net/news.rss di aplikasi aggregator. Nanti aplikasi aggregator akan menampilkan RSS feed tersebut dengan antarmuka yang mudah dibaca.</p>
<p>Ada beberapa versi format data RSS, yaitu versi 0.9, 0.91, 1.0, dan 2.0. Selain itu, juga ada format lain yang namanya Atom. Data ini (baik RSS maupun Atom) tersebut dikemas dalam format XML, yang nantinya dapat dibaca oleh aplikasi yang namanya aggregator. Ulasan tentang berbagai jenis aplikasi aggregator berbasis desktop dapat dilihat <a href="http://endy.artivisi.com/blog/aplikasi/aggregator-on-windows-2/">di sini</a>.</p>
<p>Aplikasi aggregator juga ada yang berbasis web. Misalnya Google Reader dan Bloglines. Kita juga bisa membuat sendiri dengan menggunakan library <a href="http://magpierss.sourceforge.net">Magpie RSS</a> atau menggunakan plugin di berbagai aplikasi Content Management System populer. Biasanya aplikasi populer seperti Wordpress, Drupal, atau Joomla sudah memiliki plugin untuk itu. Contoh nyatanya bisa dilihat di <a href="http://www.prayudi.web.id">website Pak Cipi</a>. Website ini dibuat dengan Drupal.</p>
<p>Cara kerja aplikasi aggregator sama dengan aplikasi mail client (Outlook Express, Evolution, atau Thunderbird). Bedanya, kalau aplikasi mail client mengambil data email dari mail server, aplikasi aggregator mengambil data RSS dari website yang menyediakan. Kita bisa memasukkan website sebanyak-banyaknya sesuai keinginan kita. Nantinya, kalau website yang kita daftarkan merilis artikel/berita baru, rangkumannya akan muncul di aggregator kita. Di aplikasi aggregator akan muncul entri <strong>Unread Items</strong> seperti halnya <strong>Unread Mails</strong>. Aplikasi aggregator ini berguna agar kita tidak perlu mengunjungi setiap website satu persatu. Cukup lihat judul dan ringkasan artikel baru. Jika menarik, kita kunjungi websitenya. Jika tidak menarik, diabaikan saja.</p>
<p>Demikianlah penjelasan singkat tentang RSS. Jangan salah lagi membedakan antara RSS dan CSS.</p>
<p>They are different !!</p>
Pesan Buku Subversion2007-02-08T01:00:48+07:00https://software.endy.muhardin.com/lain/pesan-buku-subversion<p>Berkat <a href="http://www.nagasakti.or.id/roller/Ifnu/entry/20070129">posting Ifnu yang berapi-api</a>, buku Subversion saya sudah banyak yang pesan. Ini kenyataan yang menggembirakan, mengingat saya belum banyak berpromosi.</p>
<p>Ini dia gambar sampul depannya.</p>
<p><a href="/images/uploads/2007/02/cover-front.png"><img src="/images/uploads/2007/02/cover-front.png" alt="Sampul Depan Buku Subversion " /></a></p>
<p>Sedangkan ini sampul belakangnya.</p>
<p><a href="/images/uploads/2007/02/cover-back.png"><img src="/images/uploads/2007/02/cover-back.png" alt="Sampul Belakang Buku Subversion " /></a></p>
<p>Silahkan lihat dulu <a href="http://endy.artivisi.com/downloads/writings/buku-svn-daftar-isi.pdf">daftar isinya</a> untuk mengetahui apa saja yang dibahas dalam buku ini.</p>
<p>Bab 1 dan Bab 2 boleh didownload secara cuma-cuma.</p>
<ul>
<li>
<table>
<tbody>
<tr>
<td><a href="http://endy.artivisi.com/downloads/writings/buku-svn-bab-1.pdf">Bab 1. Pendahuluan</a></td>
<td>87 kB</td>
</tr>
</tbody>
</table>
</li>
<li>
<table>
<tbody>
<tr>
<td><a href="http://endy.artivisi.com/downloads/writings/buku-svn-bab-2.pdf">Bab 2. Instalasi dan Konfigurasi</a></td>
<td>150 kB</td>
</tr>
</tbody>
</table>
</li>
</ul>
<p>Tebal buku 160 halaman.</p>
<p>Pertanyaan yang sering ditanyakan adalah,</p>
<blockquote>
<p>Saya sudah cari di toko buku, tapi tidak ada. Bagaimana cara mendapatkannya?</p>
</blockquote>
<p>Jawabannya, tentu saja tidak ada. Soalnya buku ini saya terbitkan sendiri tanpa melalui penerbit. Jadi, distribusinya juga mandiri alias melalui kantor pos terdekat.</p>
<p>Sementara ini, website untuk pemesanan masih sedang dibuat. Walaupun demikian, buku Subversion dapat dipesan dengan empat langkah mudah:</p>
<ol>
<li>
<p>Kirim email pemesanan ke endy [at] artivisi [dot] com. Sebutkan nama dan alamat Anda. Pastikan alamatnya benar, supaya tukang pos tidak tersesat.</p>
</li>
<li>
<p>Transfer pembayaran ke</p>
</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* BCA KCP Dewi Sartika Jakarta, rekening 273 124 336 2 atas nama Endy Muhardin.
* Mandiri KCP Dewi Sartika Jakarta, rekening 0 7 0000 4860 255 atas nama Endy Muhardin.
</code></pre></div></div>
<ol>
<li>
<p>Harga buku Rp. 50.000. Ongkos kirim Jabodetabek Rp. 6.000, di luar Jabodetabek Rp. 10.000. Setelah transfer dilakukan, beri tahu saya tentang pembayaran ini melalui email. Cantumkan nominal transfer, rekening asal, dan waktu transaksi untuk memudahkan verifikasi.</p>
</li>
<li>
<p>Segera setelah pembayaran saya terima, buku akan segera dikirim. Dalam beberapa hari, tergantung jauh dekatnya, buku akan sampai di tangan Anda.</p>
</li>
</ol>
<p>Ongkos kirim yang dijadikan patokan adalah Jakarta - Bogor dengan menggunakan Kilat Tercatat Kantor Pos. Saya masih mencari alternatif pengiriman yang lebih murah. Bila ada yang punya saran agar ongkos kirim lebih murah, jangan ragu untuk memberi tahu saya.</p>
<p>Terima kasih atas pesanan Anda. Kalau ada keluhan, silahkan kirim email ke saya supaya diperbaiki.</p>
Interface vs Abstract class2007-01-25T23:31:25+07:00https://software.endy.muhardin.com/java/interface-abstract<blockquote>
<p>Kapan pakai interface, dan kapan pakai abstract class?</p>
</blockquote>
<p>Pertanyaan menarik ini muncul di milis jug-indonesia. Karena jawabannya cukup panjang, jadi saya copy-paste, sesuaikan sedikit, dan posting di blog.</p>
<p>First of all, ini pertanyaan advanced. Kapan pakai interface, dan kapan pakai abstract class itu cuma bisa dipahami dengan coding banyak-banyak (learning by doing), berpikir secara abstrak, dan banyak belajar desain aplikasi. Jadi jangan berkecil hati kalau Anda bingung setelah membaca artikel ini. Bukannya Anda tidak berbakat coding, tapi hanya kurang jam terbang saja.</p>
<p>Berikut jawaban saya.</p>
<blockquote>
<p>Abstract class itu digunakan untuk mengimplementasikan pattern Template Method. Sedangkan interface digunakan (diantaranya) untuk mengimplementasikan pattern Observer.</p>
</blockquote>
<p>Pakai interface bila satu class mau memiliki beberapa tipe data. Di Java, tipe data ditentukan oleh interface dan class. Mengacu pada buku Design Pattern, interface digunakan untuk menerapkan pattern Observer.</p>
<p>Contoh mudahnya seperti ini. Misalnya kita membuat aplikasi GUI. dan menggunakan komponen text (JTextArea). Komponen ini memiliki beberapa method untuk mengaktifkan event handling, beberapa diantaranya:</p>
<ul>
<li>addMouseListener(MouseListener msl) : merespon gerakan mouse</li>
<li>addCaretListener(CaretListener cls) : merespon gerakan kursor</li>
</ul>
<p>Kalau kita definsikan class seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">EventLog</span> <span class="kd">implements</span> <span class="nc">MouseListener</span><span class="o">,</span> <span class="nc">CaretListener</span> <span class="o">{}</span>
</code></pre></div></div>
<p>maka class EventLog bisa diumpankan pada kedua method di atas, karena class EventLog bertipe data EventLog, MouseListener, dan juga CaretListener.</p>
<p>Lalu, kapan kita menggunakan abstract class? Salah satunya apabila kita ingin membuat Template Method.</p>
<p>Kutipan dari Design Pattern GoF</p>
<blockquote>
<p>By defining some of the steps of an algorithm using abstract operations, the template method fixes their ordering, but it lets Application and Document subclasses vary those steps to suit their needs.</p>
</blockquote>
<p>Seperti kita ketahui, Template Method itu salah satu methodnya concrete dan (sebaiknya) final.</p>
<p>Contoh template method bisa dilihat di implementasi AbstractFormController di <a href="http://www.springframework.org">Spring Framework</a>.</p>
<p>Untuk non-pengguna Spring, AbstractFormController itu mendefinisikan workflow pemrosesan HTML form. Method yang perlu diperhatikan di sini adalah method handleRequestInternal. Isi method ini kira2 seperti ini (dimodifikasi agar mudah dimengerti):</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="kt">void</span> <span class="nf">handleRequestInternal</span><span class="o">()</span> <span class="o">{</span>
<span class="n">bindDataDariForm</span><span class="o">();</span>
<span class="n">setelahBindSebelumValidasi</span><span class="o">();</span>
<span class="n">validasiData</span><span class="o">();</span>
<span class="n">setelahValidasi</span><span class="o">();</span>
<span class="n">processFormSubmission</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Seperti kita lihat di atas, method ini memanggil beberapa method lain secara berurutan. Urutan ini penting, karena kita tidak mau validasi dipanggil setelah form diproses. Apa gunanya validasi kalau pemrosesan sudah selesai?</p>
<p>Class AbstractFormController ini punya abstract method, yaitu:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">void</span> <span class="nf">processFormSubmission</span><span class="o">();</span>
</code></pre></div></div>
<p>Kenapa dibuat abstract? Karena pada saat menulis kode tersebut, Rod Johnson tidak tahu apa yang kita ingin lakukan pada saat form diproses. Ada yang mau simpan ke database, ada yang mau kirim email, dan berbagai kegiatan lain yang tidak terbayangkan sebelumnya. Oleh karena itu, method ini dibuat abstract, sehingga kita harus membuat implementasinya (override)</p>
<p>Nah, kita sebagai end-user, biasanya hanya perlu mengimplement method processFormSubmission tersebut. Method lainnya hanya dioverride apabila perlu. Misalnya kita ingin pakai logika validasi sendiri, atau ada pemrosesan khusus setelah validasi.</p>
<p>Teknik Template Method ini tidak bisa diimplement dengan interface, karena harus ada method concrete handleRequestInternal yang berfungsi untuk mendefinsikan workflow.</p>
<p>Demikian, mudah-mudahan bisa dimengerti.</p>
Presentasi Ruthless Testing2007-01-23T12:49:40+07:00https://software.endy.muhardin.com/java/presentasi-ruthless-testing<p>Saya akan berbicara pada pertemuan rutin komunitas Java, JaMU (Java Meet Up). Acaranya akan diadakan di kantor Sun Microsystem, Sabtu, 27 Januari 2006.</p>
<p>Tema yang akan saya bawakan adalah Ruthless Testing. Materi ini sudah dibahas pada beberapa artikel di blog ini. Silahkan datang langsung ke kantor Sun untuk mengikuti presentasinya. Gratis, tidak dipungut biaya. Anda tidak harus menguasai Java untuk bisa hadir.</p>
<p>Materi presentasi bisa <a href="http://software.endy.muhardin.com/files/slide-presentasi/software-testing-java.html">dilihat secara online</a>.</p>
<p>Presentasi dibuat menggunakan perangkat canggih bernama <a href="http://meyerweb.com/eric/tools/s5/">S5 (Simple Standards-Based Slide Show System)</a>, tools yang dibuat Eric Meyer, pakar CSS. Dengan S5, presentasi dapat dibuka oleh mayoritas browser yang digunakan masyarakat. Tidak tergantung lagi pada OpenOffice apalagi Microsoft Office.</p>
<p>Selamat membaca … sampai ketemu di JaMU.</p>
Intro JMS2007-01-18T20:19:14+07:00https://software.endy.muhardin.com/java/intro-jms<p>Pada artikel ini, kita akan coba buat demo penggunaan JMS menggunakan <a href="http://www.springframework.org">Spring Framework</a> dan <a href="http://incubator.apache.org/activemq">ActiveMQ</a>. Spring Framework 2.0 telah dilengkapi dengan helper class untuk memudahkan kita menggunakan JMS (Java Messaging Service). Sayangnya dokumentasi di Spring Reference kurang lengkap, sehingga ada beberapa bagian yang harus kita cari sendiri.</p>
<p>Jangan khawatir, bagian yang kurang tersebut bisa dibaca di artikel ini.</p>
<p>Berikut skenario yang ingin saya buat:</p>
<ol>
<li>
<p>Jalankan JMS server (kali ini saya gunakan ActiveMQ)</p>
</li>
<li>
<p>Siapkan <em>MessageListener</em>, yang akan bereaksi bila menerima pesan pada destination tertentu</p>
</li>
<li>
<p>Kirim beberapa pesan ke destination yang didengarkan oleh <em>MessageListener</em></p>
</li>
<li>
<p>Pastikan pesan tersebut diterima dengan baik</p>
</li>
</ol>
<p>Kita akan buat kasus sederhana saja, melibatkan tiga class:</p>
<ul>
<li>
<p>Sender.java : pengirim pesan</p>
</li>
<li>
<p>Receiver.java : penerima pesan (MessageListener)</p>
</li>
<li>
<p>Main.java : class untuk menjalankan pengirim dan penerima</p>
</li>
</ul>
<p>Berikut adalah kode programnya.</p>
<h3 id="senderjava">Sender.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.spring.jms</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.jms.JMSException</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.jms.Message</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.jms.Session</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.jms.core.JmsTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.jms.core.MessageCreator</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Sender</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">message</span> <span class="o">=</span> <span class="s">"default message"</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">JmsTemplate</span> <span class="n">jmsTemplate</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setJmsTemplate</span><span class="o">(</span><span class="nc">JmsTemplate</span> <span class="n">jmsTemplate</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">jmsTemplate</span> <span class="o">=</span> <span class="n">jmsTemplate</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setMessage</span><span class="o">(</span><span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">message</span> <span class="o">=</span> <span class="n">message</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">send</span><span class="o">()</span> <span class="o">{</span>
<span class="n">jmsTemplate</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="k">new</span> <span class="nc">MessageCreator</span><span class="o">(){</span>
<span class="kd">public</span> <span class="nc">Message</span> <span class="nf">createMessage</span><span class="o">(</span><span class="nc">Session</span> <span class="n">sess</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">JMSException</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">sess</span><span class="o">.</span><span class="na">createTextMessage</span><span class="o">(</span><span class="n">message</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="receiverjava">Receiver.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.spring.jms</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.jms.JMSException</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Receiver</span> <span class="kd">implements</span> <span class="nc">MessageListener</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onMessage</span><span class="o">(</span><span class="nc">Message</span> <span class="n">msg</span><span class="o">){</span>
<span class="k">if</span> <span class="o">(</span><span class="n">msg</span> <span class="k">instanceof</span> <span class="nc">TextMessage</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">TextMessage</span> <span class="n">txtMsg</span> <span class="o">=</span> <span class="o">(</span><span class="nc">TextMessage</span><span class="o">)</span> <span class="n">msg</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">txtMsg</span><span class="o">.</span><span class="na">getText</span><span class="o">());</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">JMSException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Unsupported message type : "</span><span class="o">+</span><span class="n">msg</span><span class="o">.</span><span class="na">getClass</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="mainjava">Main.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.spring.jms</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.ApplicationContext</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.support.ClassPathXmlApplicationContext</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ApplicationContext</span> <span class="n">ctx</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ClassPathXmlApplicationContext</span><span class="o">(</span><span class="s">"jms-ctx.xml"</span><span class="o">);</span>
<span class="nc">Sender</span> <span class="n">sender</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Sender</span><span class="o">)</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"sender"</span><span class="o">);</span>
<span class="n">sender</span><span class="o">.</span><span class="na">setMessage</span><span class="o">(</span><span class="s">"Percobaan menggunakan JMS dengan Spring. "</span><span class="o">);</span>
<span class="n">sender</span><span class="o">.</span><span class="na">send</span><span class="o">();</span>
<span class="n">sender</span><span class="o">.</span><span class="na">setMessage</span><span class="o">(</span><span class="s">"Pesan ini seharusnya diterima oleh Message Driven POJO"</span><span class="o">);</span>
<span class="n">sender</span><span class="o">.</span><span class="na">send</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Integrasi antara kode Java dan ActiveMQ diatur di konfigurasi Spring, jms-ctx.xml. Berikut kodenya.</p>
<h3 id="jms-ctxxml">jms-ctx.xml</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="cp"><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"></span>
<span class="nt"><beans></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"messageListener"</span> <span class="na">class=</span><span class="s">"tutorial.spring.jms.Receiver"</span> <span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"listenerContainer"</span> <span class="na">class=</span><span class="s">"org.springframework.jms.listener.SimpleMessageListenerContainer"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"connectionFactory"</span> <span class="na">ref=</span><span class="s">"jmsConnectionFactory"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"destinationName"</span> <span class="na">value=</span><span class="s">"TEST.FOO"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"messageListener"</span> <span class="na">ref=</span><span class="s">"messageListener"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"jmsConnectionFactory"</span> <span class="na">class=</span><span class="s">"org.apache.activemq.ActiveMQConnectionFactory"</span><span class="nt">></span>
<span class="nt"><constructor-arg</span> <span class="na">index=</span><span class="s">"0"</span><span class="nt">><null/></constructor-arg></span>
<span class="nt"><constructor-arg</span> <span class="na">index=</span><span class="s">"1"</span><span class="nt">><null/></constructor-arg></span>
<span class="nt"><constructor-arg</span> <span class="na">index=</span><span class="s">"2"</span> <span class="na">value=</span><span class="s">"tcp://localhost:61616"</span><span class="nt">></constructor-arg></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"sender"</span> <span class="na">class=</span><span class="s">"tutorial.spring.jms.Sender"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"jmsTemplate"</span> <span class="na">ref=</span><span class="s">"jmsTemplate"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"jmsTemplate"</span> <span class="na">class=</span><span class="s">"org.springframework.jms.core.JmsTemplate"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"connectionFactory"</span> <span class="na">ref=</span><span class="s">"jmsConnectionFactory"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"defaultDestinationName"</span> <span class="na">value=</span><span class="s">"TEST.FOO"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>Apabila kita ingin menggunakan JMS server yang lain (selain ActiveMQ), cukup ganti deklarasi <em>jmsConnectionFactory</em> pada <em>jms-ctx.xml</em> di atas. Destination name juga di-hard code dengan nilai <em>TEST.FOO</em>. Nantinya pada aplikasi nyata, nilai ini mungkin ingin kita konfigurasi lebih jauh, atau diparameterisasi dalam database.</p>
<p>ActiveMQ mendukung <em>dynamic destination creation</em>, artinya destination <em>TEST.FOO</em> akan dibuatkan bila belum ada. Pada aplikasi server JMS yang lain, mungkin kita harus membuat <em>TEST.FOO</em> secara manual di server tersebut.</p>
<p>Selamat mencoba, semoga sukses.</p>
Google "endy"2007-01-18T17:59:52+07:00https://software.endy.muhardin.com/lain/google-endy<p>Masukkan <a href="http://www.google.co.id/search?hl=id&q=endy&meta=">keyword “endy” di Google Indonesia</a>. Lihat hasil search paling atas …</p>
<p><a href="/images/uploads/2007/01/endy-google-search.png">
[<img src="/images/uploads/2007/01/endy-google-search-crop.png" alt="Google Search "endy". Klik untuk memperbesar " />](/images/uploads/2007/01/endy-google-search-crop.png)
</a></p>
Version Control untuk Database2007-01-06T01:24:50+07:00https://software.endy.muhardin.com/aplikasi/version-control-database<p>Menyimpan kode program Java ke repository tidak sulit. Yang lebih sulit adalah menyimpan artifak database, yaitu:</p>
<ul>
<li>Skema database</li>
<li>Stored Procedure</li>
<li>Functions</li>
<li>Sample/Initial data</li>
</ul>
<p>Kalau dilakukan dengan cara manual, akan sangat merepotkan, karena programmer harus melakukan langkah-langkah berikut untuk memastikan kode programnya tersimpan di repository:</p>
<ol>
<li>Copy semua source code melalui Query Editor</li>
<li>Paste satu per satu ke text file</li>
<li>Commit ke repository</li>
<li>Ulangi langkah 1 - 3 untuk setiap kali perubahan</li>
</ol>
<p>Dengan skala ribuan procedure, cara ini sangat merepotkan dan dijamin tidak akan dilakukan oleh programmer. Harus ada cara yang lebih baik.</p>
<p>Untungnya –seperti halnya masalah yang umum kita temukan– sudah banyak orang lain yang mengalami masalah sama, dan sudah ada yang memecahkannya. Kali ini solusinya adalah <a href="http://www.codeproject.com/cs/database/ScriptDB4Svn.asp" title="Homepage ScriptDB4SVN">ScriptDB4SVN</a>. Aplikasi kecil yang seumur hidupnya hanya bertugas melakukan langkah 1-3 di atas.</p>
<p>Sayangnya aplikasi ini hanya bisa digunakan untuk Microsoft SQL Server.</p>
<p>Aplikasi ini membutuhkan .Net Framework 2.0 yang dapat didownload secara cuma-cuma di website Microsoft. Segera setelah .Net Framework diinstal, kita dapat langsung menjalankan ScriptDB4SVN.</p>
<p>Sebelumnya, kita harus edit dulu file konfigurasinya. Kita harus beritahu ScriptDB4SVN tentang:</p>
<ul>
<li>Host database</li>
<li>Nama database yang akan digunakan</li>
<li>Daftar tabel yang akan didump datanya</li>
</ul>
<p>Berikut adalah contoh konfigurasinya</p>
<p>Setelah konfigurasi selesai dibuat, jalankan ScriptDB4SVN.exe. Hasilnya seperti ini.</p>
<p><a href="/images/uploads/2007/01/scriptdb4svn.png"><img src="/images/uploads/2007/01/scriptdb4svn.png" alt="Screenshot ScriptDB4SVN " /></a></p>
<p>Setiap kali ada perubahan di database, dobel-klik ScriptDB4SVN dan commit file yang berubah. Harus diperhatikan, ScriptDB4SVN tidak otomatis menjalankan svn add. Jadi kalau ada Store Procedure baru, kita harus jalankan svn add secara manual.</p>
<p>Pembaca yang teliti mungkin akan melihat script sql dengan nama aneh pada screenshot di atas. Itu adalah script replikasi. Kalau script tersebut dijalankan di Query Analyzer, maka dia akan merekonstruksi database sehingga sama dengan kondisi database target pada saat ScriptDB4SVN dijalankan.</p>
<p>Dengan adanya tools ini, masing-masing programmer dapat bekerja di database lokalnya sendiri. Secara periodik, dia harus melakukan svn update dan merekonstruksi database lokalnya dengan script terbaru sehingga tetap sinkron dengan kondisi terbaru.</p>
Project Starter Kit2006-12-28T23:45:57+07:00https://software.endy.muhardin.com/manajemen/starter-kit<p>Mengelola project software tidak mudah. Ada berbagai aspek teknis dan non teknis. Apalagi kalau projectnya berkaitan dengan pemerintahan atau organisasi lain yang birokratis. Akan banyak pekerjaan dokumentasi.</p>
<p>Di sisi lain, pekerjaan teknis selain coding juga tidak kalah banyaknya. Contohnya, dalam kehidupan sehari-hari programmer, dia harus:</p>
<ol>
<li>Menulis kode</li>
<li>Menyimpan kode di repository</li>
<li>Memberitahu programmer lain tentang perubahan yang dilakukan</li>
<li>Menulis dokumentasi kode</li>
<li>Menjalankan unit test</li>
<li>Membuat rilis</li>
<li>Menjawab pertanyaan client/bos/tim user manual</li>
</ol>
<p>Semua kegiatan ini harus dikelola dengan baik agar tidak saling tumpang tindih. Untungnya banyak perangkat pembantu yang dapat memudahkan hidup kita. Pastikan Anda menggunakan persenjataan ini agar proyek bisa berjalan dengan lebih lancar. Berikut daftarnya:</p>
<ol>
<li>Version Control</li>
<li>Bug tracker</li>
<li>Mailing list</li>
<li>File server</li>
<li>Wiki</li>
<li>Forum</li>
<li>Build server</li>
</ol>
<p>Mari kita bahas dengan lebih detail.</p>
<h3 id="version-control">Version Control</h3>
<p>Semacam file server, tapi lebih sakti. Mampu menyimpan riwayat perubahan, perbedaan antar versi, dan lainnya. Silahkan download <a href="http://endy.artivisi.com/downloads/writings/Subversion-presentation-20061129.pdf">presentasi saya tentang Subversion</a> untuk pemahaman lebih lanjut.</p>
<h3 id="bug-tracker">Bug Tracker</h3>
<p>Sepanjang perjalanan project, pasti banyak terjadi kesalahan. Baik kesalahan dokumen, kode program, permintaan yang kurang jelas, dan sebagainya. Daripada membuat mencatat di satu file khusus, lebih baik gunakan aplikasi bug tracker. Setiap entri di bug tracker bisa ditugaskan pada satu user. Status dari setiap entri (sedang dikerjakan, sudah selesai, selesai tapi tidak memuaskan) semua bisa ditentukan.</p>
<p>Gunakan bug tracker untuk memberikan tugas pada orang lain. Bug tracker yang baik biasanya terintegrasi dengan mail server, sehingga begitu kita membuat entri, email akan dikirim ke orang yang menerima tugas. Pada waktu suatu entri selesai dikerjakan dan statusnya diganti, email akan dikirim ke si pemberi tugas. Dengan demikian, tidak ada hal yang tercecer atau terlupakan.</p>
<p>Berikut daftar beberapa aplikasi bugtracker yang gratis dan open source:</p>
<ul>
<li><a href="http://www.bugzilla.org/">Bugzilla</a></li>
<li><a href="http://mantisbt.sourceforge.net/">Mantis</a></li>
<li><a href="http://scarab.tigris.org">Scarab</a></li>
<li><a href="http://trackit.sourceforge.net/">TrackIt</a></li>
</ul>
<h3 id="mailing-list">Mailing List</h3>
<p>Biasanya dalam project, kita akan sering mengirim email ke beberapa orang sekaligus. Misalnya pengumuman rilis, klarifikasi requirement, janji meeting, atau laporan kemajuan. Daripada mengetik banyak alamat sekaligus, lebih baik buat mailing list sesuai tujuan komunikasi. Buatlah mailing list untuk manajemen dan development. Segala pengumuman dan diskusi tentang manajemen proyek dikirim ke mailing list manajemen. Pembicaraan tentang pemrograman dilakukan di milis development.</p>
<p>Dengan demikian, selain tidak perlu mengetik alamat email satu persatu dan beresiko lupa, semua pembicaraan juga terekam secara kronologis.</p>
<p>Tidak mau repot menginstal aplikasi milis? Gunakan saja milis gratisan seperti <a href="http://groups.yahoo.com">YahooGroups</a> atau <a href="http://groups.google.com">GoogleGroups</a>.</p>
<h3 id="file-server">File Server</h3>
<p>Selain melalui repository, kadang kita perlu bertukar file berukuran besar. Untuk itu, kita perlu file server. Banyak penyedia file server gratisan di internet, misalnya <a href="http://rapidshare.de">Rapidshare</a> atau <a href="http://www.megaupload.com">Megaupload</a>.</p>
<p>Kalau tidak mau pakai gratisan –mungkin takut data penting diambil orang– gunakan <a href="http://www.apachefriends.org/en/xampp.html">XAMPP</a>.</p>
<h3 id="wiki">Wiki</h3>
<p>Wiki adalah website yang bisa diedit orang banyak. Contohnya <a href="http://tutorial.artivisi.com">wiki ArtiVisi</a>. Cara mengeditnya juga mudah dan sederhana. Tidak perlu keahlian HTML atau pemrograman web. Ideal digunakan untuk membuat dokumen yang sering berubah dan diakses sepanjang waktu, misalnya:</p>
<ul>
<li>spesifikasi aplikasi</li>
<li>desain sistem</li>
<li>manual penggunaan aplikasi</li>
</ul>
<p>Aplikasi wiki banyak tersedia secara gratis, misalnya:</p>
<ul>
<li><a href="http://wiki.splitbrain.org/wiki:dokuwiki">DokuWiki</a> (yang digunakan ArtiVisi, berbasis PHP)</li>
<li><a href="http://www.jspwiki.org/">JSPWiki</a> (berbasis Java)</li>
<li><a href="http://www.pmichaud.com/wiki/PmWiki/PmWiki">pmWiki</a> (berbasis PHP)</li>
<li><a href="http://www.mediawiki.org/wiki/MediaWiki">MediaWiki</a> (yang digunakan <a href="http://www.wikipedia.org">Wikipedia</a>)</li>
<li><a href="http://moinmoin.wikiwikiweb.de/">MoinMoin</a> (berbasis Python)</li>
</ul>
<p>dan masih banyak aplikasi lainnya.</p>
<p>Perbandingan antar aplikasi dapat dilihat di situs <a href="http://en.wikipedia.org/wiki/Comparison_of_wiki_software">MediaWiki</a> dan <a href="http://wiki.splitbrain.org/wiki:compare">DokuWiki</a></p>
<p>Instalasi DokuWiki sangat mudah, cukup extract, kemudian publish melalui webserver yang support PHP. Pengguna Windows bisa menggunakan XAMPP dan menjalankan DokuWiki dalam 5 (lima) menit saja.</p>
<h3 id="forum">Forum</h3>
<p>Apabila proyek/produk kita digunakan masyarakat luas, pasti akan banyak interaksi dengan pengguna. Biasanya pengguna baru akan menemui kesulitan dan butuh tempat bertanya. Mereka ini biasanya malas ikut milis, karena mungkin cuma butuh satu jawaban dan tidak ingin mengikuti diskusi di milis.</p>
<p>Kita bisa gunakan aplikasi forum yang banyak tersedia secara gratis. Salah satu yang terkenal adalah <a href="http://www.phpbb.com">phpBB</a>. Maniak Java bisa gunakan <a href="http://www.mvnforum.com">mvnforum</a>.</p>
<h3 id="build-server">Build Server</h3>
<p>Apabila proyek kita sudah dilengkapi dengan version control, automated unit test, dan automated code review, kita bisa gunakan build server. Gunanya adalah agar secara berkala (misalnya sehari sekali), ada yang mengambil semua kode terbaru dari repository, melakukan kompilasi, menjalankan semua automated test, dan melaporkan hasilnya via website, email, rss, instant messenger, sms, atau media komunikasi lainnya.</p>
<p>Berkat keajaiban open source, aplikasi ini tersedia secara gratis. Kita bisa gunakan <a href="http://cruisecontrol.sourceforge.net">CruiseControl</a> atau <a href="http://luntbuild.javaforge.com">Luntbuild</a>. Buat yang nekat tetap ingin bayar, bisa gunakan <a href="http://zutubi.com/products/pulse/">Pulse</a> atau <a href="http://confluence.atlassian.com/display/BAMBOO">Bamboo</a>.</p>
<blockquote>
<p>Wow … begitu banyak yang harus disiapkan?</p>
</blockquote>
<p>Begitu dalam pikiran Anda.</p>
<p>Jangan khawatir, ada aplikasi gratis untuk menyatukan semuanya. Gunakan <a href="http://trac.edgewall.org/">Trac</a>.</p>
<blockquote>
<p>Bagaimana kalo projectnya banyak? Di kantor saya biasanya ada 10 project jalan berbarengan. Belum lagi 15 produk yang harus dikelola.</p>
</blockquote>
<p>Jangan khawatir. Bawa pulang <a href="http://www.vasoftware.com/sourceforge/">Sourceforge</a> ke kantor Anda. Atau gunakan saingannya, <a href="http://gforge.org/">GForge</a>. Keduanya gratis dan open source.</p>
<p>Demikianlah rangkaian persenjataan dalam mengelola proyek. Semoga bermanfaat.</p>
Snap2006-12-08T22:59:47+07:00https://software.endy.muhardin.com/aplikasi/lain/snap<p>Ide orang memang ada-ada saja. Kali ini ada situs yang menampilkan preview dari suatu link. Coba arahkan (tidak perlu klik) mouse anda ke <a href="http://snap.com" title="Snap.com">link Snap.com ini</a>. Tunggu sejenak, dan (tergantung kecepatan bandwidth anda) screenshot website Snap akan muncul.</p>
<p>Cara mendapatkannya tidak sulit. Cukup buka halaman pendaftarannya, dan masukkan:</p>
<ul>
<li>URL website kita</li>
<li>Email</li>
<li>Tulisan di gambar captcha</li>
</ul>
<p>Centang <em>I Agree</em> dan klik <em>Get Code</em>.</p>
<p>Pada halaman berikutnya kita akan mendapat sepotong kode untuk dipasang di kode HTML website kita. Sangat mudah.</p>
<p>Selain Snap.Com, ada lagi website yang namanya Twitter. Kalau yang ini benar-benar kurang kerjaan. Silahkan <a href="http://twitter.com" title="Twitter">cek sendiri</a> dan cari tahu apa yang <a href="http://twitter.com/public_timeline" title="Twitter">dikerjakan dunia saat ini</a>.</p>
Ruthless Testing 52006-12-01T18:35:39+07:00https://software.endy.muhardin.com/java/ruthless-testing-5<p>Setelah beberapa seri artikel Ruthless Testing, ternyata lebih banyak yang skeptis daripada yang antusias. Respon yang paling banyak ditemukan adalah:</p>
<blockquote>
<p>Wah, ini konsep yang sangat menarik. Tentunya akan sangat baik jika diterapkan. Sayang sekali, di tempat saya tidak bisa, karena … [berbagai alasan dan kesulitan] …</p>
</blockquote>
<p>Hmm … saya mengerti perasaan Anda. Been there, done that. Saya pernah jadi programmer, dan juga pernah jadi project manager. Saya mengerti beberapa keberatan dan kesulitan implementasinya.</p>
<p>Daripada berkeluh kesah tiada guna, mari kita lihat satu persatu masalahnya.</p>
<h4 id="programmer--bikin-unit-test-nambah-kerjaan-aja-lagian-test-code-kan-gak-dideliver-ke-client">Programmer : Bikin unit test? Nambah kerjaan aja. Lagian test code kan gak dideliver ke client</h4>
<p>Jawaban saya:
Unit test itu adalah investasi Anda. Bayangkan beberapa bulan/tahun yang akan datang, client datang ke kita, meminta perbaikan untuk error dalam aplikasi. Padahal kita sedang sibuk mengerjakan proyek lain yang sama sekali berbeda. Hampir seluruh kode sudah kita lupakan. Bahkan untuk method sederhana seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>calc.divide(x, y);
</code></pre></div></div>
<p>kita sudah lupa bagaimana behaviournya kalau menghadapi pembagian dengan nol, bilangan negatif, atau bilangan tidak bulat. Dan seperti biasa, kita tidak membuat komentar Javadoc.</p>
<p><em>Unit test to the rescue</em>. Tinggal buka unit test untuk kode di atas, dan kita akan melihat bagaimana kode tersebut menghadapi bilangan negatif, nol, dan kasus-kasus aneh lainnya.</p>
<p>Next ..</p>
<h4 id="programmer--aplikasi-saya-mengakses-jaringandatabaseemail-sulit-dites">Programmer : Aplikasi saya mengakses jaringan/database/email. Sulit dites</h4>
<p>Jawaban saya:
Itu namanya integration testing, bukan unit testing. Walaupun demikian, tetap bisa dites secara otomatis. Tapi butuh sedikit investasi tambahan.</p>
<p>Misalnya, kita punya method seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void process(Order order) {
// coba lihat customernya, periksa saldonya
Customer c = order.getCustomer();
if(c.getBalance() < 0) {
// handle error
}
// saldo ok, simpan order ke database
String sql = "INSERT INTO tbl_order VALUES (?, ?, ?)";
// kirim email konfirmasi
String dest = c.getEmail();
// kode send email tidak ditunjukkan
}
</code></pre></div></div>
<p>Seperti kita lihat, ada beberapa integration point di sini, yaitu akses database dan koneksi ke mail server.</p>
<p>Untuk mengotomasi pengetesan method ini, kita gunakan teknik yang sudah teruji dari 400 tahun lalu: <em>divide et impera</em>. Yang tidak tidur di kelas waktu pelajaran Sejarah pasti tau teknik ini.
Sekarang teknik ini sudah diadaptasi ke dunia programming dengan istilah Refactoring. Silahkan google keyword tersebut untuk lebih jelasnya.</p>
<p>Pada contoh kita di atas, kita me-refactor kode akses database dan koneksi mail server ke class yang berbeda. Kita pisahkan kode tersebut ke class tersendiri. Berikut adalah interfacenya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface OrderDao {
public void save(Order o);
}
</code></pre></div></div>
<p>dan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface MailSender {
public void send(String from, String to, String message);
}
</code></pre></div></div>
<p>Implementasinya disisakan buat latihan di rumah.</p>
<p>Sehingga method kita menjadi seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void process(Order order) {
// coba lihat customernya, periksa saldonya
Customer c = order.getCustomer();
if(c.getBalance() < 0) {
// handle error
}
orderDao.save(order);
mailSender.send(adminEmail, c.getEmail(), "Order sudah dikirim");
}
</code></pre></div></div>
<p>Dengan adanya kedua class tambahan ini, kita bisa menggunakan <a href="http://www.easymock.org/" title="Salah satu implementasi mock object">Mock Object</a> untuk memalsukan object orderDao dan mailSender. Kita bisa buat kedua object ini selalu sukses, atau selalu gagal, tergantung skenario yang mau dites.</p>
<p>OrderDao sendiri bisa dites dengan menggunakan <a href="http://endy.artivisi.com/blog/java/ruthless-testing-4/" title="Database Testing">teknik pengetesan database</a>. MailSender dapat ditest dengan menggunakan <a href="http://quintanasoft.com/dumbster/" title="Mail Server Palsu">Dumbster</a>, mail server palsu.</p>
<p>Kasus seperti ini biasanya terjadi karena kodenya sudah terlanjur campur aduk. Apabila unit test diterapkan dari awal project, programmer terkondisi untuk membuat kode yang mudah dites, sehingga effort untuk refactor tidak terlalu besar.</p>
<p>Tujuan utama kita adalah semua tes bisa dijalankan otomatis, independen (tes satu tidak mempengaruhi tes lain) dan <em>repeatable</em>(bisa diulang-ulang dengan hasil yang konsisten).
Tidak harus menggunakan JUnit. Bisa pakai tools buatan sendiri.</p>
<p>Keberatan berikutnya datang dari project manager.</p>
<h4 id="project-manager--berarti-saya-harus-alokasikan-waktu-tambahan-untuk-membuat-unit-test-wah-bisa-bisa-saya-dimarahi-clientbos">Project Manager : Berarti saya harus alokasikan waktu tambahan untuk membuat unit test, wah bisa-bisa saya dimarahi client/bos.</h4>
<p>Jawaban saya:
Bos… programmer Anda <em>pasti</em> membuat kode test, walaupun tidak otomatis. Ini sudah naluri dasar manusia.</p>
<p>Berikut contohnya:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void process(Order order) {
// coba lihat customernya, periksa saldonya
// saldo ok, simpan order ke database
String sql = "INSERT INTO tbl_order VALUES (?, ?, ?)";
// coba periksa ... nanti kita hapus lagi
String coba = "SELECT * FROM tbl_order WHERE .. ";
// kirim email konfirmasi
String dest = c.getEmail();
// kode send email tidak ditunjukkan
}
</code></pre></div></div>
<p>Jadi, daripada dia pasang kode test di kode program, dan nantinya dihapus lagi, kan sayang. Lebih baik suruh saja untuk bikin unit test betulan.
Keuntungan berikutnya, dengan banyaknya unit test, kesalahan coding akan lebih cepat ditemukan, sehingga pada tahap testing oleh client, lebih sedikit bug yang ditemukan.
Dengan demikian, alokasi waktu untuk rework akan lebih sedikit.</p>
<h4 id="project-manager--kalau-terlalu-banyak-unit-test-nanti-aplikasinya-menjadi-rumit">Project Manager : Kalau terlalu banyak unit test, nanti aplikasinya menjadi rumit.</h4>
<p>Jawaban saya:
Justru sebaliknya. Aplikasi yang banyak unit testnya akan menjadi lebih modular dan tidak terlalu kusut struktur internalnya. Dengan demikian, kode program menjadi lebih mudah dipelihara dan dikembangkan.</p>
<p>Terakhir, ada beberapa tips dari saya agar penerapan unit testing dapat berjalan dengan optimal:</p>
<ul>
<li>siapkan infrastruktur sebelum mulai coding</li>
<li>sering-sering refactor agar kode mudah dites</li>
<li>gunakan coverage testing sebagai pelengkap, agar kekurangan unit test cepat terdeteksi</li>
<li>terapkan daily build otomatis yang menjalankan semua tes</li>
</ul>
<p>Demikian, semoga bermanfaat</p>
Presentasi Subversion2006-11-29T15:44:51+07:00https://software.endy.muhardin.com/aplikasi/lain/presentasi-subversion<p>Hari Sabtu, 2 Desember 2006 pukul 10.00 WIB, saya akan memberikan presentasi tentang Subversion di pertemuan rutin programmer Java (JaMU - Java Meet Up).</p>
<p>Adapun tempat kejadian presentasi (TKP) beralamat di:
SUN Microsystem Indonesia
Lantai 13, Gedung Wisma Metropolitan I (WTC Sudirman)
Jakarta</p>
<p>Subversion adalah aplikasi version control. Aplikasi ini mirip dengan file server, artinya dia bisa digunakan untuk menyimpan file, dan bisa diakses jika ingin mengambil file tersebut.
Bedanya dengan file server biasa (FTP server, Samba, atau Windows Sharing), aplikasi version control menyimpan riwayat perubahan semua yang kita simpan di dalamnya.
Kalau kita menyimpan satu file, kemudian isinya kita modifikasi (tambah baris, hapus, ganti nama, dan sebagainya), maka setiap perubahan tersebut dicatat oleh version control.</p>
<p>Tidak hanya dicatat, kita juga bisa mengembalikan kondisi file sesuai keinginan. Kita bisa melihat versi awal dari file tersebut, atau nama file sebelum diganti, dan semua titik penting lain di masa lalu.
Singkatnya, dalam urusan menyimpan file, version control mirip dengan mesin waktu.</p>
<p>SIlahkan datang ke JaMU minggu ini. Gratis, tidak dipungut biaya.</p>
<p>Materi yang akan dibawakan adalah:</p>
<ol>
<li>Konsep Version Control</li>
<li>Penggunaan sehari-hari</li>
<li>Tag, Branch, Merge</li>
<li>Aplikasi pelengkap</li>
<li>Keterbatasan Subversion</li>
<li>Aplikasi sejenis (kompetitor)</li>
</ol>
<table>
<tbody>
<tr>
<td>Presentasi dapat didownload [di sini [PDF</td>
<td>710KB]](http://endy.artivisi.com/downloads/writings/Subversion-presentation-20061129.pdf “Presentasi Subversion”).</td>
</tr>
</tbody>
</table>
BerkeleyDB Java Edition2006-10-05T17:53:37+07:00https://software.endy.muhardin.com/java/berkeleydb-java-edition<p>Beberapa waktu yang lalu, ada posting menarik tentang <a href="http://epsilondelta.net/2006/10/01/web-apps-why-does-statelessness-stop-at-the-database/" title="Stateless Database @ Google">Google yang menggunakan BerkeleyDB untuk mengelola user accountnya</a>. Karena penasaran, saya lalu mencoba mendownload dan bermain-main dengan BerkeleyDB versi Java (Java Edition).</p>
<p>BerkeleyDB adalah produk embedded database open source yang populer. Belum lama ini dia diakuisisi oleh Oracle, sehingga membuat produk ini makin terkenal lagi.</p>
<p>Pada tulisan kali ini, kita akan mencoba menggunakan BDB Java Edition.</p>
<p>Tapi jangan salah paham dengan namanya. BerkeleyDB adalah database, tapi bukan relational database. Artinya, dia tidak memiliki fasilitas SQL. Walaupun demikian, dia mendukung ACID transaction. Salah satu contoh penggunaan BDB adalah pada aplikasi version control Subversion.</p>
<p>Kalau tidak ada SQLnya, bagaimana kita melakukan operasi CRUD? Jawabnya adalah, anggap saja BDB sebagai Map. Kita bisa menggunakan Map seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Map myMap = new HashMap();
myMap.put(1, "Endy");
</code></pre></div></div>
<p>Maka object String akan disimpan dengan key 1. Object ini dapat diambil dengan kode:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> String data = myMap.get(1);
</code></pre></div></div>
<p>Bagi mereka yang sudah pernah mendengar Object Database tentunya sudah familiar dengan konsep seperti ini. Salah satu implementasi konsep ini adalah <a href="http://www.db4o.com/" title="DB4Object">db40</a> atau <a href="http://www.prevayler.org" title="Prevalence Layer">Prevayler</a>.</p>
<p>Sekarang, coba kita gunakan contoh kasus yang standar. Ada class Person yang akan kita buatkan operasi CRUD-nya. Berikut kodenya:</p>
<h3 id="personjava">Person.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package tutorial.berkeleydb;
import java.util.Date;
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.PrimaryKey;
@Entity
public class Person {
@PrimaryKey(sequence="ID")
private Integer id;
private String name;
private Date birthdate;
private String email;
public Person(){
}
public String getEmail() {
return email;
}
public void setEmail(String address) {
this.email = address;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
</code></pre></div></div>
<p>Perhatikan bahwa untuk menjadikan class Person persistent, kita hanya perlu menambahkan annotation, mirip dengan JPA atau Hibernate. Sekarang mari kita definisikan interface CRUD-nya.</p>
<h3 id="persondaojava">PersonDao.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package tutorial.berkeleydb;
import java.util.List;
public interface PersonDao {
public void save(Person p);
public void delete(Person p);
public Person getById(Integer id);
public List<Person> getAll();
}
</code></pre></div></div>
<p>Dan berikut adalah implementasinya.</p>
<h3 id="persondaoberkeleydb">PersonDaoBerkeleyDB</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package tutorial.berkeleydb;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
public class PersonDaoBerkeleyDB implements PersonDao {
private PrimaryIndex<Integer, Person> personPrimaryKey;
public PersonDaoBerkeleyDB(EntityStore storage) {
try {
personPrimaryKey = storage.getPrimaryIndex(Integer.class, Person.class);
} catch (DatabaseException e) {
e.printStackTrace();
}
}
public void delete(Person p) {
try {
personPrimaryKey.delete(p.getId());
} catch (DatabaseException e) {
e.printStackTrace();
}
}
public List<Person> getAll() {
List<Person> result = new ArrayList<Person>();
try {
EntityCursor<Person> allPerson = personPrimaryKey.entities();
Iterator<Person> iter = allPerson.iterator();
while (iter.hasNext()) {
result.add(iter.next());
}
allPerson.close();
} catch (DatabaseException e) {
e.printStackTrace();
}
return result;
}
public Person getById(Integer id) {
try {
return personPrimaryKey.get(id);
} catch (DatabaseException e) {
e.printStackTrace();
}
return null;
}
public void save(Person p) {
try {
personPrimaryKey.put(p);
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
</code></pre></div></div>
<p>Sebagai perbandingan, berikut kode CRUD menggunakan Hibernate dengan bantuan Spring.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class PersonDaoHibernate extends HibernateDaoSupport implements PersonDao{
public void save(Person p){
getHibernateTemplate().save(p);
}
public void delete(Person p){
getHibernateTemplate().delete(p);
}
public Person getById(Integer id){
Person result = null;
try {
result = (Person) getHibernateTemplate().load(Person.class, id);
getHibernateTemplate().initialize(result);
} catch (ObjectRetrievalFailureException e) {
log.log(Level.WARNING, "no object with id:"+id+" in database");
}
return result;
}
public List<Person> getAll(){
return getHibernateTemplate().find("from Person p");
}
}
</code></pre></div></div>
<p>Oh iya, contoh kode BDB di atas dapat diujicoba dengan membuat Main class sebagai berikut:</p>
<h3 id="mainjava">Main.java</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package tutorial.berkeleydb;
import java.io.File;
public class Main {
private static EntityStore store;
private static Environment env;
public static void main(String[] args) throws Exception {
openDatabase();
Person endy = new Person();
endy.setName("Endy Muhardin");
endy.setBirthdate(new SimpleDateFormat("dd-MM-yyyy").parse("17-08-1945"));
endy.setEmail("endymuhardin@yahoo.com");
PersonDao dao = new PersonDaoBerkeleyDB(store);
dao.save(endy);
System.out.println("======== Create one person with ID:"+endy.getId()+" ========");
System.out.println("======== Display all person from database ========");
List<Person> all = dao.getAll();
for (Person person : all) {
displayPerson(person);
}
System.out.println("======== Add one more person to database ========");
Person khalisa = new Person();
khalisa.setName("Khalisa Alayya");
khalisa.setBirthdate(new SimpleDateFormat("dd-MM-yyyy").parse("31-12-2000"));
khalisa.setEmail("me@khalisa.web.id");
dao.save(khalisa);
System.out.println("======== Display all person from database ========");
all = dao.getAll();
for (Person person : all) {
displayPerson(person);
}
System.out.println("======== Get person by ID "+endy.getId()+" from database ========");
Person endyFromDb = dao.getById(endy.getId());
displayPerson(endyFromDb);
System.out.println("======== Change person data with ID "+endy.getId()+" from database ========");
endyFromDb.setEmail("endy.muhardin@gmail.com");
dao.save(endyFromDb);
System.out.println("======== Display person with ID "+endy.getId()+" after update ========");
Person endyFromDb2 = dao.getById(endy.getId());
displayPerson(endyFromDb2);
closeDatabase();
}
private static void displayPerson(Person person) {
System.out.println("ID : "+person.getId());
System.out.println("Name : "+person.getName());
System.out.println("Birthdate : "+person.getBirthdate());
System.out.println("Address : "+person.getEmail());
}
private static void openDatabase(){
try {
EnvironmentConfig config = new EnvironmentConfig();
config.setAllowCreate(true);
config.setTransactional(true);
env = new Environment(new File("database"), config);
StoreConfig storeConfig = new StoreConfig();
storeConfig.setAllowCreate(true);
storeConfig.setTransactional(true);
store = new EntityStore(env, "PersonDatabase", storeConfig);
} catch (DatabaseException e) {
e.printStackTrace();
}
}
private static void closeDatabase() {
try {
store.close();
env.close();
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
</code></pre></div></div>
<p>Selain contoh kode yang ditampilkan di sini, sebetulnya BDB juga mendukung asosiasi One to One, One to Many, dan Many to Many. Sayang sekali saya belum sempat coba.</p>
<p>Dari percobaan sederhana ini, kita dapat menarik beberapa sisi positif dan negatif dari BerkeleyDB, juga kemungkinan kasus yang tepat dan tidak tepat untuk menggunakan BDB.</p>
<h3 id="positif">Positif</h3>
<ul>
<li>Ukuran jar kecil. Hanya butuh 1.1 MB untuk jar-nya BDB. Bandingkan jika kita gunakan MySQL + Hibernate. Jar mysql-connector 430KB, hibernate.jar 2MB, hibernate-annotation.jar 300KB, dependensi lainnya seperti ehcache, jta, cglib, asm, dan lainnya bisa mencapai 7MB total.</li>
<li>Tidak perlu berkutat dengan SQL. Object oriented 100%</li>
<li>Karena embedded, sangat cepat. Akses data instan langsung ke sumbernya. Bandingkan dengan rantai MySQL-network-hibernate-spring-aplikasi. Bagaikan langit dan bumi.</li>
</ul>
<h3 id="negatif">Negatif</h3>
<ul>
<li>Karena embedded, sulit dimanipulasi dengan tools lain. Berbeda dengan database RDBMS yang bisa diakses menggunakan command line, web-based interface, atau database management tools.</li>
<li>Karena tidak support SQL, tidak bisa dimanipulasi aplikasi lainnya. Misalnya aplikasi reporting.</li>
<li>Untuk sharing data dengan aplikasi lain, kita harus buatkan API via Java atau XML.</li>
<li>BDB mengandalkan Serialization untuk menyimpan object ke dalam database. Siapapun yang sudah pernah belajar Serialization pasti tahu bahwa penyakitnya ada pada class evolution. Kalau class kita berubah –misalnya menambah atau mengurangi field, apalagi rename class– BDB akan bingung, dan kita harus menulis kode program untuk melakukan migrasi.</li>
</ul>
<h3 id="kapan-menggunakan-bdb">Kapan menggunakan BDB</h3>
<ul>
<li>Data yang disimpan hanya digunakan sendiri. Misalnya penyimpanan data session HTTP, cache untuk username dan password, dsb.</li>
<li>Butuh kecepatan extra tinggi dan pemrograman yang mudah. Misalnya kita membuat server SMS gateway atau message processing. Butuh kecepatan tinggi, dan datanya kecil kemungkinan digunakan aplikasi lain.</li>
<li>Anda termasuk orang yang living on the edge. Selalu menggunakan tools yang aneh dan terbaru.</li>
</ul>
<h3 id="kapan-menggunakan-rdbms">Kapan menggunakan RDBMS</h3>
<ul>
<li>Data harus bisa diakses aplikasi lain seperti reporting.</li>
<li>Data berjumlah besar, sehingga sulit untuk dimigrasi apabila terjadi refactoring. (Ingat masalah Serialization)</li>
<li>Perusahaan sudah terlanjur beli Oracle atau DB2.</li>
</ul>
<p>Demikianlah .. semoga bermanfaat.
:D</p>
Menggunakan Eclipse BIRT [bagian 1]2006-09-29T16:54:11+07:00https://software.endy.muhardin.com/java/eclipse-birt<p>Programmer beraliran komersial tentunya sudah tidak asing dengan perlengkapan reporting seperti Crystal Report. Reporting tools ini berguna untuk membuat laporan dari sumber data yang sudah ada.</p>
<p>Cara kerjanya sederhana. Bagi yang belum pernah menggunakan aplikasi reporting dan ingin memahaminya secara sederhana, dapat menggunakan fasilitas mail merge yang dimiliki aplikasi <em>office</em> seperti Microsoft Word atau OpenOffice Writer.</p>
<p>Intinya, kita membuat satu template. Di dalam template tersebut kita isikan variabel-variabel yang kita ingin tampilkan. Pada saat dijalankan (runtime), variabel ini akan digantikan oleh template processor dengan data yang diambil dari sumber data eksternal.</p>
<p>Contoh umum penggunaan fasilitas mail merge adalah untuk mencetak label alamat pada undangan. Kita definisikan variabel yang akan tampil, misalnya:</p>
<ul>
<li>Nama</li>
<li>Alamat</li>
<li>Kota</li>
</ul>
<p>Kemudian, kita sediakan file lain di spreadsheet (MS Excel atau OO Calc) yang memuat data tersebut. Data yang disimpan bisa saja ratusan atau ribuan. Untuk Open Office, database yang digunakan tidak hanya dari spreadsheet, tapi bisa juga dari <em>database betulan</em> seperti MySQL atau Oracle.</p>
<p>Setelah template dan data siap, aplikasi dijalankan untuk melakukan <em>merge</em>. Hasilnya adalah ratusan label yang sudah terisi dengan data dari database.</p>
<p>Logika yang sama digunakan oleh perlengkapan reporting seperti Crystal Report dan kompetitor lainnya. Biasanya template dibuat dalam format XML. Selanjutnya, prosesnya sama. Desain templatenya, kemudian siapkan datanya.</p>
<p>Saya tidak akan membahas Crystal Report di sini. Kita akan membahas aplikasi reporting yang gratis, yaitu <a href="http://www.eclipse.org/birt" title="BIRT Homepage">Eclipse BIRT</a>. Sebagai bagian dari Eclipse, aplikasi ini gratis dan berkualitas tinggi.</p>
<p>Fitur BIRT juga tidak kalah dengan aplikasi reporting lain, diantaranya adalah:</p>
<ol>
<li>Berbagai pilihan datasource; RDBMS, XML, Text File, dan lain-lain. Lain-lain maksudnya di sini adalah sumber lain yang dapat diakses melalui scripting language BIRT.</li>
<li>Berbagai pilihan output; PDF, XLS, CSV, HTML, XML adalah format yang didukung. Untuk format lain, asalkan berbasis text, bisa menggunakan output XML yang kemudian diproses lagi menggunakan XSLT.</li>
<li>Berbagai pilihan integrasi. BIRT dapat dijalankan sebagai standalone report server, atau juga diembed (digabungkan) dengan aplikasi kita. Bila dijalankan sebagai standalone server, BIRT dapat berkomunikasi dengan aplikasi dengan bahasa pemrograman yang berbeda, misalnya PHP, Ruby, .Net, atau yang lainnya.</li>
<li>Visual Designer. Eclipse sudah melengkapi BIRT dengan database explorer, drag-and-drop query builder, dan fitur canggih lainnya. Kita akan lihat fitur ini dalam screenshot di bawah.</li>
<li>Integrated dengan IDE. Bila kita menggunakan Java, kita bisa coding di Eclipse, dan membuat report di Eclipse juga. Jadi tidak perlu menggunakan beberapa tools yang berbeda.</li>
</ol>
<p>Baiklah, mari kita coba saja.</p>
<p>Pertama, kita harus donlod dulu dari <a href="http://www.eclipse.org/birt" title="Homepage BIRT">homepagenya</a>. Ukurannya relatif besar, sekitar 200 MB. Tapi jangan khawatir, ada mirrornya di <a href="http://anak.kambing.vlsm.org" title="Mirror Kambing">server Universitas Indonesia</a>. Kalau kita sudah punya Eclipse terbaru, bisa langsung gunakan fitur updatenya. Jalankan downloadnya sebelum pulang kantor. Dengan demikian, besok pagi ketika kita datang, BIRT sudah terinstal.</p>
<p>Setelah terinstal, kita bisa langsung menggunakannya. BIRT terinstal lengkap bersama contoh database. <a href="http://www.eclipse.org/birt/phoenix/tutorial/basic/index.php" title="Cara penggunaan BIRT">Tutorial cara penggunaannya</a> juga cukup jelas dan lengkap. Sayangnya tidak ada screenshotnya. Jangan khawatir, pada artikel ini, saya akan sediakan screenshotnya.</p>
<p>Berikut adalah tampilan Eclipse pada saat sudah dijalankan.</p>
<p><a href="/images/uploads/2006/09/eclipse.png"><img src="/images/uploads/2006/09/eclipse.png" alt="Eclipse Interface " /></a></p>
<p>Selanjutnya, kita langsung membuat project baru. Klik File > New, kemudian pilih Report Project</p>
<p><a href="/images/uploads/2006/09/new-report-project.png"><img src="/images/uploads/2006/09/new-report-project.png" alt="Create Report Project " /></a></p>
<p>Beri nama projectnya. Kemudian klik Next. Eclipse akan menanyakan apakah kita ingin bekerja dalam Report Perspective. Jawab saja Yes.</p>
<p>Report perspective tampil seperti screenshot berikut</p>
<p><a href="/images/uploads/2006/09/eclipse-report.png"><img src="/images/uploads/2006/09/eclipse-report.png" alt="Report Perspective " /></a></p>
<p>Perhatikan di sebelah kiri ada tiga tab : Pallete, Data Explorer, dan Library. Kita akan gunakan tab ini untuk mendesain report.</p>
<p>Kemudian, mari kita buat report pertama kita. Datasource yang akan digunakan sudah disediakan Eclipse sebagai database sample. Sesuai tutorial Eclipse, kita akan membuat laporan yang berisi daftar nama pelanggan, dikelompokkan berdasarkan provinsi (State) dan kota (City).</p>
<p>Untuk membuat report baru, klik File > New > Report. Kalau pilihan Report belum ada, pilih Others dan cari di daftar yang tersedia, dalam kategori Business Intelligence and Reporting Tools.</p>
<p><a href="/images/uploads/2006/09/new-report.png"><img src="/images/uploads/2006/09/new-report.png" alt="Create Report " /></a></p>
<p>Beri nama reportnya</p>
<p><a href="/images/uploads/2006/09/new-report-1.png"><img src="/images/uploads/2006/09/new-report-1.png" alt="Define Report " /></a></p>
<p>Kemudian pilih templatenya. Supaya lebih seru, kita akan gunakan Blank Template</p>
<p><a href="/images/uploads/2006/09/new-report-blank2.png"><img src="/images/uploads/2006/09/new-report-blank2.png" alt="Blank Template " /></a></p>
<p>Selanjutnya, template report kita tampil di layar dalam Design View. Kita dapat berganti ke berbagai view melalui tab di bawah editor report.</p>
<p>Setelah report tampil, kita dapat menambahkan label. Tambahkan saja satu label untuk judul, yaitu Laporan Data Pelanggan. Jenis huruf, rata tengah, dan setting lainnya dapat dilakukan melalui toolbar yang ada di bagian bawah.</p>
<p><a href="/images/uploads/2006/09/report-design-label.png"><img src="/images/uploads/2006/09/report-design-label.png" alt="Add Label " /></a></p>
<p>Sebelum melangkah lebih jauh, kita perlu mendefinisikan Data Source untuk report ini. Buat Data Source baru melalui panel sebelah kiri.</p>
<p><a href="/images/uploads/2006/09/report-datasource-new.png"><img src="/images/uploads/2006/09/report-datasource-new.png" alt="Create DataSource " /></a></p>
<p>Pilihan datasource akan muncul. Kita akan menggunakan database sample yang sudah ada. Untuk project betulan, kita dapat gunakan database atau sumber data yang lainnya.</p>
<p><a href="/images/uploads/2006/09/report-datasource-new-1.png"><img src="/images/uploads/2006/09/report-datasource-new-1.png" alt="Select DataSource " /></a></p>
<p>Beri nama Sample di kolom Data Source Name, kemudian klik Finish.</p>
<p>Dari datasource yang ada, kita dapat mendefinsikan Data Set. Data Set ini adalah sebagian dari isi Data Source yang akan kita gunakan dalam report.</p>
<p><a href="/images/uploads/2006/09/report-dataset-new.png"><img src="/images/uploads/2006/09/report-dataset-new.png" alt="Create Dataset " /></a></p>
<p>Muncul pilihan datasource dan jenis data set. Kita bisa menggunakan lebih dari satu datasource dalam satu report. Untuk kali ini, cuma ada satu data source. Pilih tipe data set SQL Select Query. Jangan lupa beri nama yang deskriptif untuk dataset yang dibuat.</p>
<p><a href="/images/uploads/2006/09/report-dataset-new-1.png"><img src="/images/uploads/2006/09/report-dataset-new-1.png" alt="Create Dataset " /></a></p>
<p>Setelah kita tekan Next, akan muncul database explorer di panel kiri, dan SQL editor di kanan. Kita dapat melakukan drag and drop pada layar ini.</p>
<p><a href="/images/uploads/2006/09/report-dataset-new-2.png"><img src="/images/uploads/2006/09/report-dataset-new-2.png" alt="Database Explorer " /></a></p>
<p>Edit SQL menjadi seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select *
from
</code></pre></div></div>
<p>Kemudian drag-and-drop tabel Customer ke sebelah kanan <em>from</em>, sehingga kodenya menjadi seperti ini:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select *
from CLASSICMODELS.CUSTOMERS
</code></pre></div></div>
<p>Klik Next. Selanjutnya muncul Data Set Editor.</p>
<p><a href="/images/uploads/2006/09/report-dataset-new-3.png"><img src="/images/uploads/2006/09/report-dataset-new-3.png" alt="DataSet Editor " /></a></p>
<p>Kita tidak melakukan perubahan apa-apa di sini. Tapi bila ingin tahu apa isi tabelnya, kita dapat melihat <em>Preview Result</em> seperti ini:</p>
<p><a href="/images/uploads/2006/09/report-dataset-new-4.png"><img src="/images/uploads/2006/09/report-dataset-new-4.png" alt="Preview DataSet " /></a></p>
<p>Klik OK. Dataset siap digunakan.</p>
<p>Selanjutnya, kita akan tampilkan data pelanggan dalam bentuk tabel. Pilih Table di Pallete, dan letakkan di report. Kita akan tampilkan 4 kolom dan 1 baris detail. Kolom yang nantinya akan ditampilkan adalah:</p>
<ol>
<li>Provinsi</li>
<li>Kota</li>
<li>Nama Pelanggan</li>
<li>Nomer Telepon</li>
</ol>
<p>Tampilan yang dihasilkan adalah seperti ini:</p>
<p><a href="/images/uploads/2006/09/report-table-1.png"><img src="/images/uploads/2006/09/report-table-1.png" alt="Initial Table " /></a></p>
<p>Dari seluruh data pelanggan yang ada, kita akan kelompokkan berdasarkan provinsi, kemudian kota. Untuk itu, kita tambahkan Group di tabel. Caranya, klik tombol pemilih tabel, kemudian klik kanan di baris detail.</p>
<p><a href="/images/uploads/2006/09/report-table-2.png"><img src="/images/uploads/2006/09/report-table-2.png" alt="Insert Group " /></a></p>
<p>Group Editor akan muncul. Beri nama State, kemudian klik OK.</p>
<p><a href="/images/uploads/2006/09/report-table-3.png"><img src="/images/uploads/2006/09/report-table-3.png" alt="Group Editor " /></a></p>
<p>Hasilnya akan tampak seperti ini</p>
<p><a href="/images/uploads/2006/09/report-table-4.png"><img src="/images/uploads/2006/09/report-table-4.png" alt="Group by State " /></a></p>
<p>Untuk mengisikan State ke Group Row tersebut, drag-and-drop dari panel kiri ke kolom <em>paling kiri</em> di baris Group Header Row. Layar Select Data Binding akan muncul, langsung saja klik OK.</p>
<p><a href="/images/uploads/2006/09/report-table-5.png"><img src="/images/uploads/2006/09/report-table-5.png" alt="State Data Binding " /></a></p>
<p>Kita perlu memasukkan Group Row satu lagi untuk City. Caranya sama, yaitu dengan menambahkan Group Row di <em>bawah</em> Group Header State.</p>
<p><a href="/images/uploads/2006/09/report-table-6.png"><img src="/images/uploads/2006/09/report-table-6.png" alt="Insert Group Below " /></a></p>
<p>Beri nama City, kemudian klik OK. Lalu pilih field city dari Data Explorer di sebelah kiri, dan pasang di kolom kedua di Group Header Row 2. Hasilnya seperti ini:</p>
<p><a href="/images/uploads/2006/09/report-table-7.png"><img src="/images/uploads/2006/09/report-table-7.png" alt="Group by City " /></a></p>
<p>Terakhir, masukkan field CUSTOMERNAME dan PHONE ke Detail Row. Kolom judul CUSTOMERNAME kurang enak dibaca, jadi kita bisa ganti labelnya di baris paling atas menjadi Customer Name. Hasil akhirnya adalah seperti ini:</p>
<p><a href="/images/uploads/2006/09/report-table-8.png"><img src="/images/uploads/2006/09/report-table-8.png" alt="Final Design " /></a></p>
<p>Report kita sudah selesai. Silahkan disave, kemudian lihat previewnya. Kalau semua dilakukan dengan benar, kita akan melihat tampilan seperti ini:</p>
<p><a href="/images/uploads/2006/09/report-table-9.png"><img src="/images/uploads/2006/09/report-table-9.png" alt="Report Preview " /></a></p>
<p>Kita akan lihat bahwa data pelanggan sudah diurutkan dan dikelompokkan berdasarkan State dan City.</p>
<p>Kalau kita tekan menu <em>File</em>, kita akan menemui pilihan View Report in Web Viewer, as HTML, dan as PDF. Silahkan gunakan sesuai kebutuhan.</p>
<p><a href="/images/uploads/2006/09/report-table-10.png"><img src="/images/uploads/2006/09/report-table-10.png" alt="Export Format HTML PDF " /></a></p>
<p>Selamat mencoba.</p>
Membuat Gantt Chart dengan JFreeChart2006-09-28T22:33:24+07:00https://software.endy.muhardin.com/java/gantt-jfreechart<p><a href="http://www.jfree.org/jfreechart/" title="Homepage JFreeChart">JFreeChart</a> adalah library untuk menghasilkan chart dengan Java. Berbagai chart bisa dihasilkan, dari Pie Chart, Bar Chart, dan sebagainya.</p>
<p>Pada artikel ini, kita akan mencoba membuat Gantt Chart. Gantt chart adalah diagram yang menunjukkan rangkaian task, tanggal mulai, selesai, dan persentase kemajuannya. Bagi mereka yang pernah menggunakan aplikasi manajemen proyek pasti tau apa itu Gantt Chart.</p>
<p>Berikut output yang kita inginkan
<a href="/images/uploads/2006/09/gantt.png"><img src="/images/uploads/2006/09/gantt.png" alt="Gantt Chart " /></a></p>
<p>Diagram di atas dihasilkan dari sumber data sebagai berikut</p>
<table>
<thead>
<tr>
<th>Aktivas</th>
<th>Tanggal Mulai</th>
<th>Tanggal Selesai</th>
<th>Persentase Selesai</th>
</tr>
</thead>
<tbody>
<tr>
<td>UML Design</td>
<td>01-01-2006</td>
<td>03-01-2006</td>
<td>100 %</td>
</tr>
<tr>
<td>Coding</td>
<td>02-01-2006</td>
<td>03-01-2006</td>
<td>75 %</td>
</tr>
<tr>
<td>Testing</td>
<td>03-01-2006</td>
<td>14-01-2006</td>
<td>50 %</td>
</tr>
<tr>
<td>Integrate</td>
<td>04-01-2006</td>
<td>25-01-2006</td>
<td>25 %</td>
</tr>
</tbody>
</table>
<p>Untuk mengubah data tersebut menjadi chart, berikut langkah-langkah dan kode yang digunakan.</p>
<p>Pertama, kita harus buat data tersebut menjadi Task object.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Task design = new Task("UML Design", toDate("01-01-2006"), toDate("03-01-2006"));
Task coding = new Task("Coding", toDate("02-01-2006"), toDate("03-01-2006"));
Task test = new Task("Testing", toDate("03-01-2006"), toDate("14-01-2006"));
Task commit = new Task("Integrate", toDate("04-01-2006"), toDate("25-01-2006"));
</code></pre></div></div>
<p>Untuk memudahkan konversi tanggal, saya buat method seperti ini</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private static Date toDate(String date) throws ParseException {
return formatter.parse(date);
}
</code></pre></div></div>
<p>kemudian, kita set persentase kemajuan task.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>design.setPercentComplete(1);
coding.setPercentComplete(0.75);
test.setPercentComplete(0.50);
commit.setPercentComplete(0.25);
</code></pre></div></div>
<p>Task dapat dikelompokkan menjadi TaskSeries.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TaskSeries codingTasks = new TaskSeries("Coding Activities");
codingTasks.add(design);
codingTasks.add(coding);
codingTasks.add(test);
codingTasks.add(commit);
</code></pre></div></div>
<p>Dan kumpulan TaskSeries disebut TaskCollection</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TaskSeriesCollection allTasks = new TaskSeriesCollection();
allTasks.add(codingTasks);
</code></pre></div></div>
<p>TaskCollection ini digunakan untuk membuat chart.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JFreeChart chart = ChartFactory.createGanttChart("Coba Gantt Chart", "Task", "Tanggal", allTasks, false, false, false);
</code></pre></div></div>
<p>Terakhir, kita gambar chart menjadi file PNG</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ChartUtilities.saveChartAsPNG(new File("output/gantt.png"), chart, 400, 300);
</code></pre></div></div>
<p>Selain menjadi PNG, kita juga bisa menghasilkan file JPEG</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ChartUtilities.saveChartAsJPEG(new File("output/gantt.jpg"), chart, 400, 300);
</code></pre></div></div>
<p>Demikianlah cara membuat chart dengan JFreeChart. Selain Gantt chart masih banyak lagi fitur JFreeChart yang bagus. Silahkan download dan coba</p>
Membuat Installer dengan IzPack2006-09-27T18:49:25+07:00https://software.endy.muhardin.com/java/membuat-installer-dengan-izpack<p>Menggunakan izPack</p>
<p>Setelah kita selesai membuat aplikasi, what next? Tentunya mendistribusikan pada orang yang membutuhkan. Bagaimana caranya agar aplikasi kita mudah digunakan? Langkah pertama adalah dengan cara memudahkan proses instalasi.
Dalam tulisan ini, kita akan mempelajari cara menggunakan izPack, framework untuk membuat installer aplikasi, sehingga menginstal aplikasi akan semudah menekan tombol Next.</p>
<p>Para programmer tentunya sudah familiar dengan berbagai framework installer. Ada yang komersil seperti InstallShield, dan ada juga yang gratis seperti <a href="http://nsis.sourceforge.net">NSIS</a> dan izPack. Kelebihan izPack adalah dia berbasis Java, tidak seperti NSIS yang cuma bisa jalan di Windows.</p>
<p>Langkah pertama, mari kita <a href="http://www.izforge.com/izpack/downloads">download izPack</a>. IzPack disediakan sebagai installer, sehingga kita harus menginstalnya dulu sebelum bisa digunakan.</p>
<p>Selanjutnya, mari kita lihat struktur folder project kita yang ingin dibuatkan installernya. Struktur folder saya seperti ini:</p>
<p><a href="/images/uploads/2006/09/project-folder-before.png"><img src="/images/uploads/2006/09/project-folder-before.png" alt="Struktur folder project " /></a></p>
<p>Setelah terinstal di komputer pengguna, saya ingin foldernya seperti ini:</p>
<p><a href="/images/uploads/2006/09/project-folder-after.png"><img src="/images/uploads/2006/09/project-folder-after.png" alt="Hasil yang diinginkan " /></a></p>
<p>Perhatikan bahwa folder src tidak perlu diikutkan. Pengguna dapat memilih apakah mau menginstal source code atau tidak. Jadi, folder source code adalah optional. Folder dokumentasi dan plugin tambahan juga biasanya diinstal secara optional.</p>
<p>Berikutnya, tentukan screen yang akan tampil selama proses instalasi, biasanya adalah :</p>
<ol>
<li>Salam pembukaan, menyatakan kepada pengguna bahwa proses instalasi akan dimulai.</li>
<li>Informasi aplikasi, menjelaskan tentang aplikasi dan kegunaannya.</li>
<li>License Agreement</li>
<li>Pemilihan paket yang akan diinstal. Di sini pengguna dapat memilih apakah dia akan menginstal paket optional atau tidak.</li>
<li>Tujuan instalasi. Pengguna menentukan di folder mana dia akan menginstal.</li>
<li>Proses instalasi. Menunjukkan kemajuan proses instalasi. Biasanya menggunakan progress bar.</li>
<li>Instalasi selesai. Menampilkan pesan sukses atau gagal.</li>
</ol>
<p>Kerangka dari konfigurasi installer kita adalah sebagai berikut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <installation version="1.0">
<info> </info>
<guiprefs> </guiprefs>
<locale> </locale>
<resources> </resources>
<panels> </panels>
<packs> </packs>
</installation>
</code></pre></div></div>
<p>Isi dari tag info adalah sebagai berikut</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <info>
<appname>PlayBilling</appname>
<appversion>1.1-RC3</appversion>
<authors>
<author name="Endy Muhardin" email="endy@artivisi.com"/>
<author name="Anton Raharja" email="anton@ngoprek.org"/>
</authors>
<url>http://playbilling.sourceforge.net</url>
<javaversion>1.5.0</javaversion>
</info>
</code></pre></div></div>
<p>Bagian guiprefs menjelaskan tampilan yang akan digunakan. Kita akan pakai native saja, sesuai OS, supaya tampak alami. Kalau mau, kita dapat memilih beberapa tampilan Look & Feel yang tersedia.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <guiprefs width="800" height="600" resizable="yes"/>
</code></pre></div></div>
<p>Berikutnya, pilihan bahasa instalasi. Untuk mudahnya, gunakan bahasa Inggris saja.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <locale>
<langpack iso3="eng" />
</locale>
</code></pre></div></div>
<p>Untuk bagian resource, kita perlu mendefinisikan file-file yang dibutuhkan installer. File-file ini harus sudah ada.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <resources>
<res src="installer/application-description.html" id="HTMLInfoPanel.info"/>
<res src="installer/gpl.txt" id="LicencePanel.licence"/>
</resources>
</code></pre></div></div>
<p>Tentukan urutan screen seperti sudah dijelaskan di atas.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <panels>
<panel classname="HelloPanel"/>
<panel classname="HTMLInfoPanel"/>
<panel classname="LicencePanel"/>
<panel classname="PacksPanel"/>
<panel classname="TargetPanel"/>
<panel classname="InstallPanel"/>
<panel classname="SimpleFinishPanel"/>
</panels>
</code></pre></div></div>
<p>Bagian packs menentukan folder-folder yang akan diinstal. Sintaksnya mirip dengan Ant.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <packs>
<pack name="Base" required="yes">
<description>Base System files</description>
<fileset dir="." targetdir="$INSTALL_PATH">
<include name="ext/jetty/*"/>
<include name="webapp/**/*"/>
<include name="*.sh"/>
<include name="*.bat"/>
</fileset>
</pack>
<pack name="Source" required="no">
<description>Source code and test files</description>
<fileset dir="." targetdir="$INSTALL_PATH">
<include name="src/**/*"/>
<include name="lib/**/*"/>
<include name="*.xml"/>
</fileset>
</pack>
</packs>
</code></pre></div></div>
<p>Variabel $INSTALL_PATH akan diisikan sesuai folder tujuan yang dipilih pengguna.</p>
<p>Ok, file konfigurasi installer kita sudah selesai. Sekarang tinggal diotomasi sehingga bisa dipanggil lewat <a href="http://ant.apache.org">Ant</a>.
Ada beberapa informasi yang dibutuhkan oleh izPack, yaitu:</p>
<ul>
<li>lokasi konfigurasi instalasi (file xml yang baru saja kita buat)</li>
<li>nama file installer yang akan dihasilkan (misalnya MyProject-installer.jar)</li>
<li>jenis instalasi (desktop atau web)</li>
<li>base folder (folder acuan untuk menentukan lokasi relatif folder yang lain)</li>
<li>lokasi instalasi izPack</li>
</ul>
<p>Berikut adalah target untuk Ant</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <taskdef name="izpack"
classpath="${izpack_install_dir}/lib/compiler.jar"
classname="com.izforge.izpack.ant.IzPackTask"
/>
<target name="build-installer">
<izpack input="installer/playbilling-installer.xml"
output="dist/playbilling-installer.jar"
installerType="standard"
basedir="."
izPackDir="${izpack_install_dir}"
/>
</target>
</code></pre></div></div>
<p>Setelah Ant target dijalankan, kita akan mendapatkan file playbilling-installer.jar di dalam folder dist.
Mari kita coba jalankan file installer tersebut.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar dist/playbilling-installer.jar
</code></pre></div></div>
<p>Kita akan disodori tampilan selamat datang</p>
<p><a href="/images/uploads/2006/09/install-screen-1.png"><img src="/images/uploads/2006/09/install-screen-1.png" alt="Screen Selamat Datang " /></a></p>
<p>Klik Next, dan kita akan melihat keterangan tentang <a href="http://playbilling.sourceforge.net">PlayBilling</a>. Keterangan ini langsung dicopy-paste dari homepage PlayBilling</p>
<p><a href="/images/uploads/2006/09/install-screen-2.png"><img src="/images/uploads/2006/09/install-screen-2.png" alt="Deskripsi Aplikasi " /></a></p>
<p>Next lagi, dan muncullah license agreement, yaitu GPL, yang diambil langsung dari <a href="http://www.gnu.org/copyleft/gpl.txt">website GPL</a>.</p>
<p><a href="/images/uploads/2006/09/install-screen-3.png"><img src="/images/uploads/2006/09/install-screen-3.png" alt="GPL License " /></a></p>
<p>Screen selanjutnya adalah pilihan paket yang akan diinstal. Perhatikan bahwa paket wajib diburamkan dan tidak bisa diklik.</p>
<p><a href="/images/uploads/2006/09/install-screen-4.png"><img src="/images/uploads/2006/09/install-screen-4.png" alt="Pilihan paket instalasi " /></a></p>
<p>Setelah itu, kita akan ditanya lokasi instalasi.</p>
<p><a href="/images/uploads/2006/09/install-screen-5.png"><img src="/images/uploads/2006/09/install-screen-5.png" alt="Pilihan lokasi instalasi " /></a></p>
<p>Pilih lokasi yang sesuai, kemudian Next. Instalasi akan segera dimulai.</p>
<p><a href="/images/uploads/2006/09/install-screen-6.png"><img src="/images/uploads/2006/09/install-screen-6.png" alt="Proses Instalasi " /></a></p>
<p>Sukses … !</p>
<p><a href="/images/uploads/2006/09/install-screen-7.png"><img src="/images/uploads/2006/09/install-screen-7.png" alt="Sukses " /></a></p>
<p>Sebagai bonus, izPack juga akan membuatkan Uninstaller. Coba jalankan</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar /home/endy/PlayBilling/Uninstaller/uninstaller.jar
</code></pre></div></div>
<p><a href="/images/uploads/2006/09/uninstall-screen-1.png"><img src="/images/uploads/2006/09/uninstall-screen-1.png" alt="Uninstall " /></a></p>
<p>Klik OK, dan aplikasi akan dihilangkan dari komputer Anda.</p>
<p>Selamat mencoba. Semoga bermanfaat.</p>
Tapestry vs Spring MVC2006-09-27T15:13:51+07:00https://software.endy.muhardin.com/java/tapestry-vs-spring<p>Dalam beberapa project terakhir, saya menggunakan <a href="http://www.springframework.org" title="Spring Framework">Spring</a> MVC untuk membuat presentation layer. Spring sangat fleksibel dan mudah dipelajari. Ini menambah fleksibilitas dalam menyusun tim programmer. Dengan kemudahannya, programmer yang sudah pernah membuat aplikasi web –baik menggunakan Struts, PHP, Ruby on Rails, atau bahkan yang cuma mengerti servlet– dapat beradaptasi dalam hitungan hari dan langsung produktif.</p>
<p>Di lain pihak, saya sering sekali mendengar kehebatan <a href="http://tapestry.apache.org" title="Tapestry Framework">Tapestry</a>. Para pendukungnya menggembar-gemborkan peningkatan produktifitas (baca: kecepatan membuat aplikasi) yang signifikan. Harga yang harus dibayar untuk produktifitas tersebut adalah kompleksitas. Tapestry terkenal sulit. Dia menggunakan paradigma event-driven. Jadi kita harus berpikir seolah-olah kita membangun aplikasi desktop dengan Swing atau VB. Kompleksitas ini menyebabkan kita tidak bisa sembarangan menyusun tim programmer. Butuh orang yang pemahaman Java-nya menengah ke atas agar aplikasi dapat diselesaikan dengan baik.</p>
<p>Setelah merasa nyaman menggunakan Spring di beberapa project terakhir, saya putuskan untuk keluar dari zona aman dan belajar Tapestry. Masa depan aplikasi web nampaknya adalah component-mania. Berbagai komponen siap pakai yang tersedia di pasaran (baik gratis maupun bayar) yang tinggal kita tempelkan di aplikasi kita, akan menjadi trend. Terutama dengan maraknya penggunaan Ajax belakangan ini. Pasti akan banyak orang yang membuat komponen Ajax-ready yang bisa langsung digunakan.</p>
<p>Prediksi ini diperkuat dengan presentasi Chuk Mun Lee di Sun Developer Day 2006 kemarin. Dia menunjukkan bagaimana cepatnya orang bisa membuat aplikasi web. Cukup mendownload kumpulan komponen dari <a href="https://ajax.dev.java.net" title="Ajax-based component">jMaki</a>, dan langsung <em>menggambar</em> di IDE. Kalau para pesaing bekerja seperti ini, sementara kita masih secara manual memparsing request, wah … kita akan ketinggalan.</p>
<p>Sebagai titik awal pelajaran, saya akan mulai dari Spring Framework. Ada beberapa kegiatan utama dalam pembangunan aplikasi web yang terjadi berulang-ulang. Dalam membandingkan Spring vs Tapestry, kegiatan-kegiatan ini akan menjadi kriteria penilaian. Dalam setiap kegiatan, saya akan memberi nilai dan menyatakan pemenangnya. Masing-masing kegiatan bobotnya berbeda, tergantung frekuensi kemunculannya dalam aplikasi.</p>
<p>Berikut adalah kriteria penilaiannya :</p>
<ol>
<li>Hello World. Yaitu kegiatan setup pertama kali sampai Hello World muncul. Bobotnya cuma 5%, karena kegiatan ini cuma dilakukan sekali saja di awal.</li>
<li>Form binding. Bagaimana semua variabel dalam form bisa diambil sebagai satu domain object. Bobotnya 20%, karena ini adalah kegiatan wajib, inti dari aplikasi.</li>
<li>Validasi form. Bagaimana mencegah invalid input, baik secara server-side maupun client-side. Bobotnya 20%, saudara kembar dengan Form Binding.</li>
<li>Web Asset. Bagaimana penanganan image, css, javascript. Bobotnya 10%.</li>
<li>Security. Bagaimana kemudahan mengaktifkan otentikasi dan otorisasi. Bobotnya 20%. Ini juga bagian yang penting. Kalau penanganan di sisi ini tidak elegan, akan banyak duplikasi kode bertebaran di dalam kode program.</li>
<li>Ajax readiness. Bobotnya 10%.</li>
<li>Reusability. Bagaimana kemudahan menggunakan komponen/library yang sudah ada. Bobotnya 10%.</li>
</ol>
<p><em>Disclaimer: dalam kegiatan membandingkan ini, tidak ada tujuan untuk menjelekkan yang satu terhadap yang lainnya. Ini murni adalah pekerjaan harian software designer, yaitu memilih framework dan teknologi. Berbagai saran dan masukan dari pendukung masing-masing framework sangat diharapkan.</em></p>
<p>Hello World akan dibahas di artikel selanjutnya.</p>
Ruthless Testing 42006-09-26T19:39:44+07:00https://software.endy.muhardin.com/java/ruthless-testing-4<p>Database testing merupakan suatu kegiatan yang sulit, apalagi dalam dunia Java. Bayangkan saja, untuk melakukan testing database, kita harus melakukan :</p>
<ol>
<li>
<p>Membuat skema database. Skema terdiri dari dari tabel, constraint, view, dan lainnya.</p>
</li>
<li>
<p>Mengisi sampel data.</p>
</li>
<li>
<p>Mengeksekusi query.</p>
</li>
<li>
<p>Periksa ke database untuk memastikan hasilnya benar.</p>
</li>
<li>
<p>Koreksi atau lanjutkan sesuai dengan hasil di nomer 4.</p>
</li>
</ol>
<p>Semua kegiatan di atas sangat membosankan dan melelahkan untuk diulang-ulang setiap kali melakukan perubahan kode program. Akibatnya programmer menjadi bosan dan malas mengetes kode programnya. Padahal kode akses database merupakan salah satu bagian krusial dalam aplikasi.</p>
<p>Pada artikel ini kita akan membahas cara membuat kode akses database yang mudah dan cepat, dilengkapi dengan testing yang menyeluruh dan menyenangkan.</p>
<p>Saya biasanya menggunakan kombinasi Spring Framework dan Hibernate. Dengan kombinasi ini, kita dapat membuat aplikasi dengan sangat cepat.</p>
<p>Kita mulai dengan struktur tabel sederhana. Misalnya kita memiliki class Siswa, sebagai berikut:</p>
<h3 id="class-siswa">Class Siswa</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Siswa</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">nama</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">tanggalLahir</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">id</span><span class="o">;</span> <span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getNama</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">nama</span><span class="o">;</span> <span class="o">}</span>
<span class="kd">public</span> <span class="nc">Date</span> <span class="nf">getTanggalLahir</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">tanggalLahir</span><span class="o">;</span> <span class="o">}</span>
<span class="c1">// setter method </span>
<span class="o">}</span>
</code></pre></div></div>
<p>class ini akan disimpan di database dengan struktur tabel (MySQL) sebagai berikut:</p>
<h3 id="mysql-schemasql">mysql-schema.sql</h3>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">TBL_SISWA</span> <span class="p">(</span>
<span class="n">id_siswa</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="n">nama</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">),</span>
<span class="n">tanggal_lahir</span> <span class="nb">DATE</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Dengan menggunakan Hibernate, kita tidak perlu menulis SQL query. Sebagai gantinya, kita harus memberitahu Hibernate tentang skema database kita. Caranya adalah dengan menambahkan annotation sehingga class kita menjadi seperti ini:</p>
<h3 id="siswajava">Siswa.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.hibernate</span><span class="o">;</span>
<span class="nd">@Entity</span>
<span class="nd">@Table</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"TBL_SISWA"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Siswa</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">nama</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">tanggalLahir</span><span class="o">;</span>
<span class="nd">@Id</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"id_siswa"</span><span class="o">)</span>
<span class="nd">@GeneratedValue</span><span class="o">(</span><span class="n">strategy</span><span class="o">=</span><span class="nc">GenerationType</span><span class="o">.</span><span class="na">AUTO</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">id</span><span class="o">;</span> <span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getNama</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">nama</span><span class="o">;</span> <span class="o">}</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"tanggal_lahir"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Date</span> <span class="nf">getTanggalLahir</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">tanggalLahir</span><span class="o">;</span> <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pembaca yang teliti segera protes, “Ada yang ketinggalan!! Di atas public String getNama() belum ada @Column” <br />
Tidak, itu bukan kelupaan. Tapi memang Hibernate dapat dengan cerdas menebak nama kolom. <em>public String getNama</em> akan diterjemahkan menjadi kolom <em>nama</em>. Bahkan seandainya nama tabel kita <em>SISWA</em> (bukannya <em>TBL_SISWA</em>), kita tidak perlu menuliskan @Table.</p>
<p>Oke. Sekarang mapping sudah selesai. Saatnya membuat kode untuk mengisi record (create), membaca record (read), mengubah record (update), dan menghapus record (delete). Empat serangkai operasi database ini dikenal dengan istilah CRUD.</p>
<p>Pertama, kita buat interface dulu, kode yang mendefinisikan operasi CRUD ini.</p>
<h3 id="siswadaojava">SiswaDao.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.hibernate</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">SiswaDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">create</span><span class="o">(</span><span class="nc">Siswa</span> <span class="n">s</span><span class="o">);</span>
<span class="kd">public</span> <span class="nc">Siswa</span> <span class="nf">getById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">);</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">update</span><span class="o">(</span><span class="nc">Siswa</span> <span class="n">s</span><span class="o">);</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span><span class="o">(</span><span class="nc">Siswa</span> <span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pembuatan interface berguna supaya kapan-kapan kalau kita sudah bosan dengan Hibernate, kita bisa mengganti dengan implementasi yang lainnya, misalnya iBatis atau JDBC biasa.</p>
<p>Berikut implementasi operasi CRUD dengan Spring dan Hibernate.</p>
<h3 id="siswadaohibernatejava">SiswaDaoHibernate.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.hibernate</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SiswaDaoHibernate</span> <span class="kd">extends</span> <span class="nc">HibernateDaoSupport</span> <span class="kd">implements</span> <span class="nc">SiswaDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">create</span><span class="o">(</span><span class="nc">Siswa</span> <span class="n">s</span><span class="o">){</span>
<span class="n">getHibernateTemplate</span><span class="o">().</span><span class="na">save</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Siswa</span> <span class="nf">getById</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">){</span>
<span class="k">return</span> <span class="o">(</span><span class="nc">Siswa</span><span class="o">)</span> <span class="n">getHibernateTemplate</span><span class="o">().</span><span class="na">load</span><span class="o">(</span><span class="nc">Siswa</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">update</span><span class="o">(</span><span class="nc">Siswa</span> <span class="n">s</span><span class="o">){</span>
<span class="n">getHibernateTemplate</span><span class="o">().</span><span class="na">update</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span><span class="o">(</span><span class="nc">Siswa</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
<span class="n">getHibernateTemplate</span><span class="o">().</span><span class="na">delete</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Lagi-lagi ada yang bertanya, “Kok tidak ada kode untuk menangani koneksi database? Mana connect dan disconnect dengan database? Mana kode untuk begin dan commit transaction?”</p>
<p>Semua kode birokratis yang ditanyakan barusan akan diinisialisasi melalui Spring Framework. Sebenarnya kita bisa saja menginisialisasi sendiri melalui kode program biasa. Tapi akan lebih efisien dan konsisten apabila kita menggunakan Spring Framework, seperti akan kita lihat sebentar lagi.</p>
<p>Berikut adalah deklarasi SiswaDaoHibernate, DataSource koneksi database, dan konfigurasi transaction :</p>
<h3 id="belajarxml">belajar.xml</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><beans></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"siswaDaoAsli"</span> <span class="na">class=</span><span class="s">"tutorial.hibernate.SiswaDaoHibernate"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"sessionFactory"</span><span class="nt">><ref</span> <span class="na">bean=</span><span class="s">"sessionFactory"</span><span class="nt">/></property></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"sessionFactory"</span> <span class="na">class=</span><span class="s">"org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dataSource"</span> <span class="na">ref=</span><span class="s">"dataSource"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"annotatedClasses"</span><span class="nt">></span>
<span class="nt"><list></span>
<span class="nt"><value></span>tutorial.hibernate.Siswa<span class="nt"></value></span>
<span class="nt"></list></span>
<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"hibernateProperties"</span><span class="nt">></span>
<span class="nt"><props></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"hibernate.hbm2ddl.auto"</span><span class="nt">></span>create<span class="nt"></prop></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"hibernate.dialect"</span><span class="nt">></span>org.hibernate.dialect.MySQLInnoDBDialect<span class="nt"></prop></span>
<span class="nt"></props></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"dataSource"</span> <span class="na">class=</span><span class="s">"org.springframework.jdbc.datasource.DriverManagerDataSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"driverClassName"</span> <span class="na">value=</span><span class="s">"com.mysql.jdbc.Driver"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"url"</span> <span class="na">value=</span><span class="s">"jdbc:mysql://localhost/belajar"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">value=</span><span class="s">"belajar"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">value=</span><span class="s">"belajar"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"siswaDao"</span> <span class="na">class=</span><span class="s">"org.springframework.transaction.interceptor.TransactionProxyFactoryBean"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"transactionManager"</span> <span class="na">ref=</span><span class="s">"transactionManager"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"target"</span> <span class="na">ref=</span><span class="s">"siswaDaoAsli"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"transactionAttributes"</span><span class="nt">></span>
<span class="nt"><props></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"*"</span><span class="nt">></span>PROPAGATION_REQUIRED,readOnly<span class="nt"></prop></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"create*"</span><span class="nt">></span>PROPAGATION_REQUIRED<span class="nt"></prop></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"update*"</span><span class="nt">></span>PROPAGATION_REQUIRED<span class="nt"></prop></span>
<span class="nt"><prop</span> <span class="na">key=</span><span class="s">"delete*"</span><span class="nt">></span>PROPAGATION_REQUIRED<span class="nt"></prop></span>
<span class="nt"></props></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"transactionManager"</span> <span class="na">class=</span><span class="s">"org.springframework.orm.hibernate3.HibernateTransactionManager"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"sessionFactory"</span> <span class="na">ref=</span><span class="s">"sessionFactory"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>Object yang kita gunakan adalah siswaDao. Object ini dirangkai dari berbagai object lain, diantaranya adalah koneksi database (dataSource), transaction interceptor (gunakan transaction untuk method create<em>, update</em>, delete*), dan transaction manager untuk mengelola transaction Hibernate.</p>
<p>Perhatikan kode ini:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><prop</span> <span class="na">key=</span><span class="s">"hibernate.hbm2ddl.auto"</span><span class="nt">></span>create<span class="nt"></prop></span>
</code></pre></div></div>
<p>Baris kode di atas akan men-drop seluruh tabel dan constraint di database, dan membuat ulang semuanya. Dengan demikian, database kita akan fresh seperti baru.</p>
<p><strong>PERHATIAN!!! Jangan dijalankan di database production!!</strong></p>
<p>Berikut adalah unit test untuk class SiswaDaoHibernate:</p>
<h3 id="siswadaohibernatetestjava">SiswaDaoHibernateTest.java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.hibernate</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SiswaDaoHibernateTest</span> <span class="kd">extends</span> <span class="nc">TestCase</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">ApplicationContext</span> <span class="n">ctx</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">SiswaDao</span> <span class="n">dao</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">DataSource</span> <span class="n">ds</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Connection</span> <span class="n">conn</span><span class="o">;</span>
<span class="kd">static</span> <span class="o">{</span>
<span class="n">ctx</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ClassPathXmlApplicationContext</span><span class="o">(</span><span class="s">"belajar.xml"</span><span class="o">);</span>
<span class="n">dao</span> <span class="o">=</span> <span class="o">(</span><span class="nc">SiswaDao</span><span class="o">)</span><span class="n">ctx</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"siswaDao"</span><span class="o">);</span>
<span class="n">ds</span> <span class="o">=</span> <span class="o">(</span><span class="nc">DataSource</span><span class="o">)</span><span class="n">ctx</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"dataSource"</span><span class="o">););</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setUp</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">ds</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">tearDown</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">conn</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCreate</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">Siswa</span> <span class="n">endy</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Siswa</span><span class="o">();</span>
<span class="n">endy</span><span class="o">.</span><span class="na">setNama</span><span class="o">(</span><span class="s">"Endy Muhardin"</span><span class="o">);</span>
<span class="n">endy</span><span class="o">.</span><span class="na">setTanggalLahir</span><span class="o">(</span><span class="k">new</span> <span class="nc">SimpleDateFormat</span><span class="o">(</span><span class="s">"dd-MM-yyyy"</span><span class="o">).</span><span class="na">parse</span><span class="o">(</span><span class="s">"17-08-1945"</span><span class="o">));</span>
<span class="n">dao</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">endy</span><span class="o">);</span>
<span class="c1">// mari kita test</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"SELECT * FROM TBL_USER WHERE nama='Endy Muhardin'"</span><span class="o">;</span>
<span class="nc">ResultSet</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">createStatement</span><span class="o">().</span><span class="na">executeQuery</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="s">"harusnya ada minimal satu record"</span><span class="o">,</span> <span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">());</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"coba cek tanggal lahir"</span><span class="o">,</span> <span class="n">rs</span><span class="o">.</span><span class="na">getDate</span><span class="o">(</span><span class="s">"tanggal_lahir"</span><span class="o">),</span> <span class="k">new</span> <span class="nc">SimpleDateFormat</span><span class="o">(</span><span class="s">"dd-MM-yyyy"</span><span class="o">).</span><span class="na">parse</span><span class="o">(</span><span class="s">"17-08-1945"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Nah, dengan adanya unit test di atas, kita tidak perlu lagi secara manual menjalankan kode test, kemudian memeriksa isi database.</p>
<p>Sekarang, bagaimana mengetes method getById? Apakah kita harus melakukan <em>insert</em> dulu baru kemudian menjalankan <em>dao.getById(1)</em> ?</p>
<p>Tidak perlu. Kita dapat menggunakan DBUnit untuk menginisialisasi sampel data.
Kita buat sampel data (fixture) sebagai berikut:</p>
<h3 id="siswa-fixturexml">siswa-fixture.xml</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><dataset></span>
<span class="nt"><TBL_USER</span> <span class="na">id=</span><span class="s">"99"</span>
<span class="na">nama=</span><span class="s">"Khalisa Alayya"</span>
<span class="na">tanggal_lahir=</span><span class="s">"2005-12-31"</span>
<span class="nt">/></span>
<span class="nt"></dataset></span>
</code></pre></div></div>
<p>Sekarang, pastikan fixture di atas dijalankan sebelum setiap test dieksekusi. Caranya adalah dengan menambahkan kode berikut di method setUp:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">setUp</span><span class="o">()</span> <span class="o">{</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">ds</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
<span class="c1">// inisialisasi koneksi DBUnit</span>
<span class="nc">DatabaseConnection</span> <span class="n">dbUnitConn</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatabaseConnection</span><span class="o">(</span><span class="n">conn</span><span class="o">);</span>
<span class="c1">// inisialisasi fixture</span>
<span class="nc">FlatXmlDataSet</span> <span class="n">fixture</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FlatXmlDataSet</span><span class="o">(</span><span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="s">"siswa-fixtures.xml"</span><span class="o">));</span>
<span class="nc">DatabaseOperation</span><span class="o">.</span><span class="na">CLEAN_INSERT</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">dbUnitConn</span><span class="o">,</span><span class="n">fixture</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Kode ini :</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">DatabaseOperation</span><span class="o">.</span><span class="na">CLEAN_INSERT</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">dbUnitConn</span><span class="o">,</span><span class="n">fixture</span><span class="o">);</span>
</code></pre></div></div>
<p>akan menghapus semua data yang ada di database, dan mengisinya dengan satu record, yaitu <em>Khalisa Alayya</em>.</p>
<p>Sehingga kita dapat membuat kode test seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">testGetById</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Siswa</span> <span class="n">khalisa</span> <span class="o">=</span> <span class="n">dao</span><span class="o">.</span><span class="na">getById</span><span class="o">(</span><span class="mi">99</span><span class="o">);</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">khalisa</span><span class="o">);</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"Khalisa Alayya"</span><span class="o">,</span> <span class="n">khalisa</span><span class="o">.</span><span class="na">getNama</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Berikut adalah Ant script untuk mengeksekusi test di atas:</p>
<h3 id="buildxml">build.xml</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><project</span> <span class="na">name=</span><span class="s">"belajar-hibernate"</span> <span class="na">default=</span><span class="s">"test"</span> <span class="na">basedir=</span><span class="s">"."</span><span class="nt">></span>
<span class="c"><!-- silahkan lengkapi dengan target compile --></span>
<span class="nt"><target</span> <span class="na">name=</span><span class="s">"test"</span> <span class="na">depends=</span><span class="s">"compile"</span><span class="nt">></span>
<span class="nt"><junit</span> <span class="na">haltonfailure=</span><span class="s">"true"</span> <span class="na">fork=</span><span class="s">"true"</span> <span class="na">printsummary=</span><span class="s">"yes"</span><span class="nt">></span>
<span class="nt"><classpath</span> <span class="na">refid=</span><span class="s">"project-classpath"</span><span class="nt">/></span>
<span class="nt"><formatter</span> <span class="na">type=</span><span class="s">"xml"</span><span class="nt">/></span>
<span class="nt"><batchtest</span> <span class="na">todir=</span><span class="s">"report/junit"</span><span class="nt">></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"bin"</span> <span class="na">includes=</span><span class="s">"**/*Test.class"</span><span class="nt">/></span>
<span class="nt"></batchtest></span>
<span class="nt"></junit></span>
<span class="nt"></target></span>
<span class="nt"></project></span>
</code></pre></div></div>
<p>Silahkan jalankan berkali-kali sepuasnya. Rangkaian kode di atas akan dengan senang hati melakukan:</p>
<ol>
<li>
<p>Pembuatan schema</p>
</li>
<li>
<p>Mengisi sampel data</p>
</li>
<li>
<p>Menjalankan kode program</p>
</li>
<li>
<p>Memeriksa hasilnya di database</p>
</li>
</ol>
<p>Tidak perlu memelototi <em>phpmyadmin</em> lagi :D</p>
<p>Semoga bermanfaat.</p>
Wiki2006-09-18T23:40:24+07:00https://software.endy.muhardin.com/aplikasi/wiki<p>Bagi mereka yang belum tahu, wiki adalah website yang bisa diedit. Contohnya ada di <a href="http://www.wikipedia.org" title="Bapak moyangnya wiki">Wikipedia</a></p>
<p>Biasanya wiki digunakan untuk membuat dokumentasi. Sifatnya yang bisa diedit siapa saja menyebabkan banyak orang bisa langsung berkontribusi mengisi wiki. Tidak perlu direpotkan dengan birokrasi aplikasi CMS seperti biasanya.</p>
<p>Ada banyak aplikasi wiki yang tersedia. Dibuat dalam berbagai bahasa pemrograman. Biasanya, aplikasi wiki diinstal di web server dan diakses oleh orang banyak.</p>
<p>Selain yang diinstal di web server, ada juga yang digunakan di desktop. Saya sendiri menggunakan <a href="http://zoidberg.student.utwente.nl/zim/" title="Homepage Zim">Zim Desktop Wiki</a> di komputer saya untuk menulis buku.</p>
<p>Buat yang malas (atau tidak bisa) menginstal, bisa menggunakan aplikasi wiki web based yang tidak perlu diinstal di server. Gunakan <a href="http://www.tiddlywiki.com/" title="Standalone Web based Wiki">Tiddly Wiki</a>. Wiki anda tinggal ditulis di komputer sendiri, kemudian diupload ketika sudah selesai.</p>
<p>Saking banyaknya orang yang membuat aplikasi wiki, akhirnya ada yang menyediakan one stop solution untuk memilih wiki. Silahkan kunjungi <a href="http://www.wikimatrix.org" title="Wiki Matrix, bandingkan dan pilih wiki favorit">wikimatrix</a> untuk membandingkan berbagai aplikasi wiki. Dia juga menyediakan wizard untuk membantu pemilihan aplikasi wiki yang sesuai.</p>
<p>Selamat mengeksplorasi. Semoga bermanfaat.</p>
Technorati2006-08-31T22:34:49+07:00https://software.endy.muhardin.com/aplikasi/technorati<p>Orang-orang pada rame Technorati. Pengen tau barang apa sih ini.</p>
<p>Buat yang udah merasakan manfaatnya, tolong kasi masukan dong.</p>
<p>Terima kasih.</p>
IPK Tiarap2006-08-29T19:58:03+07:00https://software.endy.muhardin.com/life/ipk-tiarap<p><em>Disclaimer : tiap perusahaan dan interviewer beda-beda. Ini bukan patokan absolut. Yang saya tuliskan di sini hanyalah pengalaman pribadi disertai bumbu penyedap. Saya tidak bertanggung jawab atas segala rekomendasi di bawah. Penggunaan tips di bawah sepenuhnya adalah resiko pengguna.</em></p>
<p>Di milis Alumni STTTelkom, ada posting menarik, sebagai berikut:</p>
<blockquote>
<p>mengapa sih perusahaan membuat rumusan sakral untuk rekrutasi pegawai yakni
IPK >= 2.75 ???</p>
</blockquote>
<p>Segera saja saya merasa berkompeten untuk menjawabnya. Bukan apa-apa, IPK saya yang <em>cuma</em> 2.69 kan termasuk yang melanggar <em>rumusan sakral</em> di atas :P.</p>
<p>Berikut jawaban saya, saya paste di sini supaya khalayak ramai bisa ikut membaca dan berkomentar.</p>
<blockquote>
<p>Alasan utama membatasi IP adalah untuk menyusutkan jumlah pelamar !!!</p>
</blockquote>
<p>Batasan ini biasanya ada di posisi yang demandnya dikit, tapi supplynya berlimpah ruah.
Misalnya, entry level position, di mana kandidatnya adalah fresh grad semua
:D.</p>
<p>Coba bayangkan Anda sebagai petugas penerima karyawan baru.
Ada 15 tumpukan lamaran di meja Anda, masing-masing berisi 100 lamaran. Dan masih ada 23 tumpukan lagi di meja pojok ruangan. Kemudian office-boy datang dan mengantarkan 40 tumpukan lagi sambil berkata, “Di depan masih ada 4 kardus lagi Pak, sebentar saya mau ambil troli dulu .. gak kuat ngangkatnya.”</p>
<p>Nah, kuis singkat. Bagaimana cara memproses lamaran tersebut?
Jawaban bisa dikirim ke 8080 dengan SMS KUIS</p>
<p>Eh, tidak perlu … akan saya jawab sendiri.</p>
<p>Begini caranya.
Segera setelah si office-boy datang dengan troli dan 4 kardus lamaran, bilang ke dia, “Mas, tolong bantu saya ya. Coba ini lamaran dibuka, trus diperiksa satu-satu. Yang IPKnya lebih besar dari 2.75 kumpulkan di meja ini. Sisanya masukkan ke kardus kosong. Nanti kalo ada tukang loak lewat, dikiloin aja. Uangnya boleh buat Mas beli rokok.”</p>
<p>Beberapa jam kemudian, coba periksa hasil pekerjaan si Mas. Kalau tumpukan yang lolos masih tinggi, bilang ke dia, “Mas, revisi sedikit. Yang IPKnya lebih besar dari 3.00 kumpulkan di meja ini. Sisanya … bla .. bla … (silahkan diteruskan)”</p>
<p>Ulangi langkah di atas sampai jumlah lamaran < 20.</p>
<p>Wahh…. ternyata prosesnya begitu.
Tapi, gimana kalo IPnya tiarap?
Not to worry.</p>
<p>Dalam beberapa kesempatan sebagai decision maker rekrutmen, berikut faktor yang saya pertimbangkan:</p>
<ol>
<li>
<p>Antusiasme, atau istilah kerennya, passion.
Semangat itu harus ada dan berapi-api.
Namanya juga fresh-grad .. masa gak semangat.
Ini juga berkaitan dengan masalah motivasi. Orang yang udah dari sananya semangat, gak sulit disuruh-suruh. Gak perlu dibujuk, ditawari insentif macam-macam. Intinya adalah, saya cari yang memang menyukai pekerjaan yang ditawarkan. Jadi tidak sekedar kerja saja.</p>
</li>
<li>
<p>Smart.
Biasanya di sini saya akan menanyakan problem solving question.
Dari cara problem solvingnya, saya bisa menilai apakah seseorang smart atau tidak.</p>
</li>
<li>
<p>Get things done.
Walaupun smart, tapi kalo gak bisa kerja percuma.
Saya akan minta diperlihatkan produk-produk yang sudah dihasilkan kandidat.
Kebetulan bidang saya adalah software development.
Biasanya saya minta didemokan software yang pernah dibuat.</p>
</li>
<li>
<p>Inisiatif, atau ‘kemampuan mengerjakan hal yang tidak wajib atas kemauan sendiri’
Orang yang biasa ‘take extra mile’ biasanya lebih bagus pekerjaannya dan tidak butuh banyak supervisi dan motivasi.</p>
</li>
<li>
<p>Free time, waktu luang.
Ini adalah indikator penting apakah kandidat tersebut ‘bisa berkembang’ atau tidak.
Orang yang berkembang akan memanfaatkan freetime nya untuk belajar hal baru, walaupun tidak disuruh.</p>
</li>
</ol>
<p>Lebih lanjut bisa baca <a href="http://joelonsoftware.com/articles/fog0000000073.html">panduan Joel dalam interview</a></p>
<p>Atau panduan mas Mbot <a href="http://mbot.wordpress.com/2005/05/30/sharing-hrd-6-jangan-menghadapi-interview/">di sini</a> dan <a href="http://mbot.wordpress.com/2006/08/18/sharing-hrd-waspada-5-gelagat-buruk-waktu-melamar-kerja/">di sini</a>.</p>
<p>Jadi, kalo IP anda tidak besar, jangan berkecil harapan, tips dari saya :</p>
<ol>
<li>
<p>Punya koleksi portofolio hasil karya yang siap dipamerkan.
Buat dalam bentuk CD yang siap didemokan.</p>
</li>
<li>
<p>Sering-sering terima pekerjaan freelance walaupun gratisan.
Selain memperluas networking, juga tambah pengalaman dan mengasah problem solving skill. Kalau pekerjaannya tangible, tambahkan di koleksi portofolio</p>
</li>
<li>
<p>Latih inisiatif sehingga menjadi refleks.
Salah satu caranya adalah dengan aktif mengerjakan pekerjaan rumah seperti membereskan kamar, mendekorasi ruang kerja, berkebun, dsb.</p>
</li>
<li>
<p>Isi waktu luang dengan banyak belajar.
Sumber bisa diperoleh di internet atau toko buku</p>
</li>
<li>
<p>Banyak berkontribusi di komunitas, misalnya milis, forum, dsb.
Ini akan membuat kita menjadi terkenal sehingga nilai jual meningkat.</p>
</li>
<li>
<p>Jual diri anda dengan cara memiliki personal website.
Pasang semua portofolio di sana.
Tulis artikel yang dapat menunjukkan kapabilitas dan kompetensi kita.</p>
</li>
<li>
<p>Giat beribadah dan berdoa.
Penghasilan besar kalo korupsi percuma.
Jadi berdoa supaya dapat gaji besar yang halal.</p>
</li>
<li>
<p>Kasihanilah office boy, maksudnya petugas penerima karyawan baru.
Jangan melamar ke semua lowongan. Pilih yang benar-benar disukai dan dikuasai. Kalau belum menguasai, lihat #4. Bukan cuma perusahaan yang bisa selektif, kandidat juga dong.</p>
</li>
<li>
<p>Kalau Anda fresh graduate, jangan terlalu cerewet tentang salary.
Fokus fresh graduate, apalagi yang IPKnya tiarap, adalah sesegera mungkin meninggalkan status fresh graduate. Gimana caranya? Tentu saja dengan memiliki pengalaman kerja. Carilah pekerjaan yang walaupun gajinya kecil, tapi variatif dan besar tantangannya, juga bisa membuat kita tambah pintar. Biasanya ini bisa diperoleh di perusahaan kecil di mana orang biasanya merangkap pekerjaan.
Tenang saja, begitu kompetensi meningkat, salary pasti mengikuti. Keuntungan tambahan, sebagai pemilik IPK tiarap, persaingan di perusahaan kecil pasti tidak seketat di perusahaan besar yang pasang iklan di Kompas atau Republika.</p>
</li>
</ol>
<p>Demikian, semoga bermanfaat.</p>
Intro Framework2006-08-11T20:37:33+07:00https://software.endy.muhardin.com/java/intro-framework<p>Hari ini di milis jug ada yang bertanya tentang framework. <a href="http://dwiardiirawan.blogs.or.id">Si penanya</a> sendiri kelihatannya mengerti tentang framework, tapi kesulitan menjelaskan kepada orang lain.</p>
<p>Ini umum terjadi, apalagi kalau programmer Java berbicara dengan programmer dari dunia lain, seperti PHP, C, C++, dsb. Ini bisa disebabkan karena penggunaan framework tidak umum di dunia lain tersebut, atau simply karena programmer lain tersebut fakir bandwidth, sehingga gak aware terhadap tren terbaru.</p>
<p>Baiklah, mari kita bahas framework. Framework, sesuai dengan terjemahannya, adalah kerangka kerja. Jadi, di dalam framework ada beberapa kode program yang kalau kita gunakan, akan memberikan kerangka atau struktur ke aplikasi yang dihasilkan. Singkatnya seperti itu. Nanti akan lebih jelas setelah kita lihat contoh kasus dan contoh kode.</p>
<p>Untuk memahami framework, mari kita lihat ke dunia yang minim framework, yaitu PHP. Sebelum tahun 2005-2006 ini, (setau saya) programmer PHP di Indonesia masih asing dengan framework.</p>
<p>Misalnya saya buat aplikasi shopping cart. Aplikasi yang sangat umum dalam PHP. Setidaknya saya pernah mencoding ulang shopping cart minimal tiga kali. Dan semuanya arsitekturnya beda.</p>
<p>Pertama, waktu masih belajar. Semua kode campur aduk jadi satu. Kalau ada perubahan warna font, beberapa kode SQL harus ikut diupdate. Ubah nama tabel, 7 file kode harus diedit. Singkatnya, perubahan sedikit saja memaksa saya mengubah di banyak tempat di kode program.</p>
<p>Kemudian, setelah semakin banyak pengalaman, saya mulai memisahkan kode HTML dengan kode SQL. Dengan ini, aplikasi bisa berganti wajah dengan mudah. Cukup edit satu file konfigurasi, tampilan berubah seketika.</p>
<p>Terakhir, saya mulai mempertimbangkan multi bahasa. Semua string yang berkaitan dengan label, dikeluarkan dalam satu file sendiri. Jadi kalau kita mau ganti bahasa, cukup bikin replika file tersebut, dan terjemahkan. Kemudian edit file konfigurasi, dan bahasa berubah seketika.</p>
<p>Rapi dan bersih ..</p>
<p>Setelah mendapatkan struktur yang optimal, saya mulai me-reuse struktur tersebut. Kapan-kapan membuat aplikasi baru, saya akan langsung memisahkan kode HTML, SQL, dan bahasa.</p>
<p>Skenario di atas terjadi karena saya tidak menggunakan framework. Butuh banyak sekali trial and error buat saya sampai menemukan struktur yang rapi dan bersih. Setelah ditemukan, struktur tersebut saya jadikan framework. Kerangka untuk membuat aplikasi selanjutnya.</p>
<p>Sekarang kita pindah dari dunia yang minim framework, ke dunia yang kebanjiran framework, Java.</p>
<p>Di dunia Java, kelihatannya orang sangat suka coding framework, sehingga kalau kita search di sourceforge, mungkin lebih banyak framework daripada aplikasi siap pakai. Mengapa ini terjadi, saya tidak tahu. Yang jelas, kalau kita akrab dengan Java, pasti akan pakai framework.</p>
<p>Framework yang ideal harusnya merupakan intisari dari best practices. Pengalaman bertahun-tahun membuat aplikasi yang mirip, disimpulkan dan dibakukan menjadi framework siap pakai. Tapi saking banyaknya framework di Java, akhirnya banyak juga framework sampah. Artinya, dibuat bukan dari best practices dan experiences, tapi lebih menjadi ajang belajar membuat framework.</p>
<p>Penggunaan framework hasil belajar seperti ini sangat buruk dampaknya. APInya tidak stabil, sehingga kalau framework tersebut menerbitkan edisi baru, kode program kita harus diubah semua. Tentu saja, karena framework merupakan kerangka utama. Jadi kalau kerangkanya berubah, maka seluruh aplikasi akan terpengaruh. Selain itu, strukturnya juga belum terbukti keandalannya. Masalah sederhana menjadi kompleks, waktu banyak terbuang untuk ‘mengurusi’ framework.
Menggunakan framework jelek lebih fatal akibatnya daripada tanpa framework.</p>
<p>Sampai sejauh ini, harusnya sudah ada gambaran tentang framework.</p>
<h3 id="framework-vs-library">Framework vs Library</h3>
<p>Jangan keliru membedakan framework dengan library. Framework, by definition, sangat invasif. Dia memaksakan bagaimana kita harus coding, bagaimana memberi nama class, di mana harus meletakkan file, dan banyak aturan-aturan lain. Sedangkan library, tinggal baca dokumentasi, daftar function, input dan output masing-masing function, selesai.</p>
<p>Dengan demikian, menggunakan framework lebih sulit daripada library.
Coba kita bedakan framework dengan library
Framework Java:</p>
<ul>
<li><a href="http://www.springframework.org" title="Spring Framework Home Page">Spring Framework</a></li>
<li><a href="http://struts.apache.org">Struts</a></li>
</ul>
<p>Library Java:</p>
<ul>
<li><a href="http://joda-time.sourceforge.net/">Joda Time</a></li>
<li><a href="http://www.jfree.org/jfreechart/">JFreeChart</a></li>
<li><a href="http://freemarker.sourceforge.net">Freemarker</a></li>
<li><a href="http://jakarta.apache.org/commons/index.html">Jakarta Commons</a></li>
</ul>
<p>Framework PHP:</p>
<ul>
<li><a href="http://seagullproject.org/">Seagull</a></li>
<li><a href="http://cakephp.org/">Cake</a></li>
</ul>
<p>Library PHP:</p>
<ul>
<li><a href="http://pear.php.net/">PEAR</a></li>
<li><a href="http://adodb.sourceforge.net/">AdoDb</a></li>
<li><a href="http://smarty.php.net/">Smarty</a></li>
</ul>
<p>Ada lagi satu kategori, yaitu toolkit. Toolkit adalah kode yang kita gunakan dalam development, tapi tidak disertakan dalam produk akhir. Contohnya di Java antara lain: <a href="http://ant.apache.org">Ant</a> dan <a href="http://cruisecontrol.sourceforge.net/">CruiseControl</a>.</p>
<p>Tapi ada juga beberapa yang tidak jelas pengelompokannya. JUnit misalnya, di satu sisi, dia menyediakan perlengkapan untuk testing sehingga bisa dikategorikan sebagai library. Di sisi lain, dia mengharuskan ada prefix test di setiap nama method, sehingga layak disebut framework.</p>
<h3 id="plus-minus">Plus Minus</h3>
<p>Sekarang pertanyaan yang penting. Apa keuntungan dan kerugian menggunakan framework? Asumsikan framework yang ingin digunakan bagus dan teruji.</p>
<p>Keuntungan</p>
<ul>
<li>Struktur yang konsisten. Sangat berguna bila developer banyak dan turnover tinggi.</li>
<li>Struktur merupakan best practices. Semua sudah ditempatkan di tempat yang paling sesuai.</li>
<li>Dapat belajar tentang desain aplikasi yang baik.</li>
</ul>
<p>Kerugian</p>
<ul>
<li>Butuh investasi waktu belajar dan adaptasi</li>
<li>Ada overhead. Untuk project sangat kecil, mungkin overheadnya lebih besar dari projectnya sendiri. Lagipula, tidak semua fitur framework kita pakai, sehingga cuma akan menjadi bloat.</li>
<li>Menimbulkan dependensi. Ini terutama terasa di aplikasi yang berarsitektur plugin. Upgrade framework dapat merusak plugin.</li>
</ul>
<h3 id="memilih-framework">Memilih Framework</h3>
<p>Sekarang, bagaimana kita memutuskan pakai framework atau tidak? Kalau pakai, pilih yang mana? Sering sekali ada banyak framework yang menyelesaikan masalah yang sama.</p>
<p>Pertama, pertimbangkan benefit vs cost.
Di beberapa project, saya lebih memilih tanpa framework, karena ukuran projectnya kecil.</p>
<p>Untuk <a href="http://playbilling.sourceforge.net">playbilling</a>, saya pakai <a href="http://ibatis.apache.org">iBatis</a> alih-alih <a href="http://www.hibernate.org" title="Hibernate Home Page">Hibernate</a> yang lebih lengkap fiturnya. Pertimbangannya adalah, saya tidak menggunakan banyak tipe database. Sehingga cross-database tidak terlalu dibutuhkan. Sekarang saya sedang mempertimbangkan menghilangkan <a href="http://ibatis.apache.org">iBatis</a>, dan pakai JDBC biasa untuk mengurangi dependensi dan ukuran donlod.</p>
<p>Kedua, lihat kualitas dokumentasinya.
Alasan yang jelas adalah, kalau tidak ada dokumentasi, gimana tau cara pakainya? Kita butuh dokumentasi untuk bisa menggunakan framework.</p>
<p>Ada alasan kedua yang juga penting. Kualitas dokumentasi mencerminkan kualitas framework. Mengapa? Karena dokumentasi biasanya adalah by product (produk sampingan), apalagi di project open source. Logikanya, jika tim developer menghasilkan dokumentasi bagus, pasti kualitas dan desain frameworknya sendiri lebih bagus lagi.</p>
<p>Sejauh ini, kesimpulan saya konsisten. Framework terbaik secara desain dan kualitas kode adalah <a href="http://www.springframework.org" title="Spring Framework Home Page">Spring Framework</a>. Coba lihat dokumentasinya. Sangat rinci, detail, dan minim kesalahan.
Contoh lain adalah <a href="http://www.hibernate.org" title="Hibernate Home Page">Hibernate</a>. Dokumentasinya sangat lengkap. Bahkan di dalamnya juga dijelaskan tentang konsep Object Relational Mapping. Dengan membaca dokumentasinya saja, kita akan menambah wawasan. Apalagi menggunakan frameworknya.</p>
<p>Ketiga, lihat aktivitas komunitasnya.
Framework, apalagi open source, membutuhkan interaksi dengan sesama pengguna. Keuntungan utama tentu saja technical support dan tutorial gratis. Apalagi dengan perkembangan blog yang sangat pesat beberapa tahun terakhir. Semua pengalaman baik, buruk, mudah, sulit, semua diceritakan para pengguna framework di blognya masing-masing.</p>
<p>Kalau kita menggunakan framework minoritas, jarang digunakan orang, tutorialnya sedikit, diskusi di berbagai forum juga tidak banyak. Jadi, kalau kita search dengan <a href="http://www.google.com" title="Oom Google">Google</a>, lebih sulit menemukan permasalahan (dan solusi) yang sama dengan kebutuhan kita.</p>
<p>Terakhir, learning curve, atau kurva belajar. Apakah framework tersebut sulit atau mudah dipelajari?</p>
<p>Di bagian atas tadi saya menyebutkan keunggulan framework adalah dia dapat menyeragamkan struktur kode di antara banyak developer. Jadi, agar efektif, semua developer harus bisa menggunakan framework tersebut.</p>
<p>Nah, seorang system architect, software designer, senior developer, development team leader, atau apapun istilahnya di tempat Anda, orang yang bertugas memilih framework, harus mempertimbangkan kurva belajar ini.</p>
<p>Ini sering diabaikan banyak orang. Sebagai pemilih framework, tentunya orang tersebut memiliki keahlian/pengalaman teknis di atas rekan-rekannya. Penting bagi orang tersebut untuk memperhitungkan keahlian/pengalaman anggota tim yang lain yang tidak secanggih dirinya pada waktu memilih framework. Jangan sampai framework yang dipilih terlalu rumit, sehingga bukannya di-reuse malahan di-abuse.</p>
<p>Demikian ulasan singkat tentang framework. Mudah-mudahan bermanfaat.</p>
Bisnis Open Source2006-08-08T16:34:50+07:00https://software.endy.muhardin.com/manajemen/bisnis-open-source<p>Artikel ini, tidak seperti biasanya, ditulis berdasarkan request oleh <a href="http://sleepless.ngoprek.org" title="Anton Raharja">teman saya</a>. Selain itu, juga ada thread di milis <a href="http://groups.yahoo.com/group/jug-indonesia" title="Milis JUG Indonesia">JUG Indonesia</a> yang membahas tentang bisnis open source. Di sana saya memberikan sedikit komentar yang secara singkat serupa dengan apa yang akan dijelaskan di sini.</p>
<p>Beberapa tahun belakangan ini, gema open source mulai ramai di dunia. Banyak orang yang tadinya skeptis dengan ide software gratis, mulai mencoba peruntungannya di dunia yang buka-bukaan ini. Banyak juga orang (salah satunya saya) yang bergembira dan sangat mendukung open source, terutama karena ke’gratis’annya semata.</p>
<p>Tidak kurang dari perusahaan besar di dunia seperti <a href="http://www.oracle.com" title="Oracle">Oracle</a>, <a href="http://www.ibm.com" title="IBM">IBM</a>, <a href="http://www.sun.com" title="Sun Microsystem">Sun</a>, dan masih banyak yang lainnya, merilis aplikasi mereka yang berharga jutaan dolar secara open source atau ‘gratis’.</p>
<p>Open source tidak selalu gratis, walaupun biasanya begitu. Dan gratis juga tidak selalu berarti open-source. Untuk menyederhanakan urusan, yang akan kita bahas di sini adalah software gratis. Terlepas dari apakah software tersebut open source atau tidak.</p>
<p>Orang-orang pun mulai bertanya, “Apa tidak rugi melepas aplikasi secara gratis? Dari mana keuntungannya? Padahal untuk membuat software tersebut, telah banyak biaya yang dikeluarkan.”
Dan masih banyak lagi pertanyaan sejenis.</p>
<p>Tapi yang jelas, mereka tidak menjadi rugi karena melepas aplikasinya. Tindakan ini tentunya sudah didahului dengan kalkulasi bisnis yang matang dan tidak semata ikut-ikutan trend open source atau tiba-tiba menjadi ‘baik hati’ terhadap negara ketiga/miskin (misalnya, Indonesia). Nah, sekarang mari kita lihat, dari mana profitnya.</p>
<p>Setidaknya ada beberapa modus operandi penggunaan strategi gratis untuk perusahaan, sepanjang pengetahuan saya:</p>
<ol>
<li>RND</li>
<li>Teaser product</li>
<li>Strategi Produk Komplementer</li>
<li>By product</li>
<li>Pendukung core business</li>
<li>Sebagai produk utama</li>
</ol>
<p>Mari kita lihat satu per satu.</p>
<p><em>Disclaimer: semua yang saya tulis di sini merupakan pendapat pribadi saya, atas pengamatan tentang penggunaan strategi gratisan, yang tidak terbukti kebenarannya. Tidak ada pernyataan resmi dari perusahaan yang disebut di sini tentang strategi gratisnya. Silahkan tulis komentar anda di bawah kalau setuju/tidak setuju dengan pendapat saya ini.</em></p>
<h3 id="research-and-development">Research and Development</h3>
<p>Strategi ini digunakan oleh RedHat. Mereka membuat project open source Fedora sebagai lini depan riset Linux. Teknologi yang dihasilkan di project Fedora kemudian digunakan di lini produk komersilnya Red Hat Enterprise Linux. Dengan menggunakan project open source sebagai divisi RND, Red Hat mendapat keuntungan dari kontribusi tenaga kerja gratisan (baca: sukarelawan) baik di posisi developer, tester, dokumenter, public relation (dalam bentuk komentar positif, testimoni, word of mouth), dan juga technical writer (blog membahas penggunaan Fedora, support forum, mailing list, dsb).
Seperti kita lihat di sini, sebenarnya Red Hat mendapat keuntungan besar, setidaknya dari sisi payroll dan marketing, dengan menggunakan strategi open source.</p>
<p>Latihan buat pembaca, sebutkan contoh lain perusahaan yang menggunakan open source sebagai divisi RND.</p>
<h3 id="teaser-product">Teaser Product</h3>
<p>Promosi produk yang terbaik adalah dengan cara memberikan produk tersebut ke konsumen, dan berharap mereka puas dan bercerita kepada teman-temannya (yang diharapkan akan membeli produk tersebut). Strategi ini digunakan Oracle pada saat mereka melepas produk Oracle 10g Express Edition. Harapannya adalah, dengan menggunakan produk gratis tersebut:</p>
<ol>
<li>Lebih banyak orang yang familiar dengan produk Oracle</li>
<li>Pengguna merasa puas atau sudah terlalu terbiasa (baca: kecanduan) dengan Oracle</li>
<li>Pada suatu saat, pengguna gratisan tersebut merasa kurang dengan fitur yang ada, dan akan membeli versi berbayarnya</li>
<li>Jika pengguna gratisan diminta rekomendasi oleh bosnya/temannya, mereka akan merekomendasikan Oracle, karena itulah produk yang mereka kuasai, dan lagipula bukan mereka yang akan membayarnya.</li>
</ol>
<p>Strategi pemasaran ini lebih efektif daripada memasang iklan di media. Banyak orang (termasuk saya) yang sudah sangat bosan dengan iklan, sehingga ada fitur ‘autoskip’ di otak saya yang secara otomatis menutup popup iklan, bahkan sebelum gambarnya selesai loading. Bahkan untuk kasus ekstrim, banyak orang mau bersusah payah yang memasang filter iklan seperti Adblock atau <a href="http://www.schooner.com/~loverso/no-ads/" title="Internal Proxy for No Ads">no-ads.pac</a> untuk melucuti media dari semua iklan.</p>
<p>Dengan menggunakan ‘orang betulan’ yang tulus dan jujur untuk merekomendasikan produknya, lebih besar kemungkinan produk akan dibeli.</p>
<h3 id="strategi-produk-komplementer">Strategi Produk Komplementer</h3>
<p>Ada kalanya perusahaan menggratiskan suatu aplikasi karena aplikasi tersebut adalah komplementer dari produk utamanya. Contohnya adalah Acrobat Reader.</p>
<p>Waktu kita belajar di SMP dulu, kita belajar tentang produk substitusi dan komplementer. Produk substitusi adalah produk yang dapat menggantikan produk lain. Misalnya, Indomie adalah produk substitusi Mi Sedap.</p>
<p>Sedangkan produk komplementer adalah produk yang saling melengkapi dengan produk lain. Seperti yang sering kita lihat di tivi, salah satu iklan tag linenya adalah, “Apapun makanannya, minumnya tetap … (produk XXX)”. Nampaknya produsen minuman tersebut berusaha serakah dengan menjadikan produknya sebagai komplementer untuk semua makanan padat.</p>
<p>Acrobat Reader, adalah komplementer dari Acrobat Distiller. Dengan menggratiskan Reader, Adobe berharap penjualan Distiller akan meningkat. Sepertinya strategi ini cukup jitu, sehingga PDF bisa jadi standar format dokumen di internet.</p>
<p>Lebih lanjut tentang penggunaan strategi produk komplementer ini dijelaskan oleh Joel Spolsky. Silahkan baca sendiri di <a href="http://www.joelonsoftware.com/articles/StrategyLetterV.html" title="Joel on Open Source">websitenya</a>.</p>
<h3 id="by-products">By Products</h3>
<p>By products artinya produk sampingan. Misalnya kita membuka usaha jualan kelapa parut di pasar, kita akan mendapatkan banyak sekali air kelapa sebagai limbah. Mereka yang jeli akan segera beternak jamur di dalam air kelapa tersebut dan segera membuka lini produk baru, yang lebih tenar disebut dengan Nata de Coco.</p>
<p>Hal yang sama dilakukan oleh <a href="http://www.thoughtworks.com" title="ThoughtWorks">Martin Fowler dan gerombolannya</a>. Bisnis utama mereka adalah software development dan konsultasi bagi perusahaan software development yang lainnya. Sebagai mana konsultan manajemen pada umumnya, mereka juga berjualan jargon. Salah satu jargon yang mereka perdagangkan adalah Continuous Integration. Ini tidak lain adalah istilah keren dari <em>kompile sering-sering dan test sampai capek</em>. Menyuruh programmer kompile lima kali sehari dan testing sepuluh kali sehari kemudian menulis hasilnya dalam format HTML adalah cara cepat untuk membuat programmer pensiun dini alias resign. Oleh karena itu, mereka membuat program kecil yang bisa melakukan kegiatan tersebut secara terjadwal dan terus menerus. Sehingga jargon Continuous Integration bisa dijual dengan sukses.</p>
<p>Program kecil tersebut adalah <a href="http://cruisecontrol.sourceforge.net" title="Cruise Control homepage">Cruise Control</a>. Dapat Anda peroleh secara gratis di <a href="http://www.sourceforge.net" title="Kost-kostan aplikasi open source">SourceForge</a>.</p>
<p>Perhatikan di sini bahwa dagangan utama mereka adalah proses, bukan produk. Sehingga <a href="http://cruisecontrol.sourceforge.net" title="Cruise Control homepage">Cruise Control</a> tersebut adalah by product belaka. Sama seperti ‘nata de coco’. Karena bukan produk utama, tidak ada kerugian dari melepas aplikasi tersebut secara gratis. Keuntungannya, para calon prospek akan berpersepsi bahwa <a href="http://www.martinfowler.com" title="Bliki Martin Fowler">Martin Fowler</a> masih punya mainan lain yang lebih menarik yang tidak digratiskan. Sehingga dengan senang hati mereka akan mempekerjakan <a href="http://www.pyrasun.com/mike/mt/archives/2005/04/23/18.29.03/index.html" title="Para magang di thoughtworks">dia dan pasukan magangnya</a> dengan tarif selangit.</p>
<h3 id="pendukung-produk-utama">Pendukung produk utama</h3>
<p>Framework favorit saya, <a href="http://www.springframework.org" title="Spring Framework">Spring Framework</a> adalah contohnya. Produk utama <a href="http://www.springframework.com" title="Interface 21">Interface 21</a>, perusahaan sponsor <a href="http://www.springframework.org" title="Spring Framework">Spring Framework</a>, adalah perusahaan software development. Berbagai perusahaan besar di dunia mempercayakan pengembangan aplikasi bisnisnya pada <a href="http://www.springframework.com" title="Interface 21">Interface 21</a>. Jadi, produk utama <a href="http://www.springframework.com" title="Interface 21">Interface 21</a> adalah software development. Dalam mengembangkan aplikasi pesanan client, Interface 21 membutuhkan framework berkualitas tinggi dan berarsitektur bagus. Di situlah peranan project Spring Framework, sebagai produk pendukung aktivitas bisnis Interface 21.</p>
<h3 id="produk-utama">Produk Utama</h3>
<p>Ada juga beberapa perusahaan yang menjadikan aplikasi gratisnya sebagai produk utama. Lalu datang pertanyaan, “Kalau produk utama digratiskan, dari mana dapat uangnya?”. Ada beberapa jalur lain di luar penjualan software: training, buku, dan customization. Jalur ini ditempuh JBoss dan Hibernate. Dokumentasi utama JBoss tidak gratis. Kita harus membeli dengan sistem berlangganan di websitenya.</p>
<p>Hibernate, mendapat pemasukan dari penjualan buku. Pembuatnya, Gavin King dan Christian Bauer mengarang buku yang bagus sekali, Hibernate in Action. Isinya menjelaskan desain aplikasi pada umumnya, dan penggunaan framework Hibernate. Dengan semakin banyaknya pengguna Hibernate, buku dan training akan semakin laku.</p>
<p>PlaySMS dan PlayBilling, mendapat pemasukan dari customization. Para pelanggan seringkali membutuhkan fitur khusus yang belum ada di rilis open source. Untuk itu, mereka membayar developer untuk menambahkan fitur yang dibutuhkan. Siapa yang lagi yang lebih berkompeten menulis tambahan fitur selain <a href="http://endy.artivisi.com" title="Endy Muhardin">pengembang</a> <a href="http://sleepless.ngoprek.org" title="Anton Raharja">asli</a>nya?</p>
<p>Demikianlah uraian tentang bisnis open source. Jadi, hilangkan ilusi bahwa perusahaan besar sekarang menjadi dermawan, karena memberikan aplikasinya secara gratis. Ada kepentingan pemegang saham di baliknya, yang tentu saja, tidak mau rugi.</p>
<p>Walaupun demikian, kita lihat ada situasi win-win di sini. Pengguna diuntungkan dengan adanya software gratis. Perusahaan juga diuntungkan dari promosi gratis, tenaga sukarela, dan berbagai keuntungan lainnya.</p>
<p>Oleh karena itu, mari gunakan aplikasi gratis.
Kalo bisa gratis, kenapa harus bayar?
Kalo ada yang gratis, kenapa masih membajak?</p>
<p>:D</p>
Ruthless Testing 32006-07-20T22:38:19+07:00https://software.endy.muhardin.com/java/ruthless-testing-3<p>Alkisah, di suatu project, ada kode program yang berkelakuan aneh. Kode program tersebut berfungsi untuk melakukan pendaftaran user baru ke dalam sistem. Jadi, user membuka form, mengisi data diri, dan menekan tombol Submit. Tidak ada yang aneh dengan fiturnya.</p>
<p>Keanehannya adalah, entah kenapa, aplikasi tersebut cuma bisa digunakan 7 kali. Jadi pada waktu user ke-8 menekan tombol Submit, datanya tidak tersimpan di database. Anehnya juga, aplikasi tidak menimbulkan pesan error. Hmm … sungguh gaib.</p>
<p>Berbagai upaya dilakukan untuk mengatasi masalah ini. Tim pengusir hantu didatangkan, lengkap dengan kostum putih-putih dan botol penangkap hantu, karena dicurigai ada ‘sesuatu’ dengan angka 7. Tetapi tidak juga mencapai solusi yang memuaskan.</p>
<p>Akhirnya, seorang anggota milis JUG didatangkan. Beliau memeriksa source code fitur registrasi tersebut, sebagai berikut:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">save</span> <span class="o">(</span><span class="nc">User</span> <span class="n">u</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="n">username</span><span class="o">,</span> <span class="n">password</span><span class="o">);</span>
<span class="nc">PreparedStatement</span> <span class="n">pstm</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"INSERT INTO user VALUES (?,?,?)"</span><span class="o">);</span>
<span class="n">pstm</span><span class="o">.</span><span class="na">setInt</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">u</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="n">pstm</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">u</span><span class="o">.</span><span class="na">getUsername</span><span class="o">());</span>
<span class="n">pstm</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">u</span><span class="o">.</span><span class="na">getPassword</span><span class="o">());</span>
<span class="n">pstm</span><span class="o">.</span><span class="na">executeUpdate</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span><span class="o">(</span><span class="nc">Exception</span> <span class="n">err</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pembaca yang teliti (ya, maksudnya Anda) akan segera menemukan asal muasal semua ‘kejadian gaib’ di atas, yaitu:</p>
<ol>
<li>
<p>Connection dibuka, tapi tidak pernah ditutup. Besar kemungkinan max connection di database adalah 7 concurrent connection. Sehingga setelah 7 kali connect dan 0 kali disconnect, database akan menolak koneksi baru.</p>
</li>
<li>
<p>Catch block dibiarkan kosong, sehingga error dari database tidak akan tampil di mana-mana.</p>
</li>
</ol>
<p>Hmm .. kesalahan yang ‘newbie banget’. Melalui pemeriksaan menyeluruh, ditemukan kesalahan serupa di berbagai bagian kode yang lain.</p>
<p>Pada titik ini, para project manager, development team leader akan segera mengeluh dan bersiap-siap mengisi komentar di bawah, “Masa saya harus baca kode baris-per-baris? Kayak kurang kerjaan aja !!”
Sebelum Anda lakukan itu, sabar dulu .. baca sampai habis. Kita akan segera lihat solusinya.</p>
<p>Pemeriksaan baris kode seperti ini sangat melelahkan. Semakin besar projectnya, semakin banyak baris kodenya, semakin tinggi beban pemeriksaannya. Di lain pihak, kalau kita tidak melakukan pemeriksaan, kita menimbulkan resiko terjadinya masalah di kemudian hari.</p>
<p>Jadi, bagaimana solusinya?</p>
<p>Untungnya –seperti biasa– kita bukan yang pertama menemukan masalah seperti ini. Masalah sudah pernah dialami orang lain, dan dibuatkan solusinya, sehingga kita –seperti biasa– tinggal download dan pakai :D</p>
<p>Jawaban dari masalah ini adalah <strong>automated code review</strong>. Tools ini akan melakukan review terhadap source code, sesuai dengan aturan yang kita tetapkan, dengan teliti, otomatis, dan repeatable. Dengan tool ini, berapapun baris kode yang ada, semua akan diperiksa secara menyeluruh.</p>
<p>Ada beberapa solusi yang dapat digunakan:</p>
<ul>
<li>
<p><a href="http://csdl.ics.hawaii.edu/Tools/Jupiter/">Jupiter (Eclipse Plugin)</a></p>
</li>
<li>
<p><a href="http://pmd.sourceforge.net">PMD</a></p>
</li>
<li>
<p><a href="http://www.hammurapi.org">Hammurapi</a></p>
</li>
<li>
<p>Built-in feature IntelliJ IDEA</p>
</li>
</ul>
<p>Pada contoh kali ini, kita akan coba menggunakan PMD. Caranya mudah:</p>
<ol>
<li>
<p>Download dari websitenya.</p>
</li>
<li>
<p>Extract</p>
</li>
<li>
<p>Buat target di Ant untuk memanggil PMD.</p>
</li>
<li>
<p>Lihat reportnya</p>
</li>
</ol>
<p>Untuk langkah #1 dan #2 tidak perlu dijelaskan lebih lanjut. Target Ant untuk langkah #3 adalah sebagai berikut:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><target</span> <span class="na">name=</span><span class="s">"code-review"</span> <span class="na">depends=</span><span class="s">"prepare"</span><span class="nt">></span>
<span class="nt"><pmd</span> <span class="na">targetjdk=</span><span class="s">"1.5"</span> <span class="na">shortFilenames=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><ruleset></span>basic<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>codesize<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>clone<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>controversial<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>coupling<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>design<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>finalizers<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>imports<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>javabeans<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>logging-java<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>logging-jakarta-commons<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>naming<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>optimizations<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>strictexception<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>strings<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>sunsecure<span class="nt"></ruleset></span>
<span class="nt"><ruleset></span>unusedcode<span class="nt"></ruleset></span>
<span class="nt"><formatter</span>
<span class="na">type=</span><span class="s">"net.sourceforge.pmd.renderers.HTMLRenderer"</span>
<span class="na">toFile=</span><span class="s">"${pmd.result}/pmd.html"</span>
<span class="nt">/></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"src"</span><span class="nt">></span>
<span class="nt"><include</span> <span class="na">name=</span><span class="s">"**/*.java"</span><span class="nt">/></span>
<span class="nt"></fileset></span>
<span class="nt"></pmd></span>
<span class="nt"></target></span>
</code></pre></div></div>
<p>Berikut penjelasannya:</p>
<ol>
<li>
<p>Target JDK : memeriksa kesesuaian dengan JDK 1.5</p>
</li>
<li>
<p>Ruleset : Aturan yang akan diberlakulkan. Detailnya bisa dilihat di website PMD. Sebagai contoh, dengan memberlakukan ruleset basic, kita memeriksa blok catch yang kosong, sehingga mencegah masalah pesan error di atas. Sedangkan ruleset design memeriksa apakah Connection yang dibuat sudah ditutup.</p>
</li>
<li>
<p>Formatter : Jenis formatter yang digunakan. Report yang dihasilkan PMD dapat berupa text, xml, csv.</p>
</li>
<li>
<p>Fileset: source code yang akan direview. Kita dapat mengganti parameter ini dan mereview semua source code yang ada di harddisk kita. SIlahkan mencoba mendownload source code project open source java (misalnya Tomcat, atau JBoss), dan lihat apakah kode programnya ditulis dengan baik.</p>
</li>
</ol>
<p>Laporan yang dihasilkan bentuknya seperti ini:
<a href="/images/uploads/2006/07/pmd-report.png"><img src="/images/uploads/2006/07/pmd-report.png" alt="PMD Review Result " /></a></p>
<p>Selamat mencoba. Semoga bermanfaat.</p>
Cost of Quality2006-07-12T23:48:16+07:00https://software.endy.muhardin.com/manajemen/cost-of-quality<p>Mumpung sedang <a href="http://endy.artivisi.com/blog/java/ruthless-testing-2/">hangat</a> membahas <a href="http://endy.artivisi.com/blog/java/ruthless-testing-1/">testing</a>, coba kita dengarkan apa pendapat manajemen.</p>
<p>Programmer [P] : Bos, kata Mr. Endy, kita harus bikin unit test, integration test, coverage test dsb
Manager [M] : Stop, banyak sekali testnya? Memangnya kamu gak bisa bikin kode yang bener, sampe ditest segitu banyaknya?
[P] : Ya bisa bos, tapi ..
[M] : Ya sudah kalo bisa coding sana yang bener, test nanti saja waktu sudah delivery. Kita sudah mepet deadline ini. Kita dibayar bukan untuk coding unit test.
[P] : (kecewa, cuma bisa gigit mouse) Ya deh bos …</p>
<p>Umumnya, begitulah tanggapan pihak manajemen dalam menyikapi test. Test dianggap sebagai kegiatan yang (walaupun) penting, prioritasnya ada di kelas ekonomi.
Saya sendiri, walaupun merupakan penggemar unit testing dan percaya akan manfaatnya, kadang merasa tidak pede untuk berargumen dengan manajemen tentang urusan ini.</p>
<p>Sampai suatu hari, ada training di kantor tentang Software Engineering. Instrukturnya adalah konsultan manajemen yang ahli di bidang ISO dan CMMI.</p>
<p>Biasanya, saya rada skeptis dengan yang namanya konsultan manajemen. Baik itu konsultan pemasaran, konsultan ISO, atau bidang manajemen yang lainnya. Kebanyakan dari mereka (tidak semua, tapi kebanyakan) biasanya cuma bisnis jargon saja. Cari uang dari menciptakan jargon aneh, menjelaskan jargon tersebut, dan menyuruh kita menggunakan jargon tersebut. Selebihnya nasihatnya mirip-mirip ramalan bintang, nasihat yang buram dan bisa ditafsirkan apa saja.</p>
<p>Tapi kali ini, sang konsultan mengetengahkan jargon yang cukup menarik, yaitu <strong>Cost of Quality</strong>. Berikut penjelasannya.</p>
<p>Biasanya, dalam mengerjakan project software, kegiatan apa yang ada di project schedule kita?
Umumnya seperti ini:</p>
<ol>
<li>Analisa : membuat spesifikasi requirement user</li>
<li>Desain : desain tampilan dan arsitektur aplikasi</li>
<li>Coding</li>
<li>User Testing</li>
<li>Implementasi : migrasi data, training user, tuning</li>
<li>Garansi</li>
<li>Makan-makan (kalo ada profit)</li>
</ol>
<p>Si konsultan bilang, memang itu semua (1-6) adalah kegiatan utama. Tanpa kegiatan itu, project tidak bisa diutamakan. Tapi, dengan 6 aktifitas tersebut, kecil peluangnya kita akan sampai ke aktivitas #7, yaitu makan-makan.</p>
<p>“Kenapa begitu ?”, demikian para peserta training bertanya dengan antusias, nampaknya jelas kalau urusannya menyangkut perut (tidak jadi makan-makan) orang ngantuk bisa langsung jadi segar.</p>
<p>“Ya sebab di sana belum dianggarkan Biaya Kualitas (Cost of Quality), nantinya kualitas rendah, banyak perbaikan, sehingga profit terancam tergusur”, jawab instruktur.</p>
<p>Nah, sekarang apa saja biaya kualitas itu? Diantaranya adalah:</p>
<ol>
<li>
<p>Training : memastikan pekerjaan tidak dikerjakan secara trial and error</p>
</li>
<li>
<p>Review : mendeteksi kesalahan sedini mungkin. Kesalahan desain bisa diperbaiki dalam beberapa halaman dokumen desain. Tetapi jika dibiarkan sampai coding, banyak sekali effort untuk debug dan bugfix yang harus dikeluarkan. Misal: mengganti iBatis dengan Hibernate di dokumen desain cukup dengan Find and Replace. Mengganti iBatis dengan Hibernate di kode program? Bayangkan sendiri.</p>
</li>
<li>
<p>Rework : harus ada anggaran untuk melakukan perbaikan dokumen, bugfix, dsb</p>
</li>
<li>
<p>Audit : sebelum hasil pekerjaan dideliver ke client, harus diperiksa dulu secara internal. Audit proses juga dilakukan untuk memastikan semua Cost of Quality sudah diperhitungkan dan dijalankan.</p>
</li>
<li>
<p>Testing : testing sebanyak mungkin dan seawal mungkin.</p>
</li>
</ol>
<p>Dalam project software, kita mengenal fenomena bola salju. Tadinya kecil, menggelinding menjadi besar sehingga dapat menggulung satu desa (seperti di film kartun). Di software sama. Kita ingin mendeteksi kesalahan sedini mungkin, kalau bisa di tahap analisa semua kesalahan sudah dieliminasi, sehingga tidak merembet ke kode program. Jadi, perlu ada review dan audit untuk memastikan kesalahan tidak ditemukan di fase-fase akhir. Karena memperbaiki kesalahan di awal (requirement dan design) jauh lebih murah daripada di akhir (kode program, materi user training, user manual, dan script migrasi data).</p>
<p>Baiklah, cost of quality itu penting. Sekarang bagaimana cara menjelaskan pada manajemen mengenai tambahan biaya tersebut?</p>
<p>Mari kita rekonstruksi dialog Programmer dan Manager.</p>
<p>Programmer [P] : Bos, kata Mr. Endy, kita harus bikin unit test, integration test, coverage test …
Manager [M] : Stop, banyak sekali testnya? Memangnya kamu gak bisa bikin kode yang bener, sampe ditest segitu banyaknya?
[P] : Ya bisa bos, tapi ..
[M] : Ya sudah kalo bisa coding sana yang bener, test nanti saja waktu sudah delivery. Kita sudah mepet deadline ini. Kita dibayar bukan untuk coding unit test.
[P] : Memangnya kalo boleh tau, seperti apa jadwalnya Bos?
[M] : Kamu programmer mau tau saja.
Tapi gpp, karena ini percakapan boongan baiklah akan saya kasi tau:</p>
<ol>
<li>Analisa : 2 minggu</li>
<li>Desain : 1 minggu</li>
<li>Coding : 4 minggu</li>
<li>User Testing dan Bugfix : 4 minggu</li>
<li>Implementasi : 4 minggu</li>
<li>Garansi : 8 minggu
Total : 23 minggu</li>
</ol>
<p>[P] : Kenapa User Testing dan Implementasi lama sekali Bos? Sama lamanya dengan Coding?
[M] : Ya itu karena kamu dan teman-temanmu kerjanya salah melulu, jadi di User Testing dan Implementasi banyak masalah.</p>
<p>[P] : Bagaimana kalau saya usul jadwal seperti ini :</p>
<ol>
<li>Analisa : 2 minggu</li>
<li>Review dan rework : 1 minggu</li>
<li>Desain : 1 minggu</li>
<li>Review dan rework : 1 minggu</li>
<li>Training : 1 minggu</li>
<li>Coding + Unit Testing: 6 minggu</li>
<li>User Testing dan Bugfix: 2 minggu</li>
<li>Implementasi : 1 minggu</li>
<li>Garansi : 8 minggu
Total : 23 minggu
Sama kan Bos, total waktunya. Saya yakin dengan tambahan aktifitas review, rework, dan training, kita akan bisa tangkap sebagian besar bug sebelum User Testing. Dengan demikian, bug yang terlihat sama user tinggal sedikit karena sudah kita temukan duluan.
Kalau sebelum coding kita ditraining dulu, codingnya bisa lebih cepat dan bener Bos, sehingga bugnya lebih sedikit. Mudah-mudahan client jadi puas.</li>
</ol>
<p>[M] : Wah pintar juga kamu, di rumah dikasi makan apa sama Emaknya bisa pintar gini? Baiklah akan saya coba usul kamu.</p>
<p>Demikianlah, cost of quality itu penting. Lebih penting lagi adalah cara kita menjual pada manajemen, supaya kegiatan testing dan training tidak dianggap sebagai kegiatan ‘buang-buang uang’ dan ‘tidak dibayar client’. Sebenarnya, client membayar untuk mendapat hasil yang berkualitas dan memuaskan. Secara tidak langsung, mereka membayar kita untuk melakukan testing.</p>
<p>Referensi : <a href="http://www.stevemcconnell.com/articles/art04.htm">Software Quality at Top Speed</a></p>
Ruthless Testing 22006-07-12T19:27:12+07:00https://software.endy.muhardin.com/java/ruthless-testing-2<p>Pada <a href="http://endy.artivisi.com/blog/java/ruthless-testing-1/">artikel sebelumnya</a>, kita telah membahas tentang penggunaan <a href="http://www.junit.org">unit test</a> dan integration test sederhana menggunakan <a href="http://dbunit.sourceforge.net/">DBUnit</a>. Pada artikel kali ini, kita akan membahas tentang coverage testing</p>
<p>Sebagai technical leader, project manager -atau apapun nama jabatannya- yang bertugas mengawasi segerombolan programmer, bagaimana cara kita memastikan bahwa mereka membuat unit test untuk setiap kode yang ditulis?</p>
<p>Cara manual tentu saja dengan memeriksa unit test satu-persatu dan membandingkannya dengan kode program aplikasi. Kita cocokkan tiap unit test dengan pasangan kode yang ditest. Dengan demikian, kita tahu mana kode yang sudah ditest mana yang belum.</p>
<p>Sayangnya, cara ini tidak otomatis. Sehingga kalau nanti dihadapkan pada deadline ketat, dipastikan kegiatan ini akan segera diabaikan. Oleh karena itu kita harus mengotomasi pemeriksaan test ini sehingga di tengah tekanan jadwal, kita tetap bisa memastikan unit test dibuat dengan benar.</p>
<p>Ada beberapa tools yang beredar di pasaran untuk mengotomasi pemeriksaan ini, diantaranya:</p>
<ul>
<li>
<p><a href="http://www.cenqua.com/clover/">Clover</a> (komersial)</p>
</li>
<li>
<p><a href="http://cobertura.sourceforge.net/">Cobertura</a> (open source)</p>
</li>
<li>
<p><a href="http://emma.sourceforge.net/">Emma</a> (open source)</p>
</li>
</ul>
<p>Semua tools di atas melakukan ‘<em>coverage testing</em>’, yaitu pengetesan apakah unit test yang dibuat telah meng-cover semua kode yang ditest. Jika unit testnya terlalu sedikit, maka coverage testing akan failed.</p>
<p>Pada contoh kali ini, kita akan menggunakan Cobertura. Cobertura mampu memeriksa coverage terhadap baris kode dan percabangan. Jadi kalau ada percabangan (if-else, switch) yang belum ditest, cobertura akan melaporkan. Kalau ada baris kode yang belum terjangkau unit test, cobertura juga akan memperingatkan.</p>
<p>Sebagai supervisor, kita dapat menetapkan aturan pada project. Misalnya, “Untuk project ini, saya ingin test meng-cover minimal 70% dari seluruh baris dan 90% dari seluruh percabangan”.</p>
<p>Bagaimana cara Cobertura melakukan keajaiban ini? Jawabannya, melalui mekanisme yang disebut <em>code instrumentation</em>.
Cobertura akan memodifikasi bytecode (*.class) yang dihasilkan oleh proses kompilasi. Bytecode yang dihasilkan <em>javac</em> akan ditambah dengan kode-kode khusus untuk menghitung dan mendeteksi invokasi. Dengan demikian, pada saat unit test dijalankan, Cobertura akan mengetahui unit test yang mana menjalankan kode program yang mana. Bytecode yang akan diproses oleh Cobertura harus dikompile dengan flag -debug.</p>
<p>Dengan demikian, untuk menjalankan Cobertura, kita membutuhkan dua kali tahap kompilasi. Kompilasi pertama mengubah *.java menjadi *.class. Kompilasi kedua menambahkan instrumen di *.class.</p>
<p>Berikut adalah Ant target untuk melakukan instrumentasi.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><target</span> <span class="na">name=</span><span class="s">"instrument-cobertura"</span> <span class="na">depends=</span><span class="s">"compile-cobertura"</span><span class="nt">></span>
<span class="c"><!--
Instrument the application classes, writing the
instrumented classes into ${build.instrumented.dir}.
--></span>
<span class="nt"><cobertura-instrument</span> <span class="na">todir=</span><span class="s">"${compile.cobertura}"</span><span class="nt">></span>
<span class="nt"><includeClasses</span> <span class="na">regex=</span><span class="s">".*"</span> <span class="nt">/></span>
<span class="nt"><excludeClasses</span> <span class="na">regex=</span><span class="s">".*\Test.*"</span> <span class="nt">/></span>
<span class="nt"><instrumentationClasspath></span>
<span class="nt"><pathelement</span> <span class="na">location=</span><span class="s">"${compile.debug}"</span> <span class="nt">/></span>
<span class="nt"></instrumentationClasspath></span>
<span class="nt"></cobertura-instrument></span>
<span class="nt"></target></span>
</code></pre></div></div>
<p>Selanjutnya, kita menjalankan unit test seperti biasa (melalui Ant target). Bedanya adalah, kita menggunakan hasil kompilasi Cobertura. Ant target untuk menjalankan unit test adalah sebagai berikut:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><target</span> <span class="na">name=</span><span class="s">"unit-test"</span> <span class="na">depends=</span><span class="s">"instrument-cobertura"</span><span class="nt">></span>
<span class="nt"><junit</span> <span class="na">haltonfailure=</span><span class="s">"false"</span> <span class="na">fork=</span><span class="s">"yes"</span><span class="nt">></span>
<span class="nt"><classpath</span> <span class="na">location=</span><span class="s">"${compile.cobertura}"</span><span class="nt">/></span>
<span class="nt"><classpath</span> <span class="na">refid=</span><span class="s">"cobertura-classpath"</span><span class="nt">/></span>
<span class="nt"><formatter</span> <span class="na">type=</span><span class="s">"xml"</span><span class="nt">/></span>
<span class="nt"><batchtest</span> <span class="na">todir=</span><span class="s">"${junit.result}"</span><span class="nt">></span>
<span class="nt"><fileset</span> <span class="na">dir=</span><span class="s">"${compile.debug}"</span> <span class="na">includes=</span><span class="s">"**/*Test.class"</span><span class="nt">/></span>
<span class="nt"></batchtest></span>
<span class="nt"></junit></span>
<span class="nt"></target></span>
</code></pre></div></div>
<p>Perhatikan referensi ke <strong>${compile.cobertura}</strong> yang diletakkan di atas referensi <strong>cobertura-classpath</strong>. Ini mengisyaratkan bahwa class hasil instrumentasi Cobertura diload <strong>lebih dulu</strong> daripada class yang dikompilasi secara normal.</p>
<p>Terakhir, kita buat Ant target untuk memeriksa coverage dan menghasilkan HTML report.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><target</span> <span class="na">name=</span><span class="s">"coverage-test"</span> <span class="na">depends=</span><span class="s">"unit-test"</span><span class="nt">></span>
<span class="nt"><cobertura-check</span> <span class="na">datafile=</span><span class="s">"cobertura.ser"</span>
<span class="na">branchrate=</span><span class="s">"70"</span>
<span class="na">linerate=</span><span class="s">"90"</span>
<span class="na">haltonfailure=</span><span class="s">"false"</span>
<span class="nt">/></span>
<span class="nt"><cobertura-report</span> <span class="na">datafile=</span><span class="s">"cobertura.ser"</span>
<span class="na">srcdir=</span><span class="s">"src"</span> <span class="na">destdir=</span><span class="s">"${cobertura.result}"</span>
<span class="nt">/></span>
<span class="nt"></target></span>
</code></pre></div></div>
<p>Perhatikan nilai batas baris (linerate) dan percabangan (branchrate) yang harus ditest. Nilainya sudah disesuaikan dengan aturan yang kita tetapkan di atas, yaitu sebesar 70% dan 90%.</p>
<p>Misalnya kita memiliki class DayCounter.java sebagai berikut:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DayCounter</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">numDays</span><span class="o">(</span><span class="kt">int</span> <span class="n">month</span><span class="o">,</span> <span class="kt">int</span> <span class="n">year</span><span class="o">){</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">month</span><span class="o">)</span> <span class="o">{</span>
<span class="k">case</span> <span class="mi">1</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">3</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">5</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">7</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">8</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">10</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">12</span><span class="o">:</span> <span class="k">return</span> <span class="mi">31</span><span class="o">;</span>
<span class="k">case</span> <span class="mi">4</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">6</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">9</span><span class="o">:</span>
<span class="k">case</span> <span class="mi">11</span><span class="o">:</span> <span class="k">return</span> <span class="mi">30</span><span class="o">;</span>
<span class="k">case</span> <span class="mi">2</span><span class="o">:</span>
<span class="k">if</span> <span class="o">(</span><span class="n">year</span><span class="o">%</span><span class="mi">4</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="mi">29</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="mi">28</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">default</span><span class="o">:</span> <span class="k">return</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dan unit testnya DayCounterTest.java sebagai berikut:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.testframework</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">junit.framework.TestCase</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DayCounterTest</span> <span class="kd">extends</span> <span class="nc">TestCase</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testNumDays</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">DayCounter</span> <span class="n">d</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DayCounter</span><span class="o">();</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">31</span><span class="o">,</span> <span class="n">d</span><span class="o">.</span><span class="na">numDays</span><span class="o">(</span><span class="mi">12</span><span class="o">,</span><span class="mi">2001</span><span class="o">));</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">30</span><span class="o">,</span> <span class="n">d</span><span class="o">.</span><span class="na">numDays</span><span class="o">(</span><span class="mi">11</span><span class="o">,</span><span class="mi">2001</span><span class="o">));</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">28</span><span class="o">,</span> <span class="n">d</span><span class="o">.</span><span class="na">numDays</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span><span class="mi">2001</span><span class="o">));</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">29</span><span class="o">,</span> <span class="n">d</span><span class="o">.</span><span class="na">numDays</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span><span class="mi">2000</span><span class="o">));</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">d</span><span class="o">.</span><span class="na">numDays</span><span class="o">(</span><span class="mi">21</span><span class="o">,</span><span class="mi">2000</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Akan menghasilkan coverage yang bagus, karena semua baris sudah ditest. Berikut adalah hasil coverage testnya:</p>
<p><a href="/images/uploads/2006/07/cobertura-all-green-small.png"><img src="/images/uploads/2006/07/cobertura-all-green-small.png" alt="Cobertura All Pass " /></a></p>
<p>Kita juga bisa melihat coverage detail dari class DayCounter.java:</p>
<p><a href="/images/uploads/2006/07/cobertura-class-green-small.png"><img src="/images/uploads/2006/07/cobertura-class-green-small.png" alt="Cobertura Class Detail Pass " /></a></p>
<p>Bila kita non-aktifkan beberapa test, seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">tutorial.testframework</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">junit.framework.TestCase</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DayCounterTest</span> <span class="kd">extends</span> <span class="nc">TestCase</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testNumDays</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">DayCounter</span> <span class="n">d</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DayCounter</span><span class="o">();</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">31</span><span class="o">,</span> <span class="n">d</span><span class="o">.</span><span class="na">numDays</span><span class="o">(</span><span class="mi">12</span><span class="o">,</span><span class="mi">2001</span><span class="o">));</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">30</span><span class="o">,</span> <span class="n">d</span><span class="o">.</span><span class="na">numDays</span><span class="o">(</span><span class="mi">11</span><span class="o">,</span><span class="mi">2001</span><span class="o">));</span>
<span class="c1">//assertEquals(28, d.numDays(2,2001));</span>
<span class="c1">//assertEquals(29, d.numDays(2,2000));</span>
<span class="c1">//assertEquals(0, d.numDays(21,2000));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Maka kode kita tidak akan lolos test, karena hasil coverage totalnya seperti ini:</p>
<p><a href="/images/uploads/2006/07/cobertura-all-red-small.png"><img src="/images/uploads/2006/07/cobertura-all-red-small.png" alt="Cobertura All Failed " /></a></p>
<p>dan detail classnya seperti ini</p>
<p><a href="/images/uploads/2006/07/cobertura-class-red-small.png"><img src="/images/uploads/2006/07/cobertura-class-red-small.png" alt="Cobertura Class Detail Failed " /></a></p>
<p>Bagaimana?
Mudah dan cepat kan?
Dengan cara ini, kita dapat mendeteksi tingkat kemalasan programmer dalam membuat unit test.
Pada artikel selanjutnya, kita akan membahas tentang code-compliance test. Test ini sering juga disebut code-review.</p>
Plugin untuk Archive2006-07-12T00:49:47+07:00https://software.endy.muhardin.com/aplikasi/plugin-untuk-archive<p>Tadinya cuma pengen tampilan arsip seperti <a href="http://andryshuzain.com/archives">punyanya mas Andry</a>. Tapi sayangnya dia pakai <a href="http://textpattern.com/">aplikasi blog lain</a> yang beda dengan <a href="http://www.wordpress.org">saya punya</a>. Jadi solusinya dia kemungkinan besar tidak bisa diimplement di sini.</p>
<p>Tadinya mau coding sendiri, browsing ke <a href="http://codex.wordpress.org">situs dokumentasi Wordpress</a>, malah ketemu <a href="http://www.sonsofskadi.net/extended-live-archive/">plugin Extended Live Archives</a>, berikut <a href="http://www.24fightingchickens.com/2006/01/03/extended-live-archives/">cara instalasinya</a>.</p>
<p>Setelah sedikit berkutat dengan file CSS, tadaaa…. silahkan <a href="http://endy.artivisi.com/blog/archive-live/">lihat sendiri</a>. Kalau anda melihat halaman kosong, tunggu sejenak, tandanya dia sedang loading.
Nanti setelah loading, ada rangkuman arsip canggih berteknologi AJAX.</p>
<p>Cool … :D</p>
<p>Update: Sesuai saran mas Andry, saya coba instal <a href="http://www.sporadicnonsense.com/2006/05/04/clean-archives-20/">SRG Clean Archives</a>. Hasilnya bisa dilihat <a href="http://endy.artivisi.com/blog/archive-simple/">di sini</a>.</p>
Ruthless Testing 12006-06-13T22:59:30+07:00https://software.endy.muhardin.com/java/ruthless-testing-1<p>Sebelum menyerahkan aplikasi ke client, sudahkah Anda melakukan testing?
100% programmer pasti menjawab dengan kompak, “SUDAAAAHHHH !!!”</p>
<p>Nah, kalau begitu, pertanyaannya, apa saja yang sudah ditest?</p>
<p>Jawaban orang biasanya bervariasi, tergantung nilai project, jumlah orang yang tersedia, dan juga tergantung tingkat kegaptekan si programmer terhadap teknologi testing yang tersedia.</p>
<p>Testing minimal yang dilakukan biasanya adalah user testing. Jadi, ada user (atau programmer yang pura-pura jadi user) mencoba menggunakan aplikasi yang (katanya sih) sudah selesai. Biasanya fenomena ini akan berakhir dengan periode bugfix yang lebih lama daripada periode coding itu sendiri.</p>
<p>Jadi, bagaimana tipsnya supaya user testing berjalan dengan lancar, minim bug, kalaupun ada, bisa diselesaikan dalam bilangan hari?</p>
<p>Jawabannya adalah melaksanakan “ruthless testing”. Artinya testing secara kejam. Testing yang berlapis-lapis sehingga kalau ada programmer bandel yang membuat percabangan if terlalu dalam, akan langsung ketahuan.</p>
<p>Bagaimana itu? Kita akan lihat sebentar lagi.</p>
<p>Ada berapa jenis testing? Hmm .. menurut saya ada:</p>
<ol>
<li>Unit testing</li>
<li>Integration testing</li>
<li>Coverage testing</li>
<li>Code compliance testing</li>
<li>User/functional testing</li>
<li>Performance testing</li>
</ol>
<p>Semua test ini harus dijalankan tanpa kecuali.</p>
<p>What the !@#$? Banyak sekali? Apakah semua harus dijalankan? Wah, bisa-bisa kerjaan kita cuma ngurusin test doang? Bagaimana ini?</p>
<p>Hmm.. jangan khawatir, semua bisa dijalankan dengan investasi minimal di awal project.
Tips saya adalah:</p>
<ol>
<li>
<p>Alokasikan pembuatan test bersama-sama dengan construction. Jangan setelah construction. Jadi, wahai Project Managers, kalau merencanakan construction, tambahkan alokasi waktu untuk membuat test.</p>
</li>
<li>
<p>Semua test harus otomatis dan bisa dijalankan dengan sekali perintah. Di project saya, semua test dijalankan cukup dengan:</p>
</li>
</ol>
<p><code class="language-plaintext highlighter-rouge">ant full-test</code></p>
<p>Baiklah, mari kita lihat satu persatu secara lebih detail.</p>
<h3 id="unit-testing">Unit Testing</h3>
<p>Ini adalah testing di level paling detail dari aplikasi. Artinya, testing di level method/function. Misalnya saya punya kode seperti ini:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DayCounter</span><span class="o">{</span>
<span class="cm">/**
* menghitung jumlah hari dalam bulan dan tahun yang diminta.
* Misalnya, bila month = 2 dan year = 2000, method ini akan menghasilkan 29.
* @param month bulan yang akan dihitung
* @param year tahun yang akan dihitung
* @return jumlah hari dalam bulan dan tahun bersangkutan
*/</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">numDays</span><span class="o">(</span><span class="kt">int</span> <span class="n">month</span><span class="o">,</span> <span class="kt">int</span> <span class="n">year</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="mi">29</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Coba lihat contoh kode di atas? Salah bukan?
Apapun yang kita masukkan, hasilnya pasti 29.
Mari kita buktikan dengan unit test bahwa kode di atas salah.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DayCounter</span> <span class="kd">extends</span> <span class="n">junit</span><span class="o">.</span><span class="na">framework</span><span class="o">.</span><span class="na">TestCase</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testNumDays</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">DayCounter</span> <span class="n">d</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DayCounter</span><span class="o">();</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">31</span><span class="o">,</span> <span class="n">d</span><span class="o">.</span><span class="na">numDays</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2000</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Pada waktu dijalankan, unit test tersebut akan menghasilkan tulisan FAILED</p>
<p>Di Java, unit test dapat dilakukan secara otomatis dengan menggunakan <a href="http://ww.junit.org">JUnit</a>.</p>
<h3 id="integration-test">Integration Test</h3>
<p>Sebagian besar aplikasi yang kita buat akan menggunakan database. Untuk itu, kita perlu mengetes apakah kode program kita dapat berinteraksi dengan database sesuai harapan.
Mirip dengan JUnit, kita membuat unit test yang mengakses database. Misalnya, berikut kode programnya:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DatabaseAccess</span><span class="o">{</span>
<span class="cm">/**
* memeriksa username dan password dalam database.
* Method ini akan mencocokkan username dan password yang diberikan
* dan yang terdaftar di database.
* Apabila cocok, akan menghasilkan true, dan apabila tidak cocok akan menghasilkan false.
* @param username
* @param password
* @return true kalau cocok, false kalau tidak cocok
*/</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">checkLogin</span><span class="o">(</span><span class="nc">String</span> <span class="n">username</span><span class="o">,</span> <span class="nc">String</span> <span class="n">password</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="s">"SELECT * FROM user WHERE username=? AND password=?"</span>
<span class="c1">// dst kode akses database</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Dan ini adalah kode untuk mengetesnya.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DatabaseAccess</span> <span class="kd">extends</span> <span class="n">junit</span><span class="o">.</span><span class="na">framework</span><span class="o">.</span><span class="na">TestCase</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCheckLogin</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">DatabaseAccess</span> <span class="n">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatabaseAccess</span><span class="o">();</span>
<span class="c1">// asumsikan ada username endy dan password secret di database</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">db</span><span class="o">.</span><span class="na">checkLogin</span><span class="o">(</span><span class="s">"endy"</span><span class="o">,</span> <span class="s">"secret"</span><span class="o">));</span>
<span class="c1">// coba dengan user sembarang, harusnya akan menghasilkan false</span>
<span class="n">assertFalse</span><span class="o">(</span><span class="n">db</span><span class="o">.</span><span class="na">checkLogin</span><span class="o">(</span><span class="s">"nobody"</span><span class="o">,</span> <span class="s">"gakada"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Perhatikan bahwa kedua test di atas mengasumsikan bahwa kita punya database, terisi data endy:secret, dan tidak ada record nobody:gakada di dalam tabel user.
Berarti, setiap dijalankan, kita harus memastikan asumsi di atas terpenuhi.
Lalu, bagaimana cara menjalankannya secara otomatis? Gampang, gunakan <a href="http://dbunit.sourceforge.net/">DBUnit</a>.</p>
<p>Dengan DBUnit, kita dapat me-reset kondisi database sesuai keinginan setiap kali test akan dijalankan.
Caranya, kita harus buat test data, yaitu satu record berisi data endy:test dalam tabel user. Test data ini disimpan dalam file checkLogin.xml</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dataset></span>
<span class="nt"><user</span> <span class="na">id=</span><span class="s">'1'</span>
<span class="na">username=</span><span class="s">'endy'</span>
<span class="na">password=</span><span class="s">'secret'</span>
<span class="nt">/></span>
<span class="nt"></dataset></span>
</code></pre></div></div>
<p>Sebelum test dimulai, pastikan database direset. Tambahkan method berikut di kode test:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DatabaseAccess</span> <span class="kd">extends</span> <span class="n">junit</span><span class="o">.</span><span class="na">framework</span><span class="o">.</span><span class="na">TestCase</span><span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCheckLogin</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// reset database</span>
<span class="n">prepareDatabase</span><span class="o">();</span>
<span class="nc">DatabaseAccess</span> <span class="n">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatabaseAccess</span><span class="o">();</span>
<span class="c1">// asumsikan ada username endy dan password secret di database</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">db</span><span class="o">.</span><span class="na">checkLogin</span><span class="o">(</span><span class="s">"endy"</span><span class="o">,</span> <span class="s">"secret"</span><span class="o">));</span>
<span class="c1">// coba dengan user sembarang, harusnya akan menghasilkan false</span>
<span class="n">assertFalse</span><span class="o">(</span><span class="n">db</span><span class="o">.</span><span class="na">checkLogin</span><span class="o">(</span><span class="s">"nobody"</span><span class="o">,</span> <span class="s">"gakada"</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">prepareDatabase</span><span class="o">(){</span>
<span class="nc">DatabaseOperation</span><span class="o">.</span><span class="na">CLEAN_INSERT</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">dbConn</span><span class="o">,</span> <span class="n">getTestData</span><span class="o">());</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">IDataSet</span> <span class="nf">getTestData</span><span class="o">(){</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">FlatXmlDataSet</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="s">"checkLogin.xml"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Nah, DBUnit akan dengan senang hati me-reset isi <strong>database development</strong> Anda. Pastikan test tersebut dijalankan di DATABASE DEVELOPMENT, dan bukan DATABASE PRODUCTION.</p>
<p>Sementara cukup sekian. Pada artikel selanjutnya, kita akan bahas tentang:</p>
<ol>
<li>Coverage Testing</li>
<li>Code compliance testing</li>
<li>User/functional testing</li>
<li>Performance testing
Dan sesuai janji saya, semua test otomatis dan sekali pencet.</li>
</ol>
Game Oldies2006-05-30T23:55:50+07:00https://software.endy.muhardin.com/aplikasi/game-oldies<p>Berangkat dari membaca <a href="http://www.saryada.com/2006/05/23/wow-transport-tycoon-deluxe-has-an-open-source-version/">blog teman</a> yang mengulas tentang <a href="http://www.tycoongames.net/downloadarea.html">Transport Tycoon Deluxe</a> yang sekarang ada <a href="http://www.openttd.org/">versi open sourcenya</a>, saya jadi teringat koleksi game oldies saya.</p>
<p>Jaman saya SMU dulu, sekitar tahun 1994-1997, game favorit saya dan teman-teman adalah <a href="http://en.wikipedia.org/wiki/X-COM">UFO-Enemy Unknown</a>. Game ini berkisah tentang penyerbuan alien ke bumi. Dengan fitur yang cukup variatif, seperti kejar-kejaran pesawat alien, penyerbuan ke markas musuh, mempertahankan markas dari serbuan alien, dan riset teknologi, game ini terasa sangat canggih pada masa itu.</p>
<p>Pada waktu mulai bermain, teknologi pasukan kita sangat minim. Seiring dengan seringnya bertempur, kita mulai mengkoleksi persenjataan alien. Persenjataan ini kemudian diteliti. Setelah penelitian selesai, kita bisa menggunakan senjata tersebut.</p>
<p>Bukan cuma senjata, mayat dan pesawat alien juga diteliti, sehingga kalau kita sudah main cukup lama, semua teknologi alien bisa kita gunakan.</p>
<p>Game yang sudah lama punah ini, tetap bisa dimainkan di masa kini dengan cara <a href="http://www.abandonia.com/games/38/download/UFOEnemyUnknown.htm">download dari abandonia</a>.</p>
<p>Sekuelnya juga ada, yaitu <a href="http://www.abandonia.com/games/60/X-Com-TerrorFromTheDeep">Terror From The Deep</a> dan <a href="http://www.abandonia.com/games/307/X-Com-Apocalypse">Apocalypse</a>.</p>
<p>Selain game itu, kami dulu juga suka memainkan <a href="http://www.abandonia.com/games/395/IncredibleMachine2">The Incredible Machine 2</a>. Ini adalah game puzzle, di mana kita disuruh mengkombinasikan alat-alat yang tersedia untuk memecahkan masalah yang diberikan. Alat-alat yang disediakan bermacam-macam, mulai dari balon zeppelin, anti gravitasi, motor listrik, blender, dan sebagainya.
Sangat bermanfaat untuk mendongkrak kreativitas adik-adik kita di SD.</p>
<p>Untuk memainkan game berbasis DOS ini, kita membutuhkan DOS Emulator. Saya biasa menggunakan <a href="http://dosbox.sourceforge.net">DosBox</a> dengan front-end <a href="http://members.home.nl/mabus/">D-Fend</a>.</p>
<p>Dengan dos emulator ini, kita bisa juga menjalankan game yang bahkan lebih tua lagi seperti Digger.</p>
Otomasi Transfer File dengan WinSCP2006-05-24T22:50:54+07:00https://software.endy.muhardin.com/aplikasi/otomasi-transfer-file-dengan-winscp<p>Setelah sebelumnya kita mengotomasi backup untuk Subversion, baik <a href="http://endy.artivisi.com/blog/aplikasi/subversion-backup-script-untuk-windows/">di Windows</a> maupun <a href="http://endy.artivisi.com/blog/aplikasi/subversion-backup-dan-restore/">di Linux</a>, masih ada sedikit PR yang tersisa.</p>
<p>File backup yang dihasilkan oleh script masih berkeliaran di komputer yang sama dengan repository. Jadi kalau sempat terjadi bencana harddisk jebol, repository beserta backupnya akan musnah tanpa sisa.</p>
<p>Solusi sementaranya adalah dengan cara rajin memindahkan file-file backup secara periodik ke komputer lain, untuk selanjutnya ditulis ke dalam CD/DVD.
Tetapi, kegiatan copy-mengcopy setiap minggu sangat membosankan. Belum lagi kalau ternyata ada assignment mendadak sehingga kegiatan transfer file menjadi terlupakan.
Solusi yang lebih <em>hi-tech</em> adalah dengan mengotomasi proses transfer file. Jadi, file backup yang sudah dihasilkan akan kita transfer secara otomatis ke komputer lain, kalau perlu komputer di negara lain.</p>
<p>Untuk melakukan otomasi ini, kita membutuhkan aplikasi <a href="http://www.winscp.net">WinSCP</a>. Ini adalah aplikasi untuk mentransfer file antara komputer Windows dengan Linux. Sejak menggunakan aplikasi ini, saya tidak pernah lagi menggunakan Windows Sharing dan Samba Server untuk kebutuhan file transfer.</p>
<p>Berikut caranya. Perhatikan bahwa konfigurasi saya adalah sebagai berikut:</p>
<ul>
<li>
<p>Repository Server: Windows 2000 Professional dengan WinSCP terinstal</p>
</li>
<li>
<p>Backup Server: Linux OpenSuSE 10.0 dengan SSH server aktif [IP Address: 192.168.0.2]</p>
</li>
<li>
<p>Username di Backup Server: <code class="language-plaintext highlighter-rouge">backuponly</code></p>
</li>
<li>
<p>Password di Backup Server: <code class="language-plaintext highlighter-rouge">backuppasswd</code></p>
</li>
<li>
<p>Folder yang mau dibackup: <code class="language-plaintext highlighter-rouge">c:\Backup</code></p>
</li>
<li>
<p>Folder tujuan: <code class="language-plaintext highlighter-rouge">/home/endy/backup</code></p>
</li>
</ul>
<p>Salah satu keunggulan WinSCP adalah, selain bisa mengelola transfer secara GUI, dia juga memiliki kemampuan scripting, sehingga bisa dipanggil dari command prompt.</p>
<p><a href="/images/uploads/2006/05/winscp.png"><img src="/images/uploads/2006/05/winscp.png" alt="Screenshot WinSCP " /></a></p>
<p>Berikut adalah script untuk mengotomasi proses copy folder <code class="language-plaintext highlighter-rouge">C:\Backup</code> dari Windows ke <code class="language-plaintext highlighter-rouge">/home/endy/backup</code> di Linux. Save sebagai <code class="language-plaintext highlighter-rouge">c:\upload-backup.txt</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Modus batch, supaya WinSCP tidak tanya-tanya
option batch on
# Otomatis overwrite file yang sama
option confirm off
# Masukkan username dan password
open backuponly:backuppasswd@192.168.0.2
# Pindah ke folder backup
cd /home/endy/backup
# Upload folder C:\Backup seisinya
put c:/Backup
# Disconnect
close
# Exit WinSCP
exit
</code></pre></div></div>
<p>Dan berikut adalah bat script untuk mengeksekusi script di atas, save dengan nama <em>c:\backup-scp.bat</em>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"C:\Program Files\WinSCP3\winscp3.exe" /console /script=upload-backup.txt
</code></pre></div></div>
<p>Script backup-scp.bat siap dijalankan melalui Windows Scheduler, sebaiknya 10 menit setelah jadwal backup.</p>
Synergy2006-05-24T17:13:37+07:00https://software.endy.muhardin.com/aplikasi/synergy<p><a href="/images/uploads/2006/05/synergy.gif"><img src="/images/uploads/2006/05/synergy.gif" alt="Synergy Screenshot " /></a></p>
<p>Perhatikan gambar di atas.</p>
<p>Seringkali kita bekerja dengan lebih dari satu komputer pada saat yang bersamaan. Misalnya seperti saya. Untuk kegiatan rutin sehari-hari saya menggunakan Debian GNU/Linux, yaitu untuk browsing (Firefox), membaca email (KMail), berlangganan RSS (aKregator), chatting (Gaim), dan sebagainya.</p>
<p>Tetapi ada kalanya saya ‘terpaksa’ menggunakan Windows. Misalnya untuk mengedit template dokumen dengan MS Word. Template dokumen sangat mementingkan ketepatan setting font, border, margin pada suatu dokumen. Jadi walaupun bisa dibuka dengan OpenOffice, tetap saja ada beberapa komponen layout yang rusak. Kegiatan lainnya adalah membuat perencanaan project dengan menggunakan MS Project. Aplikasi ini belum ada padanannya di Linux. Jadi mau tidak mau saya harus menggunakan MS Project agar rencana project saya bisa digunakan orang</p>
<p>Banyak juga orang yang bekerja menggunakan laptop, sedangkan di kantor tetap disediakan PC. Biasanya polanya sama. Kegiatan internet rutin tetap di laptop, sedangkan pekerjaan kantor di PC.</p>
<p>Apapun alasannya, banyak orang yang menggunakan dua komputer sekaligus.</p>
<p>Pada jaman dahulu kala, kita terpaksa menggunakan dua perangkat keyboard dan mouse untuk mengendalikan kedua komputer tersebut. Yang lebih canggih biasanya menggunakan remote desktop agar bisa mengendalikan komputer satu dari lainnya, sehingga tidak perlu pindah-pindah keyboard. Tapi solusi ini, walaupun hi-tech, tetap masih kurang produktif. Karena dengan menggunakan dua monitor, kita bisa lebih cepat bekerja. Monitor satu menampilkan referensi/tutorial, monitor satu lagi digunakan untuk coding.</p>
<p>Gambar di atas adalah solusinya. Ada aplikasi ajaib bernama <a href="http://synergy2.sourceforge.net">Synergy</a>. Selain ajaib, aplikasi ini juga gratis dan mudah diinstal.</p>
<p>Cara kerjanya demikian. Kita instal Synergy di kedua komputer. Kemudian kita jalankan Synergy sebagai server <strong>di komputer yang keyboard dan mousenya ingin kita gunakan</strong>. Untuk berjalan sebagai server, kita perlu membuat file konfigurasi <em>synergy.conf</em> sebagai berikut:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>section: screens
laptop:
pc:
end
section: links
laptop
right = pc
pc:
left = laptop
end
</code></pre></div></div>
<p>Berikut penjelasannya:</p>
<ul>
<li>
<p>laptop: hostname komputer yang pertama</p>
</li>
<li>
<p>pc: hostname komputer yang kedua</p>
</li>
<li>
<p>pada konfigurasi di atas, laptop diletakkan di sebelah kiri pc.</p>
</li>
</ul>
<p>Dengan konfigurasi di atas, apabila kita sedang mengendalikan laptop, kemudian kita menggerakkan kursor mouse ke pinggir layar sebelah kanan, kursor akan menyeberang dari monitor laptop ke monitor PC. Begitu juga sebaliknya.</p>
<p>Untuk administrator, Synergy ini bisa digunakan untuk menggantikan KVM terminal. KVM terminal adalah hardware yang memungkinkan beberapa komputer dikendalikan dengan satu perangkat monitor, keyboard, dan mouse.</p>
<p>Lebih canggih lagi, clipboardnya berhubungan. Artinya kita bisa copy text dari laptop, dan paste di PC.</p>
<p>Setelah selesai, silahkan lakukan koneksi dari client ke server.
Petunjuk lengkap bisa dibaca <a href="http://synergy2.sourceforge.net/running.html">di sini</a>.</p>
<p>Oh iya, walaupun sudah jelas dari screenshot di atas (KDE di kiri -sudah pasti Linux- dan Windows di kanan), pasti ada aja yang tanya, “Bisa jalan di Windows atau Linux gak ya?”
Jawabannya, “Sangat bisa !!!”</p>
<p>Saya sudah menggunakan Synergy untuk menghubungkan Debian Sarge dan Windows 2000 Professional. Untuk Sarge harus pakai Synergy 1.2.2 (bawaan apt-get) sedangkan versi terbaru (per 24 Mei 2006) adalah 1.3.1.</p>
<p>Tentu saja versi client dan server harus sama, untuk menghindari kepusingan tanpa manfaat.</p>
<p>Update:
Beberapa teman mencoba menjalankan Synergy server di Windows dan mengaku mengalami kesulitan. Kebetulan di tempat saya ada beberapa anak magang yang tertarik untuk mencoba.
Ternyata, kesalahan utama dalam setting server di Windows adalah <strong>lupa memberitahu Synergy komputer mana yang di kiri dan yang mana yang di kanan.</strong></p>
<p>Baiklah, mari kita lihat saja screenshotnya:</p>
<h3 id="tampilan-awal">Tampilan Awal</h3>
<p>Begitu Synergy dijalankan, kita akan melihat tampilan berikut:
<a href="/images/uploads/2006/08/sinergi1.JPG"><img src="/images/uploads/2006/08/sinergi1.JPG" alt="Synergy Start Up " /></a></p>
<p>Dari sini, kita kemudian memilih setting Synergy sebagai server. Lalu klik configure, sehingga kita masuk ke halaman selanjutnya.</p>
<h3 id="konfigurasi-screen">Konfigurasi Screen</h3>
<p>Screen, atau layar, memberitahukan Synergy berapa komputer yang terlibat dalam jaringan, dan nama masing-masing komputer. Sebagai ilustrasi, kita akan menggunakan dua komputer, kita beri nama <strong>komputerkiri</strong> dan <strong>komputerkanan</strong>. Dengan layout (seperti dari namanya) komputerkiri berada di sebelah kiri komputerkanan.</p>
<p>Berikut adalah tampilan untuk menambah screen:
<a href="/images/uploads/2006/08/sinergi2.JPG"><img src="/images/uploads/2006/08/sinergi2.JPG" alt="Konfigurasi Screen " /></a></p>
<p>Setelah dikonfigurasi, komputerkiri muncul di layar sebagai berikut:
<a href="/images/uploads/2006/08/sinergi3.JPG"><img src="/images/uploads/2006/08/sinergi3.JPG" alt="KomputerKiri sudah dikonfigurasi " /></a></p>
<p>Lanjutkan dengan konfigurasi komputerkanan.
<a href="/images/uploads/2006/08/sinergi4.JPG"><img src="/images/uploads/2006/08/sinergi4.JPG" alt="Konfigurasi KomputerKanan " /></a></p>
<p>Layar setelah konfigurasi terlihat seperti ini:
<a href="/images/uploads/2006/08/sinergi5.JPG"><img src="/images/uploads/2006/08/sinergi5.JPG" alt="Setelah konfigurasi dua screen " /></a></p>
<h3 id="konfigurasi-link">Konfigurasi Link</h3>
<p>Beberapa orang yang menceritakan kesulitan pada saya, biasanya berhenti pada tahap ini. Oh, kedua komputer sudah saya entri ke dalam Synergy, berarti sekarang sudah bisa connect. Ya memang bisa connect, kalo kita jalankan dengan log debug, akan terlihat bahwa kedua komputer sudah connected. Tapi kenapa mousenya belum bisa <em>menyeberang layar</em>?</p>
<p>Jawabannya adalah, karena Synergy belum tau seperti apa layoutnya. Bagaimana susunan komputernya? Apakah komputerkiri di sebelah kiri, kanan, atas, bawah atau di mana?</p>
<p>Baiklah, sekarang kita akan memberi tahu layoutnya kepada Synergy. Lihat panel Link di bawah? Nah di situlah letak konfigurasinya.
Isikan nilai yang diinginkan, yaitu di 0-100% sisi layar kiri dari <strong>komputerkanan</strong> adalah <strong>komputerkiri</strong>. Setelah diisi, klik tombol +.
Berikut adalah layar konfigurasinya adalah sebagai berikut:
<a href="/images/uploads/2006/08/sinergi7.JPG"><img src="/images/uploads/2006/08/sinergi7.JPG" alt="Link komputer kiri ke kanan " /></a></p>
<p>Lakukan hal yang sama dengan komputer kanan. Ini supaya mouse yang sudah <strong>menyeberang</strong> ke komputer kiri bisa kembali lagi ke komputerkanan. Kalau cuma satu link, dia bisa pindah ke kiri, tapi tidak bisa kembali lagi. Jangan lupa membalik nilainya, isinya adalah:
0-100% sisi layar kanan dari <strong>komputerkiri</strong> adalah <strong>komputerkanan</strong>.</p>
<p>Berikut layar konfigurasi setelah diisi:
<a href="/images/uploads/2006/08/sinergi8.JPG"><img src="/images/uploads/2006/08/sinergi8.JPG" alt="Layar konfigurasi akhir " /></a></p>
<p>Selanjutnya, jalankan servernya, dan coba connect dari client.</p>
<p>Selamat mencoba :D</p>
Subversion backup script untuk Windows2006-05-16T01:46:02+07:00https://software.endy.muhardin.com/aplikasi/subversion-backup-script-untuk-windows<p>Dulu saya pernah menulis tentang <a href="http://endy.artivisi.com/blog/aplikasi/subversion-backup-dan-restore/">cara backup repository Subversion secara otomatis</a>. Tetapi, script yang ada di situ hanya bisa dijalankan di Linux, karena ada satu baris bermasalah pada Perl scriptnya, yaitu:</p>
<p><code class="language-plaintext highlighter-rouge">$next_backup_file = "daily-incremental-backup." . </code>date +%Y%m%d<code class="language-plaintext highlighter-rouge">;</code></p>
<p>Baris di atas akan menjalankan perintah operating system seakan-akan kita menjalankan perintah ini di command prompt:
<code class="language-plaintext highlighter-rouge">$ date +%Y%m%d</code></p>
<p>Di Linux, perintah ini berjalan lancar, menampilkan output sebagai berikut:
<code class="language-plaintext highlighter-rouge">20060515</code></p>
<p>Output tersebut nantinya digunakan untuk membentuk nama file dump. Tetapi di Windows, hasilnya adalah:
<code class="language-plaintext highlighter-rouge">The system cannot accept the date entered .. blablabla </code>
Rupanya Windows mengira kita akan ganti tanggal komputer.</p>
<p>Jadi akhirnya, karena saya tidak mengerti Perl, saya membuat backup script baru dengan Ruby. Logika yang sama, alur program yang sama, cuma beda bahasa. Inilah hasilnya. Silahkan digunakan dan dimodifikasi sesuai kebutuhan.</p>
<h2 id="svn-weekly-backuprb">svn-weekly-backup.rb</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Full Backup script for Subversion Repository #
require 'date'
# 1. Initialize variables
backup_folder = "c:/Backup"
repo_folder = "c:/Repository/Imagine"
youngest = `svnlook youngest #{repo_folder}`.strip
next_backup_file = "weekly-full-backup.#{Time.now.strftime("%Y%m%d")}.dmp"
compress_script = backup_folder+"/compress-"+youngest+".tzs"
compress_command = "C:/Program Files/TUGZip/TzScript.exe #{compress_script}"
# 2. Perform backup
puts "Backing up to revision #{youngest}\n";
result = `svnadmin dump #{repo_folder} > #{backup_folder}/#{next_backup_file}`
# 3. Compress backup result
# 3.1 Creating temporary zip script [TugZip Required]
puts "Generating compress script ...\n";
tzs_file = < <-TZS
function main()
{
var Comp = new Compress();
Comp.Archive = "#{backup_folder}/#{next_backup_file}.zip";
Comp.Type = "ZIP";
Comp.Compression = 3;
Comp.WorkingDir = "#{backup_folder}";
Comp.Data = "#{next_backup_file}";
Comp.Password = "";
Comp.DateExtension = 0;
Comp.TimeExtension = 0;
Comp.Overwrite = 1;
Comp.Recurse = 0;
Comp.StoreFolderNames = 1;
Comp.IncludeHiddenFiles = 1;
Comp.DoCompress();
}
TZS
File.open(compress_script, "w") do | file |
file << tzs_file
end
# 3.2. Executing compression
puts "Compressing dump file...\n";
puts `#{compress_command}`
# 3.3. Deleting temporary zip script
puts "Deleting compress script ...\n";
File.delete(compress_script);
# 3.4 Deleting uncompressed dump file
puts "Deleting uncompressed dump file ...\n";
File.delete(backup_folder+"/"+next_backup_file);
# 4. Write last backup position to marker file
File.open(backup_folder+"/last_backed_up", "w") do | file |
file << youngest
end
</code></pre></div></div>
<h2 id="svn-daily-backuprb">svn-daily-backup.rb</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Full Backup script for Subversion Repository #
require 'date'
# 1. Initialize variables
backup_folder = "c:/Backup"
repo_folder = "c:/Repository/Imagine"
youngest = `svnlook youngest #{repo_folder}`.strip
next_backup_file = "daily-incremental-backup.#{Time.now.strftime("%Y%m%d")}.dmp"
compress_script = backup_folder+"/compress-"+youngest+".tzs"
compress_command = "C:/Program Files/TUGZip/TzScript.exe #{compress_script}"
# 1.a. Get last backed up revision
last_backup = 0;
if File.exist?(backup_folder+"/last_backed_up")
File.open(backup_folder+"/last_backed_up", "r") do | file |
while line = file.gets
last_backup = line.strip.to_i
end
end
end
# 2. Perform backup
if last_backup == youngest.to_i
puts "No new revision since last backup .. exiting"
exit 0;
end
start_backup = last_backup + 1
puts "Backing up from revision #{last_backup} to revision #{youngest}\n";
result = `svnadmin dump --incremental --revision #{last_backup}:#{youngest} #{repo_folder} > #{backup_folder}/#{next_backup_file}`
# 3. Compress backup result
# 3.1 Creating temporary zip script [TugZip Required]
puts "Generating compress script ...\n";
tzs_file = < <-TZS
function main()
{
var Comp = new Compress();
Comp.Archive = "#{backup_folder}/#{next_backup_file}.zip";
Comp.Type = "ZIP";
Comp.Compression = 3;
Comp.WorkingDir = "#{backup_folder}";
Comp.Data = "#{next_backup_file}";
Comp.Password = "";
Comp.DateExtension = 0;
Comp.TimeExtension = 0;
Comp.Overwrite = 1;
Comp.Recurse = 0;
Comp.StoreFolderNames = 1;
Comp.IncludeHiddenFiles = 1;
Comp.DoCompress();
}
TZS
File.open(compress_script, "w") do | file |
file << tzs_file
end
# 3.2. Executing compression
puts "Compressing dump file...\n";
puts `#{compress_command}`
# 3.3. Deleting temporary zip script
puts "Deleting compress script ...\n";
File.delete(compress_script);
# 3.4 Deleting uncompressed dump file
puts "Deleting uncompressed dump file ...\n";
File.delete(backup_folder+"/"+next_backup_file);
# 4. Write last backup position to marker file
File.open(backup_folder+"/last_backed_up", "w") do | file |
file << youngest
end
</code></pre></div></div>
<p>Langkah terakhir, buat bat script sederhana untuk mengeksekusi script ruby ini. Dengan adanya bat script, kita bisa dengan mudah menambahkan entri di Windows Scheduler untuk dijalankan secara otomatis dan terjadwal.</p>
<h2 id="svn-weekly-backupbat">svn-weekly-backup.bat</h2>
<p><code class="language-plaintext highlighter-rouge">ruby C:\Backup\svn-weekly-backup.rb</code></p>
<h2 id="daily-full-backupbat">daily-full-backup.bat</h2>
<p><code class="language-plaintext highlighter-rouge">ruby C:\Backup\svn-daily-backup.rb</code></p>
<p>Terakhir, buat folder c:\Backup dan letakkan file-file berikut dalam folder tersebut:</p>
<ul>
<li>
<p>svn-weekly-backup.rb</p>
</li>
<li>
<p>svn-daily-backup.rb</p>
</li>
<li>
<p>svn-weekly-backup.bat</p>
</li>
<li>
<p>svn-daily-backup.bat</p>
</li>
</ul>
<p>Sistem backup Anda siap digunakan.</p>
<table>
<tbody>
<tr>
<td>Buat yang malas copy-paste, silahkan <a href="http://endy.artivisi.com/downloads/code/svn-backup-windows.tar.bz2">download keempat file tersebut</a> [tar.bz2</td>
<td>1.4 KB].</td>
</tr>
</tbody>
</table>
<p>**
Perhatian: Untuk menjalankan kode ini dengan baik, Anda harus menginstal aplikasi <a href="http://www.tugzip.com">TugZIP</a>. Ini dibutuhkan untuk mengkompres file hasil dump. Buat yang lebih suka WinZip, WinRAR, atau aplikasi kompresi yang lain, silahkan memikirkan cara memanggilnya dari command prompt dan memodifikasi kode program di atas, yaitu bagian #3: Compress Backup Result.</p>
<p>Selain itu, Anda juga harus menginstal <a href="http://ruby-lang.org/">bahasa pemrograman Ruby</a>. Installernya bisa didownload <a href="http://rubyforge.org/projects/rubyinstaller/">di sini</a>.
**</p>
<p>Selamat menggunakan, semoga bermanfaat :D</p>
Aplikasi Portabel2006-05-12T02:17:27+07:00https://software.endy.muhardin.com/aplikasi/aplikasi-portabel<p>Masih ingat jaman DOS dulu? Untuk menyalakan komputer kita harus memasukkan floppy disk yang lebar. Setelah komputer selesai booting, kalau kita ingin mengetik, ganti disket booting dengan disket berisi aplikasi <a href="http://www.wordstar.org/">Wordstar</a>. Selesai mengetik, ingin menyimpan dokumen, ganti disket Wordstar dengan disket data. Capek bekerja, ingin main game, masukkan disket berisi <a href="http://www.digger.org">Digger</a>.</p>
<p>Hidup begitu mudah dan sederhana. Satu aplikasi muat di dalam satu disket. Berbeda dengan jaman sekarang. Harus instal dulu semua aplikasi. Kalau sudah tidak digunakan dan harddisk penuh, uninstal lagi. Lama-lama registry jadi berantakan dan akibatnya komputer jadi lemot.</p>
<p>Selain itu, kadang ada beberapa di antara kita yang kurang beruntung. Tidak bisa instal aplikasi di komputer yang sedang digunakan. Sebabnya bisa macam-macam. <a href="http://oky.or.id">Ada yang dilarang kantor</a>, ada juga yang cuma pinjam komputer sebentar, sehingga kalo mau instal aplikasi rasanya kurang sopan. Masa cuma pinjam 5 menit saja pakai instal-instal segala.</p>
<p>Nah, sekarang ada solusi jitu. Namanya aplikasi portabel. Artinya, aplikasi ini cukup dicopy ke USB disk dan bisa langsung dijalankan tanpa perlu instalasi.</p>
<p>Ada banyak aplikasi yang disertakan di dalamnya, antara lain:</p>
<ul>
<li>
<p>Gaim: untuk chatting</p>
</li>
<li>
<p>Firefox: untuk browsing</p>
</li>
<li>
<p>Thunderbird: untuk email</p>
</li>
<li>
<p>OpenOffice: untuk menggantikan MS Office</p>
</li>
<li>
<p>dan masih banyak lagi.</p>
</li>
</ul>
<p>Silahkan langsung ke <a href="http://portableapps.com">websitenya</a></p>
PrtScr di Linux2006-05-09T17:03:45+07:00https://software.endy.muhardin.com/aplikasi/prtscr-di-linux<p>Membuat screenshot di Windows itu gampang. Tinggal ikuti <a href="http://mbot.multiply.com/journal/item/225">langkah-langkah yang dijelaskan Oom Mbot</a>, beres. Tapi bagaimana caranya membuat screenshot di Linux?</p>
<p>Gampang, jika Anda pengguna KDE, instal saja KSnapshot. Ini adalah aplikasi kecil yang bisa menangkap tampilan layar.</p>
<p><a href="/images/uploads/2006/05/ksnapshot.png"><img src="/images/uploads/2006/05/ksnapshot.png" alt="KSnapshot " /></a></p>
<p>Ada beberapa mode yang bisa digunakan, yaitu Full Screen, Region, dan Window under cursor. Full screen menangkap seluruh layar, termasuk menunya. Window under cursor hanya menangkap aplikasi yang sedang kita gunakan. Sedangkan Region hanya menangkap wilayah yang kita pilih saja.</p>
<p><a href="/images/uploads/2006/05/ksnapshot-mode.png"><img src="/images/uploads/2006/05/ksnapshot-mode.png" alt="KSnapshot Mode " /></a></p>
<p>Bagaimana kalau kita mau menampilkan popup-menu klik-kanan? Gampang, atur saja delay menjadi beberapa detik. Kemudian klik New Snapshot, dan lakukan klik kanan di aplikasi yang diinginkan. Selang beberapa detik kemudian, KSnapshot akan menangkap gambar.</p>
<p>Berbeda dengan di Windows, di Linux tombol PrtScr tidak secara otomatis menangkap tampilan layar. Tetapi masalah ini mudah diatasi. Kita dapat menyambungkan tombol PrtScr dengan KSnapshot, sehingga apabila tombol tersebut kita tekan, KSnapshot akan muncul.</p>
<p>Caranya, klik kanan tombol K di pojok kiri bawah. Kemudian pilih Menu Editor</p>
<p><a href="/images/uploads/2006/05/kmenu-properties.png"><img src="/images/uploads/2006/05/kmenu-properties.png" alt="Right Click K " /></a></p>
<p>Di panel sebelah kiri, cari aplikasi KSnapshot. Di Debian Sarge, biasanya KSnapshot ada di Graphics, Other Application. Begitu ketemu, langsung saja klik.</p>
<p><a href="/images/uploads/2006/05/kmenu-editor-left.png"><img src="/images/uploads/2006/05/kmenu-editor-left.png" alt="Menu Editor - Left Pane " /></a></p>
<p>Di panel sebelah kanan akan muncul keterangan tentang aplikasi. Kita akan mengatur Shortcut Key, letaknya di baris paling bawah.</p>
<p><a href="/images/uploads/2006/05/kmenu-editor-right.png"><img src="/images/uploads/2006/05/kmenu-editor-right.png" alt="Menu Editor - Right Pane " /></a></p>
<p>Klik tombol shortcutnya, akan muncul dialog kecil. Di sini kita bisa langsung tekan tombol PrtScr. Kemudian klik OK.</p>
<p><a href="/images/uploads/2006/05/edit-shortcut.png"><img src="/images/uploads/2006/05/edit-shortcut.png" alt="Edit Shortcut " /></a></p>
<p>Selesai sudah. Sekarang setiap kali tekan PrtScr, KSnapshot akan segera beraksi.</p>
<p>Pembaca yang teliti akan berpikir, “Bagaimana caranya menangkap gambar KSnapshot itu sendiri? Padahal ketika kita tekan New Snapshot pasti KSnapshot akan menghilang.”</p>
<p>Gampang, jalankan saja dua KSnapshot sekaligus. Yang satu untuk di-capture, satu lagi untuk meng-capture. :D</p>
Hello Hibernate Annotation2006-05-05T00:49:49+07:00https://software.endy.muhardin.com/java/hello-hibernate-annotation<p>Adanya fitur annotation di Java 5 membuka dunia baru bagi para coder Java. Dulu semua metadata mau tidak mau harus disimpan di XML file. Sekarang ada pilihan baru, kita bisa taruh di XML file, atau juga bisa di source code melalui annotation.</p>
<p>Salah satu implementasinya adalah <a href="http://hibernate.org/247.html">annotation untuk hibernate</a> untuk mendefinisikan mapping <a href="http://www.hibernate.org">Hibernate</a>. Sekarang kita tidak perlu lagi <a href="http://endy.artivisi.com/downloads/writings/Hibernate.pdf">membuat *.hbm.xml</a> untuk menyatakan mapping antara Java class dengan tabel di database.</p>
<p>Mari kita lihat kodenya.</p>
<h2 id="categoryjava">Category.java</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">id.web.endy.tutorial</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Category</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">description</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getDescription</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">description</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setDescription</span><span class="o">(</span><span class="nc">String</span> <span class="n">description</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">description</span> <span class="o">=</span> <span class="n">description</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Class di atas akan kita simpan ke database dengan skema tabel (MySQL) sebagai berikut:</p>
<h2 id="category-ddlsql">Category-ddl.sql</h2>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">Category</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">INT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="n">name</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">),</span>
<span class="n">description</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Sebelum jaman annotation, kita harus buat mapping file sebagai berikut.</p>
<h2 id="categoryhbmxml">Category.hbm.xml</h2>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0"?></span>
<span class="cp"><!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"></span>
<span class="nt"><hibernate-mapping></span>
<span class="nt"><class</span> <span class="na">name=</span><span class="s">"id.web.endy.tutorial.Category"</span> <span class="na">table=</span><span class="s">"Category"</span><span class="nt">></span>
<span class="nt"><id</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">column=</span><span class="s">"id"</span><span class="nt">></span>
<span class="nt"><generator</span> <span class="na">class=</span><span class="s">"auto"</span><span class="nt">/></span>
<span class="nt"></id></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"name"</span> <span class="na">column=</span><span class="s">"name"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"name"</span> <span class="na">column=</span><span class="s">"description"</span><span class="nt">/></span>
<span class="nt"></class></span>
<span class="nt"></hibernate-mapping></span>
</code></pre></div></div>
<p>Tapi dengan menggunakan annotation, kita bisa lupakan file Category.hbm.xml. Sebagai gantinya, kita tambahkan sedikit annotation pada class Category sebagai berikut</p>
<h2 id="categoryjava-dengan-annotation">Category.java dengan Annotation</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">id.web.endy.tutorial</span><span class="o">;</span>
<span class="nd">@Entity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Category</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">description</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getDescription</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">description</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setDescription</span><span class="o">(</span><span class="nc">String</span> <span class="n">description</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">description</span> <span class="o">=</span> <span class="n">description</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Id</span><span class="o">(</span><span class="n">generate</span><span class="o">=</span><span class="nc">GeneratorType</span><span class="o">.</span><span class="na">AUTO</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Perbedaan pada kode di atas cuma dua baris saja, yaitu:</p>
<ul>
<li>@Entity</li>
<li>@Id(generate=GeneratorType.AUTO)</li>
</ul>
<p>Selain untuk property dan id biasa, hibernate annotation juga dapat digunakan untuk one-to-many, many-to-many, dan teknik-teknik advanced lainnya.</p>
Booklet Printing2006-05-04T20:06:02+07:00https://software.endy.muhardin.com/aplikasi/booklet-printing<p>Jika Anda adalah kolektor PDF obsesif-kompulsif seperti saya, pasti cepat atau lambat Anda akan mencari cara yang paling efektif untuk mencetak koleksi PDF tersebut.</p>
<p>Misalnya, kita punya <a href="http://ikc.cbn.net.id/umum/endy-php.php">Tutorial PHP-MySQL</a> seperti pada gambar berikut</p>
<p><a href="/images/uploads/2006/05/endy-php.png"><img src="/images/uploads/2006/05/endy-php.png" alt="Modul PHP " /></a></p>
<p>Berhubung ada 81 halaman di file tersebut, kita ingin menghemat kertas dengan cara print bolak balik. Selain itu, kita juga mau cetak 2 halaman per lembar, sehingga yang tadinya seperti ini</p>
<p><a href="/images/uploads/2006/05/normal.png"><img src="/images/uploads/2006/05/normal.png" alt="Normal Page Print " /></a></p>
<p>menjadi seperti ini</p>
<p><a href="/images/uploads/2006/05/2sheet.png"><img src="/images/uploads/2006/05/2sheet.png" alt="Two Sheet per Page " /></a></p>
<p>Tapi sebetulnya, ada lagi yang lebih optimal, yaitu teknik yang dikenal dengan nama keren “booklet printing”, menjadikan susunan halaman menjadi seperti ini:</p>
<p><a href="/images/uploads/2006/05/booklet.png"><img src="/images/uploads/2006/05/booklet.png" alt="Booklet Print " /></a></p>
<p>Saya pernah membuat buku dengan cetakan seperti itu menggunakan printer Canon di Windows. Driver printer Canon tersebut sudah menyertakan fasilitas untuk melakukan booklet printing. Tetapi kalau menggunakan printer selain Canon, yah kita harus cari cara lain.</p>
<p>Setelah googling ke sana kemari, dengan menggunakan keyword “booklet printing kprinter”, akhirnya saya berhasil menemukan <a href="http://wiki.scribus.net/index.php/How_to_make_a_booklet">tutorial ini</a>, yang menjelaskan cara melakukan booklet printing dengan KDE. Berikut langkah-langkahnya, saya contohkan dengan Acrobat Reader.</p>
<h2 id="1-buka-print-dialog-pilih-kprinter-sebagai-print-command-dan-tekan-print">1. Buka Print Dialog, pilih kprinter sebagai print command, dan tekan Print</h2>
<p><a href="/images/uploads/2006/05/acroprint.png"><img src="/images/uploads/2006/05/acroprint.png" alt="Acrobat Reader Print Dialog " /></a></p>
<h2 id="2-dialog-kprinter-muncul-klik-tombol-properties-di-pojok-kanan-atas">2. Dialog KPrinter muncul, klik tombol Properties di pojok kanan atas</h2>
<p><a href="/images/uploads/2006/05/kprinter.png"><img src="/images/uploads/2006/05/kprinter.png" alt="KPrinter Dialog " /></a></p>
<h2 id="3-pilih-tab-filter-kemudian-tambah-filter-baru-dengan-cara-menekan-tombok-corong">3. Pilih tab Filter, kemudian tambah filter baru dengan cara menekan tombok corong</h2>
<p><a href="/images/uploads/2006/05/kprinter-properties.png"><img src="/images/uploads/2006/05/kprinter-properties.png" alt="KPrinter Properties " /></a></p>
<h2 id="4-tambah-filter-baru-dengan-cara-menekan-tombol-corong">4. Tambah filter baru dengan cara menekan tombol corong</h2>
<p>Untuk tahap pertama, yang dipilih adalah Pamphlet Printing - Even Page (step 1)</p>
<p><a href="/images/uploads/2006/05/kprinter-select-filter-1.png"><img src="/images/uploads/2006/05/kprinter-select-filter-1.png" alt="Pamphlet Printing - Even Page (step 1) " /></a></p>
<h2 id="5-balikkan-kertas">5. Balikkan kertas</h2>
<p>Setelah hasil print keluar, balikkan susunan kertas dan letakkan kembali di printer. Untuk tahap ini, kita perlu trial-and error untuk mendapatkan teknik yang tepat. Coba print halaman 1-8 dulu sebagai percobaan.</p>
<h2 id="6-print-sekali-lagi">6. Print sekali lagi</h2>
<p>Lakukan langkah 1-6, dengan sedikit perbedaan. Pada langkah #6, pilih Pamphlet Printing - Odd Page (step 2)</p>
<p><a href="/images/uploads/2006/05/kprinter-select-filter-2.png"><img src="/images/uploads/2006/05/kprinter-select-filter-2.png" alt="Pamphlet Printing - Odd Page (step 2) " /></a></p>
<p>Selesai. Sekarang tinggal diberikan ke tukang jilid. :D</p>
Debian Sarge vs OpenSuSE 10.02006-05-04T01:01:13+07:00https://software.endy.muhardin.com/aplikasi/debian-sarge-vs-opensuse-100<p>Di kantor saya ada PC berkualifikasi cukup canggih untuk ukuran workstation. Spesifikasinya P-4 3GHz HT, 4GB DDR, 2x80GB SATA, DVD RW. PC tersebut sebetulnya dibeli untuk menjadi server. Tetapi karena dia terlambat datang, semua aplikasi server sudah terinstal di mesin lain dan sudah terisi banyak data. Sebelum sempat semua data tersebut dimigrasi, tugas lain buat saya sudah datang bertubi-tubi, sehingga alhasil tidak sempat migrasi. Akhirnya, PC server tersebut turun kasta menjadi ‘<em>sekedar pembakar DVD saja</em>’.</p>
<p>Sejak menjadi pembakar DVD, effort untuk migrasi menjadi semakin sulit. Karena banyak oknum-oknum yang pinjam untuk membackup data. Kalau sekedar pinjam saja tidak masalah, tetapi kadang data yang mau dibackup dicopy dan tidak dibakar-bakar, sehingga semakin sulit untuk reinstall PC tersebut.</p>
<p>Kenapa harus reinstall? Hmm, saya menggunakan Software-RAID dan LVM untuk mempartisi server tersebut, berjalan di atas Debian Sarge. Kernel yang digunakan Debian Sarge (pada waktu saya instal PC tersebut) adalah 2.6.8, sedangkan dukungan LVM baru ada di 2.6.13. Jadi, terpaksa menggunakan kernel 2.4.sekian yang sudah bisa menangani LVM. Tapi dengan kernel tersebut, memori yang didukung cuma maksimal 1GB, sehingga sisanya 3GB lagi makan gaji buta. Dengan demikian, solusi yang paling feasible adalah instal ulang dengan distro lain. Buat linux-mania yang sedang siap-siap tulis komentar, tidak usah repot-repot, “Saya tidak akan kompile kernel, terimakasih…. “</p>
<p>Berdasarkan anjuran <a href="http://sleepless.ngoprek.org">teman saya</a>, saya pilih <a href="http://www.opensuse.org">OpenSuSE 10.0</a>. Selain paketnya lebih maju daripada Debian, <a href="http://suse.cbn.net.id">mirrornya juga ada di Indonesia</a>. Belakangan saya tau kalo di CBN itu gak komplit. Tapi sudah terlambat … :P</p>
<p>Jadi akhirnya saya lakukan juga migrasi ke OpenSuSE. Berikut adalah daftar aplikasi yang ingin dimigrasi:</p>
<ul>
<li>
<p>Repository Subversion dengan sebesar 5GB</p>
</li>
<li>
<p>Bug Database</p>
</li>
</ul>
<p>Konfigurasi server:</p>
<ul>
<li>
<p>RAID + LVM</p>
</li>
<li>
<p>HTTPS</p>
</li>
<li>
<p>LDAP Authentication</p>
</li>
<li>
<p>PHPLDAPAdmin</p>
</li>
</ul>
<p>Dan berikut adalah langkah-langkah migrasinya</p>
<ol>
<li>Install subversion, jangan lupa <a href="http://endy.artivisi.com/blog/aplikasi/instalasi-subversion/">buat group repousers dan mask-script</a></li>
<li>Install apache2</li>
<li><a href="http://endy.artivisi.com/blog/aplikasi/subversion-backup-dan-restore/">Dump data repository dari repo lama</a></li>
<li>
<p>Create repository di tempat baru, dan load hasil dump</p>
</li>
<li>
<p>Dump data otentikasi (username dan password) dari OpenLDAP yang lama
<code class="language-plaintext highlighter-rouge">slapcat -c -l data-otentikasi.ldif</code></p>
</li>
<li>Config slapd.conf</li>
</ol>
<ul>
<li>
<p>suffix = artivisi.com</p>
</li>
<li>
<p>disable rootdn dan rootpw. Di Debian tidak ada entri ini. Entri ini menyebabkan saya (entah kenapa) tidak bisa login, padahal username dan password sudah benar</p>
</li>
<li>
<p>access level</p>
</li>
</ul>
<ol>
<li>
<p>Load data otentikasi ke OpenLDAP yang baru
<code class="language-plaintext highlighter-rouge">slapadd -c -l data-otentikasi.ldif</code></p>
</li>
<li>
<p>Konfigurasi Apache:</p>
</li>
</ol>
<ul>
<li>
<p>Add user untuk apache (di OpenSuSE 10.0 namanya wwwuser, kalo di Debian www-data) dalam grup repousers</p>
</li>
<li>
<p>Buat file svn.conf di folder conf.d</p>
</li>
<li>
<p>Instalasi Subversion bawaan OpenSuSE 10.0 tidak ada mod_dav_svn.so dan mod_authz_svn.so, jadi copy aja dari Debian</p>
</li>
<li>
<p>Selanjutnya <a href="http://endy.artivisi.com/blog/aplikasi/konfigurasi-user-subversion-di-xampp/">sama dengan tulisan saya sebelumnya</a></p>
</li>
</ul>
<ol>
<li>Instalasi PHPLDAPAdmin.</li>
</ol>
<ul>
<li>
<p>Download di <a href="http://phpldapadmin.sourceforge.net">websitenya</a>. Paket standar OpenSuSE tidak menyertakan, tidak seperti apt-get</p>
</li>
<li>
<p>Extract dan edit config.php</p>
</li>
<li>
<p>Install php-session bila ketemu error session_module_name() not found</p>
</li>
</ul>
<ol>
<li>Aktifkan https</li>
</ol>
<ul>
<li>
<p><a href="http://www.eclectica.ca/howto/ssl-cert-howto.php">buat sertifikat</a></p>
</li>
<li>
<p>konfigurasi apache, buat vhost</p>
</li>
<li>
<p><a href="http://en.opensuse.org/SDB:Apache_2_FAQ_(Frequently_Asked_Questions)#How_do_I_configure_Apache_2_for_SSL.3F">edit /etc/sysconfig/apache2, tambahkan ssl di APACHE_MODULES dan “SSL” di APACHE_SERVER_FLAGS</a></p>
</li>
</ul>
<p>Kesimpulan akhir:</p>
<ol>
<li>
<p>Paket instalasi apt-get di <a href="http://komo.vlsm.org">rumahnya Si Komo</a> masih belum terkalahkan. Untuk menginstal aplikasi demi aplikasi di OpenSuSE, saya harus menjadi <em>human-cd-changer. Mirror sih ada, tapi internasional semua, sangat lemot dari tempat saya</em>.</p>
</li>
<li>
<p>Entah salah setting atau gimana, kecepatan komputer tidak se-responsif ekspektasi saya.</p>
</li>
<li>
<p>Paket aplikasi Debian masih jauh lebih kuno daripada OpenSuSE. Misalnya OpenOffice, di Debian masih versi 1, sedangkan di distro lain sudah versi 2.</p>
</li>
<li>
<p>Dukungan hardware OpenSuSE lebih banyak.</p>
</li>
<li>
<p>Konfigurasi grafis disediakan dalam Yast, tapi modifikasi manual masih memungkinkan.</p>
</li>
</ol>
Belajar Java, mulai dari mana?2006-04-28T18:43:54+07:00https://software.endy.muhardin.com/java/belajar-java-mulai-dari-mana<p>Ini adalah pertanyaan yang paling sering ditanyakan ke saya, baik melalui Y!, email, ataupun tatap muka di kelas.</p>
<p>Saya sendiri belajar Java secara otodidak. Tidak melalui bangku kuliah (saya kuliah Teknik Industri), tidak juga ikut kursus atau pelatihan. Pada waktu itu -sekitar tahun 2002- milis <a href="http://groups.yahoo.com/group/jug-indonesia/">jug-indonesia</a> dan <a href="http://groups.yahoo.com/group/jlinux/">jlinux</a> belum seramai sekarang. Sehingga untuk konsultasi dan tanya-jawab agak sulit. Perlu diperhatikan juga bahwa pada masa itu blog belum ngetren. Jarang ada blog yang membahas pemrograman Java dalam bahasa Indonesia.</p>
<p>Singkat kata, resource di internet tidak sebanyak saat ini (awal 2006).</p>
<p>Sebelum mulai belajar Java, kemampuan teknis saya adalah sebagai berikut:</p>
<ul>
<li>
<p>Bisa menggunakan Linux, tapi sebatas user, bukan administrator ahli.</p>
</li>
<li>
<p>Mengerti tentang basic networking (IP Address, Subnet, cara kerja DNS, dan sedikit tentang routing)</p>
</li>
<li>
<p>Mengerti sedikit tentang relational database. SELECT, INSERT, UPDATE sih bisa.</p>
</li>
<li>
<p>Bisa PHP sedikit-sedikit. Cuma bisa HelloWorld, dan simpan isian form HTML ke dalam database. Tapi untuk aplikasi skala besar belum pernah buat.</p>
</li>
</ul>
<p>Dengan modal pas-pasan seperti itu, saya berhasil diterima bekerja di sebuah kursus pendidikan franchise dari India. Waktu itu mereka baru membuka cabang di Surabaya, dan saya masuk sebelum grand-launching dilakukan.</p>
<p>Karena masih baru, siswa peserta kursus juga masih sedikit.Seingat saya, sehari cuma ada satu kelas selama 4 jam sehari. 4 jam sisanya, 20 perangkat komputer terbaru terhubung dengan jaringan termasuk satu asisten lab (tidak lain dan tidak bukan adalah saya) praktis menjadi pengangguran. Internet di sana dial-up, tapi ada beberapa buku pelajaran dan referensi di perpustakaan mini.</p>
<p>Waktu luang banyak, komputer banyak, buku banyak, internet agak lemot. Ya sudah, akhirnya saya ngoprek saja sendirian. Instruktur di sana, walaupun native speaker India, nampaknya kurang kompeten, sehingga kalo tanya ke dia, yang ada malah tambah bingung.</p>
<p>Minggu-minggu pertama saya habiskan membuat website dengan PHP. Berusaha bikin Content Management System kecil-kecilan. Tapi karena belum pengalaman, yang ada malah berantakan. Pengguna bukannya menjadi mudah malah menjadi sulit, karena untuk posting artikel harus mengerti HTML dan PHP :D. Padahal niatnya mau memudahkan. Yah, mau bagaimana lagi … ada masanya ketika kita masih muda dan bodoh :P</p>
<p>Bosan dengan PHP, saya mulai lihat-lihat buku di perpustakaan. Ada VB, Java, Windows 2000 Server, Oracle, dan sebagainya. Entah karena background saya yang pengguna Linux, saya kurang tertarik belajar VB. Selain itu, instruktur di sana (menganggap dirinya) master VB. Jadi saya tidak mau kompetitif dengan belajar VB juga. Oracle terlalu sulit buat saya (waktu itu). Instalasi saja sulitnya setengah mati. Installernya terlalu banyak tanya ini-itu yang saya gak ngerti apa jawabannya. Windows 2000 Server juga kelihatan kurang menarik. Jadilah akhirnya saya pilih belajar Java saja.</p>
<p>Saya coba belajar dari buku yang ada. Referensinya waktu itu Core Java vol 1 dan 2 karangan Cay Horstmann. Belakangan saya tau kalo buku itu sangat bagus dan lengkap. Ada beberapa konsep rumit seperti <em>anonymous inner class</em> yang dijelaskan dengan sangat baik di sana. Tapi untuk pemula, Core Java itu relatif sulit dimengerti.</p>
<p>Sekitar sebulan saya berputar-putar mencari cara belajar dan referensi yang bagus. Karena kualitas dan kecocokan referensi dengan cara belajar kita akan sangat berpengaruh terhadap kecepatan belajar.</p>
<p>Setelah browsing ke <a href="http://java.sun.com">website Sun</a>, saya berhasil menemukan <a href="http://java.sun.com/docs/books/tutorial/index.html">tutorial Java</a> dan <a href="http://java.sun.com/j2se/1.5.0/docs/api/index.html">dokumentasi Java</a>. Ini merupakan referensi yang benar-benar cocok buat saya. Dengan bermodalkan dua bahan tersebut, akhirnya dua bulan berikutnya menjadi terang-benderang.</p>
<p>Satu demi satu konsep Java saya pelajari:</p>
<ol>
<li>
<p>Sintaks dan Semantic (for loop, statement, if-else, dsb)</p>
</li>
<li>
<p>Konsep OOP</p>
</li>
<li>
<p>Implementasi Class dan Object di Java</p>
</li>
<li>
<p>Package</p>
</li>
<li>
<p>I/O</p>
</li>
<li>
<p>Collection</p>
</li>
<li>
<p>Swing</p>
</li>
<li>
<p>Thread</p>
</li>
<li>
<p>JDBC</p>
</li>
<li>
<p>dsb</p>
</li>
</ol>
<p>Sampai akhirnya saya lumayan bisa membuat aplikasi desktop sederhana yang mengakses database.</p>
<p>Pada bulan keempat, saya masuk ke kantornya instruktur dan menemukan buku baru. Modul pelatihan Java Servlet. Wah, ada mainan baru. Segera saja saya coba semua contoh kodenya. Lumayan dari buku tersebut saya bisa memahami web.xml, servlet, dan application server. Waktu itu <a href="http://tomcat.apache.org">Tomcat</a> belum terkenal seperti sekarang. Saya pakai Java Web Server untuk mendeploy servlet.</p>
<p>Sekitar enam bulan kemudian, saya sudah cukup mengerti apa itu EJB (waktu itu masih versi 1.x) dan bisa mendeploy EJB kecil-kecilan.</p>
<p>Kemudian saya ditugaskan menjadi dosen di Stikom, sebagai bagian dari paket kerjasama kursus tempat saya bekerja dengan Stikom. Di sana lebih banyak teman diskusi dan buku.</p>
<p>Dari titik ini, perjalanan belajar Java menjadi lebih mudah. Karena selain perpustakaan cukup lengkap, milis java juga sudah lumayan aktif. Sehingga saya dapat mainan baru seperti Hibernate dan Ant.</p>
<p>Sudah cukup panjang ceritanya. Pesan moral dari cerita ini adalah:</p>
<ol>
<li>
<p>Dengan waktu luang dan referensi yang tepat, belajar Java secara otodidak sangat mungkin dilakukan</p>
</li>
<li>
<p>Download <a href="http://java.sun.com/docs/books/tutorialNB/download/tutorial.zip">tutorial</a> dan <a href="http://java.sun.com/j2se/1.5.0/download.jsp">dokumentasi</a> Java yang dikeluarkan Sun.</p>
</li>
<li>
<p>Banyak berlatih (saya berlatih 6-8 jam sehari, setelah jam kantor saya masih stay untuk belajar)</p>
</li>
<li>
<p>Untuk dapat melakukan poin #1 dan #2, kemampuan bahasa Inggris (read-only sudah cukup) wajib dimiliki.</p>
</li>
</ol>
<p>Sedikit saran dari saya, cobalah berkontribusi di milis. Baik bertanya maupun menjawab. “Bagaimana kalo saya menjawab tapi jawabannya salah? Nanti diketawain … “
Jangan takut. Menjawab itu bagian dari belajar. Ini berarti kita mengujicoba pemahaman kita terhadap sesuatu.</p>
<p>Kalau pemahaman kita benar, komunitas akan memperkuat, dan kadang menambahkan sudut pandang yang berbeda sehingga pengetahuan kita semakin kaya.
Kalau kita salah, akan ada yang mengoreksi. Sehingga pemahaman kita yang salah tersebut tidak akan terbawa sampai tua dan terungkap dalam event yang jauh lebih memalukan.</p>
<p>Nada-nada pedas dan komentar tidak ramah jangan sampai mengendurkan semangat. Anggap saja sebagai biaya kursus, karena : “Hei, ternyata bertanya di milis gak bayar. GRATIISS !!!”</p>
<p>Ok, selamat belajar mandiri :D</p>
Java: Tidak untuk Pemula2006-04-27T01:27:35+07:00https://software.endy.muhardin.com/java/java-tidak-untuk-pemula<p>Beberapa kali sepanjang perjalanan hidup, saya berkesempatan untuk mengajarkan Java kepada orang lain. Satu hal yang saya simpulkan dari pengalaman tersebut adalah “Java kurang cocok bagi pemula”.</p>
<p>Beberapa orang yang tidak setuju sekarang sedang bersiap-siap menggulung scroll-bar ke bawah, ke bagian komentar, untuk menumpahkan uneg-unegnya. :D Tapi sabar dulu, baca sampai selesai dan Anda akan mengerti maksud saya.</p>
<p>Sebelum kita mulai, mari definisikan dulu kata “pemula”. Pemula yang saya maksudkan adalah orang yang sama sekali belum pernah coding. Bisa memformat harddisk tidak masuk hitungan.</p>
<p>Ok, sekarang bayangkan Anda adalah seorang pemula. Bersemangat tinggi ingin belajar pemrograman, soalnya kayaknya titel programmer terlihat keren di kartu nama. Berdasarkan hasil browsing dan chatting di sana-sini, kata orang sih sekarang jamannya Java. Baiklah, mari kita belajar Java.</p>
<p>Anda ikut training Java -seperti lazimnya sopan santun di dunia pemrograman- hal pertama yang diajarkan instruktur adalah Hello World.</p>
<p>Instruktur: Selamat pagi bapak dan ibu. Sekarang kita akan belajar Hello World. Silahkan buka Notepad, dan ketik kode berikut:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HelloWorld</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">xx</span><span class="o">){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Hello World"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Instruktur: Dengan kode di atas, kita akan dapat mencetak tulisan “Hello World” ke layar. Bagaimana? Hebat kan?</p>
<p>Peserta: !@#$%[sumpah serapah disensor :P ]. Sama sekali tidak hebat. Apa itu class? Kenapa harus public? Apa artinya void? static? Berarti ada dynamic dong? Apa bedanya kurung kotak [] dengan kurung bulat () dan kurung kurawal {} ? …. [lagi-lagi disensor karena pertanyaan terlalu banyak]</p>
<p>Java, memang adalah bahasa yang mature. Sudah stabil (artinya tidak terlalu banyak perubahan fundamental) dan sudah teruji digunakan berbagai aplikasi besar dengan sukses. Tetapi tidak berarti mudah bagi pemula.</p>
<p>Seperti pada contoh sederhana di atas, sebetulnya baris yang ingin kita ajarkan adalah:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Hello World"</span><span class="o">);</span>
</code></pre></div></div>
<p>Tapi ada banyak baris lainnya yang ikut muncul. Karena ya di Java untuk Hello World memang itu kebutuhan minimalnya. Bandingkan dengan:</p>
<p><strong>Ruby</strong></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">puts</span> <span class="s2">"Hello World"</span>
</code></pre></div></div>
<p><strong>PHP</strong></p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">echo</span><span class="p">(</span><span class="s2">"Hello World"</span><span class="p">);</span>
</code></pre></div></div>
<p>Perhatikan bahwa saya tidak mempermasalahkan urusan kompile dan eksekusi, karena itu memang sudah konsep dasar Java bahwa source code harus dikompilasi.</p>
<p>Jadi, kesimpulannya adalah untuk mengajarkan Hello World, kita juga harus mengajarkan tentang:</p>
<ol>
<li>
<p>Apa itu class</p>
</li>
<li>
<p>Akses level untuk class dan method, kalo gak pake public gimana? Gak bisa diakses di luar package. Jadi, harus ajarkan juga tentang ….</p>
</li>
<li>
<p>Apa itu package</p>
</li>
<li>
<p>Konsep method dan return value</p>
</li>
<li>
<p>Array</p>
</li>
<li>
<p>Perbedaan class method (static) dan instance method</p>
</li>
</ol>
<p>Waaa … :(
Berdasarkan keterangan di atas, saran saya untuk yang belum pernah coding sebelumnya adalah belajar dengan bahasa lain dulu. Misalnya PHP yang sintaksnya agak mirip. Nanti kalo sudah tau apa itu array, function, class, object, baru belajar Java.</p>
<p>Ini akan membuat hidup jadi lebih mudah untuk yang belajar, juga untuk yang mengajari.</p>
Katak dalam tempurung2006-04-20T01:59:26+07:00https://software.endy.muhardin.com/life/katak-dalam-tempurung<p>Rupanya <a href="http://endy.artivisi.com/blog/life/pengetahuan-wajib-buat-programmer/">standar pengetahuan bagi programmer</a><a href="http://endy.artivisi.com/blog/life/pengetahuan-wajib-buat-programmer/"> ala saya</a> banyak mendapat protes di sana sini. Tidak hanya itu sebetulnya. Beberapa waktu yang lalu saya sempat posting di milis tentang <a href="http://groups.yahoo.com/group/jug-indonesia/message/21651">standar pengetahuan seorang dosen pemrograman</a><a href="http://groups.yahoo.com/group/jug-indonesia/message/21651"> juga ala saya</a>, yang pada intinya adalah:</p>
<ul>
<li>keep on learning, termasuk cara pakai version control</li>
<li>bikin aplikasi yang berkualitas production (bukan cuma demo)</li>
<li>
<p>sering-sering live coding
Rupanya banyak orang yang walaupun setuju, menganggap standar saya terlalu tinggi dengan alasan antara lain:</p>
</li>
<li>standar tersebut tidak berlaku bagi freshman, tapi hanya untuk experienced</li>
<li><a href="http://groups.yahoo.com/group/jug-indonesia/message/21660">dosen tidak perlu bisa teknis praktis (seperti menggunakan version control), cukup asisten lab saja. Dosen mah, teoritis saja.</a></li>
<li>bukan bagian saya (kan sudah ada DBA, Network Admin, Architect, dsb)</li>
<li>menurut spesifikasi Sun, tidak perlu bisa semua itu
Menurut saya, berbagai pendapat di atas ada benarnya. Tetapi mari kita tinggalkan sejenak soal benar-salahnya pendapat dan melihat dari perspektif lain.</li>
</ul>
<p>Harus diakui, kita sangat jauh ketinggalan dalam hal teknologi informasi. Di luar negeri sana, dosen tidak hanya sekedar mengoceh di dalam kelas menjelaskan Teori Automata. Dia membuat implementasi parser, menyumbangkan parser tersebut menjadi open source, dan tidak lupa memasang foto konyol di <a href="http://antlr.org/">website parser</a> tersebut.</p>
<p>Di tempat lain, ada yang membuat <a href="http://subversion.tigris.org">version control yang mampu mengelola file binary</a> (bukan hanya teks) dengan efisien. Tidak dapat diragukan, <a href="http://www.red-bean.com/sussman/">orang tersebut</a> mengerti lebih dari satu algoritma diff untuk file binary, membandingkan semuanya, dan memilih yang paling baik. Dan ini dilakukan di waktu luang, <a href="http://www.red-bean.com/pluess-sussman">sambil bermain musik</a>.</p>
<p>Bagaimana dengan di Indonesia? Hmm .. pakai version control saja tidak bisa, apalagi bikin version control.</p>
<p>Dengan kenyataan seperti itu, sepertinya kita santai sekali kalau menganggap standar saya terlalu tinggi.</p>
<p>Seharusnya standar itu diset empat kali lebih tinggi lagi. Kenapa?</p>
<p>Begini.. misalnya standar kita sama dengan standar mereka, maka selisih pengetahuan kita dengan orang-orang bule tersebut akan tetap. Jadi kalo sekarang mereka 10 tahun lebih maju, pada 5 tahun yang akan datang kita akan tetap 10 tahun ketinggalan. <br />
Iya dong, mereka kan juga berkembang.</p>
<p>Nah, kalo standar kita lebih tinggi daripada mereka, maka setiap tahun kita akan bisa menyusul sedikit demi sedikit, sehingga pada suatu saat nanti (hopefully) kita akan sama cerdas dan maju dengan mereka.</p>
<p>Jadi, sampai kapan kita mau terus berpuas diri? Seperti katak dalam tempurung. Kita kira kepala sudah menyundul langit, padahal yang dikira langit cuma batok kelapa. Sampai suatu saat ada orang yang menendang batok tersebut dan si katak (hopefully bukan kita) sadar kalo tempurung != langit.</p>
<p>Beruntung kalo cuma ditendang orang sehingga si katak sadar. Kalo digilas metromini? Gak sempat lihat langit deh. Sampai mati tetap beranggapan tempurung == langit.</p>
<p>Ok, mau sampai kapan santai terus? Mudah-mudahan tidak sampai semua emas, minyak, hutan Indonesia dihabiskan bule sehingga kita tinggal punya utang.</p>
<p>Mari belajar, berkarya, dan berkontribusi buat komunitas.</p>
Menghadang spam secara sederhana2006-04-19T18:21:01+07:00https://software.endy.muhardin.com/aplikasi/menghadang-spam-secara-sederhana<p>Rupanya tidak di Indonesia saja orang yang kurang kerjaan. Mentang-mentang sekarang gampang sekali mendapatkan akses internet, segerombolan orang kurang kerjaan seenaknya menginstal aplikasi spammer.</p>
<p>Spam di email saya kebanyakan (sekitar 80%) isinya adalah iklan obat dan software. Sedangkan di blog, hampir semuanya adalah judi online.</p>
<p>Lama-lama bosan juga memoderasi komentar orang-orang. <br />
Jadi begitu ada waktu luang, saya segera eksplorasi mencari plugin anti-spam untuk Wordpress. Hasilnya, ada beberapa alternatif, misalnya Akismet, yang sudah terinstal secara default pada WP 2.0.</p>
<p>Sayangnya aktifasi Akismet membutuhkan API key yang didapat dengan cara daftar di <a href="http://www.wordpress.com">wordpress.com</a>. Karena saya malas daftar, akhirnya browsing ke <a href="http://codex.wordpress.org">dokumentasi Wordpress</a>.</p>
<p>Di sana menemukan <a href="http://www.herod.net/dypm/">plugin sederhana tapi ampuh</a>. Idenya seperti CAPTCHA, tapi lebih sederhana. Tidak menggunakan image, melainkan cukup dengan operasi tambah-tambahan sederhana.</p>
<p>Yah cukuplah untuk menangkis spambot. Silahkan coba pluginnya dengan cara memberikan komentar.</p>
Interview Java2006-03-30T01:56:58+07:00https://software.endy.muhardin.com/java/interview-java<p>Hasil interview online saya dengan Sony AK sudah dipublish.</p>
<p>Silahkan <a href="http://www.sony-ak.com/articles/6/intv_java_growth_indonesia.php">baca sendiri</a>.</p>
Weekend Crash Session2006-03-30T01:23:24+07:00https://software.endy.muhardin.com/manajemen/weekend-crash-session<p>Seperti kebanyakan perusahaan IT lainnya, client saya sekarang juga sering mengalami kejar tayang. Berbagai alasan menjadi penyebabnya. Tapi kita tidak mencari kambing hitam di sini. Yang paling penting adalah <em>Lesson Learned</em> dan _Best Practices_nya. Karena toh yang namanya krisis memang tidak direncanakan dan bisa terjadi kapan saja.</p>
<p>Jadi, dari beberapa kali ngantor di hari libur dan beberapa kali terpaksa melewatkan bioskop TransTV jam 21 di rumah, berikut adalah pelajaran yang dapat diambil.
<strong>1. Jangan buru-buru mulai bekerja.</strong>
Lho, katanya kejar tayang, lalu tunggu apa lagi? Mari segera mulai.
Salah besar. Hal pertama yang harus dilakukan adalah membuat daftar tugas yang harus diselesaikan. Setelah itu, susun prioritas dan tentukan PIC (<em>person in charge</em>, alias oknum yang harus melakukan). <a href="http://www.openoffice.org/product/calc.html">Aplikasi spreadsheet</a> adalah tools yang paling tepat untuk ini. Kita bisa sort, filter, dan melakukan perhitungan dengan cepat.</p>
<p><a href="/images/uploads/2006/03/dsc02995.jpg"><img src="/images/uploads/2006/03/dsc02995.jpg" alt="Khalisa In Action " /></a></p>
<p><strong>2. Tentukan load masing-masing orang</strong>
Ada kalanya, tugas tertentu tergantung pada orang tertentu. Temukan ketergantungan ini, dan hitung dengan teliti. Bisa jadi pembagian tugasnya sudah rata, tapi ternyata pembagian ketergantungan tidak dilakukan, sehingga akan terjadi bottleneck pada orang tertentu.</p>
<p><strong>3. Cegah bottleneck dengan delegasi (a.k.a load balancing)</strong>
Orang yang punya tugas atau dependensi paling tinggi biasanya disebabkan karena dia memiliki informasi yang dibutuhkan sehingga sulit untuk digantikan. Jangan khawatir, suruh orang tersebut menulis langkah-langkah penyelesaian setiap tugas sedetil mungkin, kemudian delegasikan tugas tersebut. Biasanya menulis instruksi lebih cepat daripada menyelesaikan sendiri tugas tersebut. Dengan demikian, beban oknum bottleneck bisa berkurang.
<strong>4. Buat checkpoint</strong>
Tentukan jam-jam tertentu di mana setiap orang akan diperiksa progressnya. Ini akan memungkinkan deteksi masalah seawal mungkin. Sehingga masalah besar akan cepat diketahui dan diantisipasi. Keuntungan kedua, semua orang akan merasa bahwa pekerjaannya akan dikumpulkan, dan bekerja dengan lebih serius.</p>
<p>Berikutnya, tips terakhir, yang paling ditunggu-tunggu.
<strong>5. Sediakan makanan</strong>
Perusahaan yang mengharuskan karyawannya kerja lembur biasanya tidak keberatan untuk menyediakan makanan. Sekilas tampak merugikan perusahaan secara finansial. Tapi kalau dipikir lebih jauh, dengan memastikan urusan perut beres, ada keuntungan ganda yang diperoleh:</p>
<ul>
<li>
<p>Semua orang bekerja dengan tenang, tanpa memikirkan mau makan apa/dimana</p>
</li>
<li>
<p>Orang-orang tidak perlu meninggalkan tempat untuk makan (minimal akan menghabiskan waktu 2 jam sekali makan, untuk jalan, ngobrol, merokok, mematikan rokok dan menyadari bahwa orang lain masih merokok kemudian menyalakan sebatang lagi, dan seterusnya)</p>
</li>
</ul>
<p>Semoga lembur Anda menyenangkan</p>
<p><a href="/images/uploads/2006/03/dsc03125.jpg"><img src="/images/uploads/2006/03/dsc03125.jpg" alt="Khalisa Gigit Jari " /></a></p>
Netbeans 5.0 dan Matisse2006-03-28T20:22:59+07:00https://software.endy.muhardin.com/java/netbeans-50-dan-matisse<p>Beberapa orang di milis JUG sangat berkomentar tentang canggihnya Swing Editor di Netbeans, yang lebih terkenal dengan nama Matisse. Berbagai fitur canggih seperti:</p>
<ul>
<li>
<p>Snap-in placement. Komponen otomatis lengket ke lokasi penempatan. Mirip seperti kalau kita menggeser-geser WinAmp di pojok layar.</p>
</li>
<li>
<p>Dynamic Layout. Dengan setting mudah, layout menjadi kebal terhadap resize dan perbedaan resolusi</p>
</li>
<li>
<p>Baseline Alignment. Label, tombol, dan text component diatur agar tulisannya segaris.</p>
</li>
</ul>
<p>sudah terinstal secara default.
So, berbekal pengalaman buruk pahitnya mencoding GridBagLayout, saya segera mendonlod Netbeans dan berharap akan adanya titik terang dalam pembuatan desktop apps di Java. Test singkat saya adalah menghasilkan tampilan seperti ini:</p>
<p><a href="/images/uploads/2006/03/netbeans-hasil.png"><img src="/images/uploads/2006/03/netbeans-hasil.png" alt="Hasil akhir tampilan " /></a></p>
<p>Berikut adalah tampilan editor di Netbeans:
<a href="/images/uploads/2006/03/netbeans-design.png"><img src="/images/uploads/2006/03/netbeans-design.png" alt="Netbeans Design View " /></a></p>
<p>Tapi, seperti biasa, kode yang dihasilkan sama sekali tidak indah. Coba lihat sendiri:</p>
<p><a href="/images/uploads/2006/03/netbeans-code.png"><img src="/images/uploads/2006/03/netbeans-code.png" alt="Autogenerated code by Netbeans " /></a></p>
<p>Tapi secara keseluruhan .. lumayanlah. Membuat tampilan seperti di atas cuma dalam waktu 5 menit itu sudah kemajuan besar.</p>
<p>PR Swing selanjutnya tinggal masalah binding dan validation yang masih harus ‘kerja keras’.</p>
Konfigurasi user Subversion di XAMPP2006-03-25T00:23:48+07:00https://software.endy.muhardin.com/aplikasi/konfigurasi-user-subversion-di-xampp<p>Berikut cara konfigurasi repository Subversion pada Windows agar bisa diakses melalui http. Ijin akses hanya diberikan pada user yang sudah terdaftar dalam database.</p>
<p>Sebetulnya tutorial versi Linux juga ada. Tapi sepertinya pengguna Windows masih ada yang menemui kesulitan, walaupun sudah pernah diberikan training.</p>
<p>Aplikasi berikut harus sudah terinstal:</p>
<ol>
<li>
<p><a href="http://subversion.tigris.org">Subversion</a></p>
</li>
<li>
<p><a href="http://www.apachefriends.org/en/xampp.html">XAMPP</a>, gunakan versi 1.4</p>
</li>
</ol>
<p>Pertama, kita akan membuat user dengan fasilitas htpasswd yang ada di Apache.</p>
<ol>
<li>
<p>Masuk ke folder apache
<code class="language-plaintext highlighter-rouge">c:\> cd c:\xampp\apache\bin</code></p>
</li>
<li>
<p>jalankan htpasswd untuk membuat database username/password, misalnya namanya user.txt
<code class="language-plaintext highlighter-rouge">c:\> htpasswd -cm user.txt endy
New password: ****
Re-type new password: ****
Adding password for user endy</code></p>
</li>
</ol>
<p>Setelah itu, htpasswd akan membuatkan file database user di file user.txt
Kira-kira isinya seperti ini:
<code class="language-plaintext highlighter-rouge">endy:$apr1$m15.....$LbO0nY5v2FaRQ7NC4NVLY0</code></p>
<p>Option c artinya buat file baru, karena user.txt sebelumnya tidak ada, maka kita perlu pakai option c ini.
Untuk menambah user lain, cukup tambahkan ke file user.txt yang sudah ada:
<code class="language-plaintext highlighter-rouge">c:\> htpasswd -m user.txt benny
New password: ****
Re-type new password: ****
Adding password for user benny</code></p>
<p>Option m artinya password akan dienkripsi dengan algoritma MD5.
Itu sebabnya password ‘test’ untuk user endy terlihat seperti sumpah serapah dalam file user.txt</p>
<ol>
<li>Copy user.txt ke folder c:\xampp\apache\conf</li>
</ol>
<p>Kemudian, kita akan mengkonfigurasi Apache agar:</p>
<ul>
<li>
<p>meneruskan request http dengan suffix /svn (misal http://localhost/svn) ke Subversion</p>
</li>
<li>
<p>meminta requester memasukkan username dan password</p>
</li>
<li>
<p>mencocokkan username dan password dengan file c:\xampp\apache\conf\user.txt</p>
</li>
<li>
<p>kalau cocok, tampilkan repository yang ada di folder c:\repoku</p>
</li>
</ul>
<ol>
<li>
<p>Copy file c:\Program Files\Subversion\bin\mod_authz_svn.so dan mod_dav_svn.so ke folder c:\xampp\apache\modules
File ini adalah modul tambahan agar apache mampu menangani request Subversion.</p>
</li>
<li>
<p>Edit c:\xampp\apache\conf\httpd.conf, tambahkan baris berikut:
`LoadModule dav_module modules/mod_dav.so
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so</p>
</li>
</ol>
<p><Location /svn>
DAV svn
SVNPath c:/repoku
AuthType Basic
AuthName “Subversion Web Authentication”
AuthUserFile c:/xampp/apache/conf/user.txt
Require valid-user
</Location> `</p>
<ol>
<li>
<p>Pastikan repository sudah dibuat di folder c:\repoku, kalau belum buat dulu:
<code class="language-plaintext highlighter-rouge">c:\> mkdir c:\repoku
c:\> svnadmin create --fs-type fsfs c:\repoku</code></p>
</li>
<li>
<p>Silahkan browse ke http://localhost/svn/</p>
</li>
</ol>
Pengetahuan wajib buat programmer2006-03-21T01:35:47+07:00https://software.endy.muhardin.com/life/pengetahuan-wajib-buat-programmer<p>Setelah beberapa kali mewawancara calon programmer baru, saya menemukan bahwa cukup banyak dari kandidat pelamar kerja, baik <em>fresh graduate</em> maupun yang (ngakunya) <em>experienced</em> masih belum memahami beberapa pengetahuan dasar.</p>
<p>Entah apa sebabnya. Beberapa kemungkinan bisa saya perkirakan, sebagai berikut:</p>
<ul>
<li>
<p>Tidak diajarkan di kuliah</p>
</li>
<li>
<p>Diajarkan, tapi mahasiswa bersangkutan lebih banyak dugem daripada kuliah</p>
</li>
<li>
<p>Diajarkan dan <em>pernah</em> mengerti, tapi karena jarang digunakan jadi lupa</p>
</li>
</ul>
<p>Apapun masalahnya, yang jelas kenyataan ini sangat memprihatinkan. Indonesia tidak akan maju jika bibit tenaga kerjanya mudah merasa cukup.</p>
<p>Secara pribadi, saya punya standar sendiri dalam melakukan seleksi. Jadi buat yang mau melamar kerja, silahkan berlatih. Siapa tahu Anda berhadapan dengan saya di meja wawancara :D</p>
<p>Berikut menu wajib programmer:</p>
<ul>
<li>
<p>Konsep dasar sistem operasi.</p>
</li>
<li>
<p>Konsep dasar jaringan.</p>
</li>
<li>
<p>Konsep dasar relational database.</p>
</li>
<li>
<p>Karena sekarang jaman internet, maka wajib memahami protokol HTTP, FTP, POP3, SMTP, SSH.</p>
</li>
<li>
<p>Karena sekarang jaman globalisasi, maka <a href="http://joelonsoftware.com/articles/Unicode.html">wajib memahami Unicode</a>.</p>
</li>
<li>
<p>Lebih dari satu bahasa pemrograman.</p>
</li>
<li>
<p>Cara menggunakan Version Control.</p>
</li>
</ul>
<p>Berikut pertimbangannya.</p>
<p>Kebanyakan dari programmer Indonesia biasanya membuat aplikasi di atas sistem operasi, sehingga banyak yang berpendapat bahwa tidak perlu memahami cara kerja sistem operasi. Pendapat ini boleh saja, kalau Anda adalah staf akunting yang kebetulan dipaksa bos untuk membuat aplikasi <em>general ledger</em>. Untuk programmer profesional, pemahaman ini akan membuat Anda lebih siap untuk membuat aplikasi server yang biasanya <em>multithreaded</em> dan harus efisien digunakan dalam waktu yang lama.</p>
<p>Pemahaman mendalam di salah satu sistem operasi juga merupakan nilai tambah yang signifikan. Dengan mengetahui struktur internal sistem operasi (misalnya Linux), kita dapat mengetahui berbagai pertimbangan dalam merancang aplikasi besar yang terus berkembang.</p>
<p>Saat ini, kalau kita harus membuat aplikasi, besar kemungkinannya aplikasi kita tidak berjalan sendiri. Aplikasi tersebut pasti harus berhubungan dengan internet, melayani banyak pengguna, atau berhubungan dengan perangkat lain seperti handphone atau PDA. Untuk itu, pemahaman atas konsep jaringan sangat penting.</p>
<p>Tes sederhana untuk menguji pemahaman Anda. Coba jelaskan proses yang terjadi mulai dari Anda mengetik http://endy.artivisi.com di browser Anda, sampai halaman ini terbentang di depan mata Anda. Dengan mendengarkan jawaban Anda, saya akan tahu kualitas Anda. Jawaban yang saya harapkan mengandung istilah name-resolve, http-request, virtual directory, query database, HTML response, dan CSS. Kalau Anda menyebutkan (apalagi menjelaskan) tentang routing, gateway, proxy, port 80, saya akan lebih senang lagi.</p>
<p>Tentang relational database. Saya tahu ini pasti pernah diajarkan di kuliah. Jadi lulusan informatika dan sejenisnya jangan bilang belum diajarkan. Yang saya maksud bukan sekedar sintaks SQL. Sintaks itu gampang, <a href="http://www.google.co.id/search?q=sql+tutorial">bisa dicari dengan mudah di internet</a>. Yang saya inginkan adalah penjelasan tentang Boyce-Codd Normal Form, lengkap dengan contoh kasusnya, di luar kepala. Kalau sudah bisa menjelaskan ini, inner join, subquery, union, itu perkara sepele.</p>
<p>Protokol HTTP sekarang adalah protokol yang paling banyak digunakan di internet. Jangan salah, ini bukan tentang sintaks HTML atau CSS. Jadi apa? Begini, coba tampilkan halaman website ini dengan menggunakan telnet. Benar, bukan browser, tapi telnet.</p>
<p>Kalau sudah bisa browsing dengan telnet, sekarang coba untuk baca email via telnet. Menggunakan protokol POP3 atau IMAP tentunya. Punya account Gmail kan? Hare gene gak punya? Ya bagus, kalo punya coba aktifkan fitur POP3nya, setelah itu buka dengan telnet.</p>
<p>Unicode itu penting supaya aplikasi kita tetap bisa diinstal di komputer orang Jepang atau Korea, atau komputer berbahasa Sansekerta.</p>
<p>Pemahaman lebih dari satu bahasa itu penting agar wawasan kita terbuka. Bahwa tidak ada bahasa yang one-fit-all, bahwa ada cara berpikir yang berbeda dalam tiap bahasa, bahwa komunitas tiap bahasa berbeda budayanya. Semua ini akan berkontribusi dalam pendewasaan kita dalam berdiskusi dan menanggapi perbedaan (terutama pendapat).</p>
<p>Satu lagi, trend bahasa pemrograman adalah, tiap sepuluh tahun, market leader berganti. Dulu COBOL, kemudian C++, sekarang Java. Jadi, kemampuan belajar bahasa baru sangat penting. Bukan cuma bahasanya yang penting, tapi kemampuan belajarnya yang lebih penting.</p>
<p>Di tempat saya bekerja, penggunaan version control adalah wajib. Ini standar (de facto) internasional. Kalau kita punya project opensource, baik di Sourceforge, Apache, Codehaus, dan semua hosting project opensource, pasti kita akan diberikan version control. Silahkan download dan coba gunakan <a href="http://www.cvshome.org">CVS</a> atau <a href="http://subversion.tigris.org">Subversion</a>.</p>
<p>Ok, itu standar minimal saya. Menurut Anda terlalu sulit? Hmm .. kalau begitu <strike>dunia IT</strike> menjadi programmer nampaknya kurang cocok buat Anda. Silahkan coba karir lainnya, misalnya notaris atau sopir busway :D</p>
RTFM !!!2006-03-17T22:44:45+07:00https://software.endy.muhardin.com/life/rtfm<p><strong>Disclaimer</strong>: Saya menulis artikel ini bukan karena sudah tidak mau lagi menjawab pertanyaan. Bertanya boleh, sepanjang saya ada waktu dan bisa membantu insya Allah akan saya jawab. Tapi intinya adalah, bagaimana teknik bertanya yang dapat menghasilkan nilai tambah yang besar, bukan hanya untuk penanya, tapi juga untuk penjawab. Mengingat saya tidak dibayar untuk menjawab pertanyaan Anda, melainkan sebagai sedekah dan niat baik saja. Di lain pihak, dalam menjawab pertanyaan Anda, ada waktu yang saya gunakan. Waktu ini entah merupakan hak pelanggan saya (yang membayar untuk jasa saya), atau hak keluarga di rumah. <em>Jadi untuk kebaikan kita semua, mohon baca artikel berikut sampai tuntas</em>.</p>
<p>RTFM adalah singkatan dari Read The F!@#*ing Manuals, atau Read The Fine Manual, tergantung mood Anda saat mengucapkannya.</p>
<p>Kalimat sederhana, tapi entah kenapa banyak orang yang tidak melakukannya. Untuk kesekian kalinya, saya mendapat pertanyaan, “Bagaimana cara pakai ini? Bagaimana menjalankan aplikasi itu? Mengapa framework ini tidak berjalan seperti harapan saya?” dan pertanyaan lain yang sejenis.</p>
<p>Bukan, ini bukan tentang <a href="http://playbilling.sourceforge.net">playbilling</a>. Playbilling memang parah dokumentasinya, jadi bukan salah Anda kalo mengalami kesulitan :D</p>
<p>Yang sedang saya bicarakan adalah teknologi yang sehari-hari digunakan programmer. Misalnya PHP, Java, Hibernate, Spring, Freemarker, Linux, dan lainnya. Itu semua adalah produk mature, yang siap pakai, sudah banyak komunitasnya. Dengan demikian, manual dan dokumentasi pasti tersedia di mana-mana.</p>
<p>Sering sekali terjadi, ada yang tanya ke saya, baik melalui Y!, SMS, telpon, dan sebagainya. Dulu, saya masih mau menjawab panjang lebar. Akhir-akhir ini, pertanyaan semakin banyak, jadi respon standar saya adalah balik bertanya. Berikut urutan pertanyaan saya:</p>
<ol>
<li>
<p>Sudah download?</p>
</li>
<li>
<p>Sudah extract?</p>
</li>
<li>
<p>Sudah baca dokumentasinya?</p>
</li>
<li>
<p>Sudah coba jalankan contohnya?</p>
</li>
<li>
<p>Sudah lihat dokumentasi/FAQ/forum di websitenya?</p>
</li>
<li>
<p>Sudah cari di Google?</p>
</li>
</ol>
<p>Misal, ada yang tanya, “Bagaimana cara membuat mapping one-to-many di Hibernate?” Maka respon saya adalah:</p>
<ol>
<li>
<p>Sudah download Hibernate?</p>
</li>
<li>
<p>Sudah extract hibernate-xx.zip?</p>
</li>
<li>
<p>Sudah baca dokumentasinya?</p>
</li>
<li>
<p>Sudah coba jalankan contohnya?</p>
</li>
<li>
<p>Sudah lihat dokumentasi/FAQ/forum di <a href="http://www.hibernate.org">websitenya</a>?</p>
</li>
<li>
<p>Sudah cari di Google dengan keyword “hibernate one to many mapping”?</p>
</li>
</ol>
<p>Sayang sekali, sampai saat ini belum ada yang sampai nomer 6. Biasanya para penanya sudah “menyerah” di nomer 3. Pada beberapa kasus menyedihkan, nomer 1 saja dijawab “Belum”.</p>
<p>Untuk mereka yang menyerah di nomer 1, Anda hanya membuang waktu dan bandwidth saya yang berharga. Mereka ini adalah tipikal orang yang ingin gampang dan tidak memikirkan kepentingan orang lain. Sama saja dengan menganggap saya sebagai petugas 108 yang siap menjawab segala pertanyaan, karena memang dibayar untuk itu.</p>
<p>Bagi mereka yang menyerah di nomer 3, saya masih sedikit berempati. Mungkin penguasaan bahasa Inggrisnya kurang. Atau mungkin keder melihat dokumen berpuluh-puluh halaman, dalam bahasa Inggris pula. Walaupun mungkin juga ada yang bermentalitas cari gampang seperti nomer 1 di atas.</p>
<p>Tapi saran saya adalah, bacalah dokumentasi. Kalau bahasa Inggris yang kurang, kursus. Kalau ngeri melihat dokumen yang banyak, jangan dibaca semua. Gunakan Ctrl+F alias fasilitas find.</p>
<p>Jika Anda ingin serius dalam karir duniawi, investasi Bahasa Inggris sangat penting. Kemampuan membaca tulisan Inggris akan memungkinkan Anda maju secara mandiri. Kalau tidak, kemajuan Anda akan tergantung kepada orang-orang baik hati yang mau membantu Anda. Orang seperti ini tidak selalu ada. Mereka sangat mungkin memiliki kesibukan lain, sehingga kemajuan Anda akan menunggu ‘bala bantuan’ tersebut punya waktu luang untuk membantu Anda. Konsekuensinya jelas, Anda hanya akan jadi orang tertinggal.</p>
<p>Dalam proses membaca dan memahami dokumentasi, pengetahuan kita akan meningkat. Bukan saja kita mendapat jawaban atas masalah kita, tapi kita mendapatkan pengalaman memahami dokumentasi teknis. Ini adalah skill yang sangat bermanfaat, dan telah terbukti berguna pada waktu saya mengalami kesulitan.</p>
<p>Ada satu skill lagi yang sangat bermanfaat, yaitu skill problem solving. Anda mendapat masalah, kemudian melakukan langkah-langkah pemecahan. Kalau tidak berhasil, mungkin ada yang perlu diperbaiki di sistematika pemecahan masalahnya. Kemampuan problem solving sangat penting bagi karir Anda. Anda akan dianggap orang yang ‘<em>bisa bekerja secara mandiri</em>’ atau ‘<em>mampu bekerja dengan supervisi minimal</em>’. Sering lihat kan kriteria seperti ini di lowongan pekerjaan?</p>
<p>Problem solving skill bukan teknik instan. Ini harus dilatih terus menerus sepanjang hidup. Semakin banyak masalah yang kita hadapi dan kita coba pecahkan, kemampuan kita akan meningkat, berhasil itu cuma masalah waktu saja.</p>
<p>Bagi mereka yang sudah menjawab Ya sampai nomer 5, saya ucapkan selamat untuk Anda. Anda sudah membuktikan bahwa Anda telah mengerjakan PR Anda sebelum akhirnya memutuskan untuk meminta waktu saya. Dan kalau ternyata memang belum menyelesaikan masalah, saya tidak keberatan untuk membantu Anda.</p>
<p>Untuk kaum nomer 5, biasanya prosedur standar saya adalah menggunakan Google untuk mencari jawaban. Kemungkinan pertama, search langsung menghasilkan jawaban. Pada kasus ini, saya akan memberikan keyword google yang saya gunakan dan menunjukkan pada entri keberapa jawaban ditemukan. Harapan saya adalah, pada kesempatan berikutnya si penanya akan tahu metode dan teknik memilih keyword supaya hasil Google-nya akurat. Dengan demikian, si penanya akan bisa lebih mandiri.</p>
<p>Kemungkinan yang lain, saya harus bersusah payah mencari sebelum berhasil menemukan jawaban. Biasanya alirannya begini: Google -> Website -> dapat keyword lain -> Google lagi, dan seterusnya sampai ketemu. Kalau begini kejadiannya, saya akan menjelaskan proses berpikir saya dalam menelusuri internet kepada si penanya. Lagi-lagi tujuannya adalah supaya si penanya bisa mencoba sendiri di kemudian hari.</p>
<p>Buat mereka yang sudah lulus sampai nomer 6, tapi masih belum ketemu jawaban, lalu tanya saya … hmm … sejauh ini belum ada :D Kalau sudah lihai pakai Google dan gak ketemu, kecil sekali kemungkinan saya bakal ditanya. Kalaupun ditanya, jawaban saya cepat. Kalau saya sudah pernah mengalami masalah yang sama, saya akan jawab. Kalo belum pernah, ya saya bilang belum pernah, sehingga saya sama tidak tahunya dengan si penanya.</p>
<p>Pertanyaan yang saya benci adalah pertanyaan debug. Misalnya begini, “Saya bikin kode seperti ini , kemudian muncul NullPointerException, kenapa ya?”</p>
<p>Saya tidak suka karena:</p>
<ol>
<li>
<p>Baca source code di Y! adalah penyiksaan, lewat SMS itu mungkin sudah melanggar HAM :P . Jangan ketawa, ada yang pernah kirim source code via SMS ke saya.</p>
</li>
<li>
<p>Untuk bisa debug, paling efektif adalah dengan coding langsung, pasang println disana-sini, recompile, re-run, lihat hasilnya.</p>
</li>
</ol>
<p>So, jangan ajukan pertanyaan debug ke saya. Coba kirim ke milis. Mudah-mudahan ada yang mau bantu. Kalo saya sih tidak … lihat source code yang ditulis sendiri aja pusing, apalagi source code orang, lewat SMS pula …</p>
<p>Kalau ada pertanyaan yang dibenci, tentunya ada pertanyaan yang disukai. Pertanyaan yang saya suka adalah yang konseptual. Misalnya, “Apa itu anonymous inner class, dan kapan kita menggunakannya”. Contoh lain, “Teknik mapping inheritance mana yang paling baik untuk kasus saya, one-table-for-all, table-per-subclass, atau table-per-class”?</p>
<p>Pertanyaan seperti ini mencerdaskan kedua belah pihak. Si penanya mendapat wawasan baru, sedangkan saya mendapat kesempatan untuk menguji apakah pemahaman saya sudah lengkap dan benar.</p>
<p>Jadi, mari bertanya secara cerdas demi kebaikan kita semua.</p>
Intro Manajemen Proyek2006-03-17T00:00:22+07:00https://software.endy.muhardin.com/manajemen/intro-manajemen-proyek<p>Apabila kita ditunjuk menjadi project manager, ada beberapa hal yang harus kita kelola, yaitu:</p>
<ul>
<li>
<p>Waktu</p>
</li>
<li>
<p>Uang</p>
</li>
<li>
<p>Orang</p>
</li>
<li>
<p>Teknologi</p>
</li>
</ul>
<p>Semuanya adalah variabel yang sangat dinamis. Masing-masing seringkali berubah seenaknya sendiri.</p>
<p>Waktu tidak dapat diprediksi, manajemen seringkali memajukan deadline secara sepihak. Demikian juga dengan uang, banyak pos pengeluaran yang sulit diprediksi dan dapat membengkak tiba-tiba.</p>
<p>Mengelola orang tidak kalah sulitnya. Resign, sakit, cuti mendadak, adalah hal yang biasa bagi project manager yang berpengalaman (baik pengalaman sukses maupun gagal :P). Teknologi baru memiliki faktor ketidakpastian yang tinggi. Terlihat mudah di tutorial, tapi ternyata sulit sekali untuk menangani masalah yang kompleks.</p>
<p>Semua faktor di atas dapat disimpulkan dengan satu kata: <strong>RESIKO</strong>. Pekerjaan project manager sebetulnya adalah mengelola resiko.</p>
<p>Dalam mengelola resiko, cuma ada dua teknik: mitigasi dan kontingensi. Mitigasi adalah tindakan pencegahan, yaitu tindakan yang kita lakukan agar resiko tidak terjadi. Sedangkan kontingensi adalah tindakan pengobatan, yaitu tindakan yang kita lakukan ketika resiko sudah benar-benar menjadi perkara.</p>
<p>Dengan bermodalkan akal sehat dan sedikit inisiatif, kita sebetulnya bisa mengidentifikasi resiko proyek. Sebut saja identifikasi resiko ini sebagai PR buat project manager sebelum project mulai dijalankan. Beberapa resiko yang umum terjadi dapat diantisipasi di awal sehingga tidak menjadi perkara di kemudian hari.</p>
<p>Terdengar sederhana? Memang benar. Saking sederhananya, sering diabaikan orang.</p>
<p>Pada artikel selanjutnya, saya akan berikan contoh resiko yang pasti dihadapi dalam project. Tentu saja lengkap beserta teknik mitigasi dan kontingensinya.</p>
Kategori Baru: Manajemen2006-03-16T23:40:57+07:00https://software.endy.muhardin.com/manajemen/kategori-baru-manajemen<p>Sudah beberapa bulan ini saya tidak coding seintensif biasanya. Ini berkaitan dengan project baru saya. Sekarang saya ada di divisi Business Process Improvement, yang intinya adalah membuat proses pengembangan software menjadi lebih baik.</p>
<p>Konsekuensinya, wawasan saya beralih dari perkembangan framework terbaru di Java menjadi teknik manajemen proyek yang baik.</p>
<p>Untuk merangkum pengetahuan yang saya dapat di dunia baru ini, saya buatkan kategori manajemen. Di sini nantinya akan ditulis berbagai pengalaman, best practices, jebakan maut, maupun tips dan trik. Tentunya sejauh tidak melanggar perjanjian kerahasiaan dengan client.</p>
<p>Nantikan saja tulisannya. :D</p>
Bacaan Wajib2006-03-10T00:53:32+07:00https://software.endy.muhardin.com/java/bacaan-wajib<p>Sepanjang karir saya sebagai programmer, ada beberapa bacaan yang kontribusinya sangat signifikan terhadap perkembangan diri saya.</p>
<p>Selain yang spesifik untuk Java, tapi ada juga yang bersifat umum, dapat diterapkan di bahasa apapun.</p>
<p>Berikut daftarnya. Daftar ini dapat diupdate sewaktu-waktu. Jadi silahkan sering-sering membuka halaman ini.</p>
<p><strong>Pemrograman Umum</strong></p>
<ol>
<li>
<p><a href="http://www.faqs.org/docs/artu/">The Art of Unix Programming</a> - Eric Raymond</p>
</li>
<li>
<p><a href="http://www.pragmaticprogrammer.com">Pragmatic Programmer: From Journeyman to Master</a> - Andy Hunt & Dave Thomas</p>
</li>
<li>
<p><a href="http://martinfowler.com/books.html#refactoring">Refactoring</a> - Martin Fowler</p>
</li>
<li>
<p>The Mythical Man Month - Fred Brooks</p>
</li>
<li>
<p>Peopleware - Tom DeMarco & Timothy Lister</p>
</li>
<li>
<p><a href="http://www.joelonsoftware.com">Joel on Software</a></p>
</li>
<li>
<p>Design Patterns: Elements of Reusable Object-Oriented Software - GoF</p>
</li>
</ol>
<p><strong>Java</strong></p>
<ol>
<li>
<p>Effective Java - Joshua Bloch</p>
</li>
<li>
<p>Hibernate in Action - Christian Bauer & Gavin King</p>
</li>
<li>
<p>J2EE Design and Development - Rod Johnson</p>
</li>
</ol>
<p>Penerbit dan cara membeli/mendownload dapat diperoleh melalui <a href="http://www.google.com">Google</a>. Semoga bermanfaat bagi para pembaca.</p>
File Manager2006-03-07T22:00:10+07:00https://software.endy.muhardin.com/aplikasi/file-manager<p>Kadangkala, kita tidak terlalu beruntung, mendapatkan jatah akses internet yang serba terbelenggu. Cuma bisa browsing, tidak bisa ssh, tidak bisa chat, ataupun tidak bisa ftp.</p>
<p>Pada saat seperti itu, kita dapat menggunakan <a href="http://www.meebo.com">aplikasi chatting berbasis web</a>, atau menginstal aplikasi file manager untuk mengelola file di server hosting kita.</p>
<p>Untuk file manager, saya menggunakan <a href="http://www.solitude.dk/filethingie/">File Thingie</a>, aplikasi PHP yang cuma terdiri dari satu file. Cukup kirim ke server, dan browse melalui browser.</p>
<p><a href="/images/uploads/2006/03/filethingie.png"><img src="/images/uploads/2006/03/filethingie.png" alt="File Thingie User Interface " /></a></p>
<p>Sederhana dan mudah.</p>
Blog Client2006-03-03T02:57:40+07:00https://software.endy.muhardin.com/aplikasi/blog-client<p>Sekarang ini banyak aplikasi internet yang juga menyediakan desktop client. Contoh paling klasik adalah e-mail. Server email komersial biasanya menyediakan akses web based. Tetapi selain melalui browser, email juga bisa didownload dan dibaca secara offline menggunakan desktop mail client, seperti KMail, Mozilla Thunderbird, atau Microsoft Outlook.</p>
<p>Tidak hanya email, blog juga sekarang punya desktop client. Aplikasi blog yang saya gunakan adalah Wordpress, termasuk salah satu yang paling populer. Jadi, banyak aplikasi client yang sudah mendukung Wordpress.</p>
<p>Salah satunya, yang saya gunakan untuk menulis artikel ini adalah <a href="http://www.performancing.com">Performancing</a>. Performancing ini diinstal sebagai plugin di browser Mozilla Firefox. Instalasinya cukup mudah, cukup download langsung di <a href="https://addons.mozilla.org/extensions/moreinfo.php?id=1730&application=firefox">situs Mozilla</a>.</p>
<p>Setelah diinstal, di pojok kanan bawah browser akan muncul icon kecil.</p>
<p><a href="/images/uploads/2006/03/mozilla.png"><img src="/images/uploads/2006/03/mozilla.png" alt="Mozilla with Performancing " /></a>
Klik icon tersebut, dan tampilan aplikasi segera keluar, mengambil tempat separuh layar browser.</p>
<p><a href="/images/uploads/2006/03/performancing.png"><img src="/images/uploads/2006/03/performancing.png" alt="Performancing User Interface " /></a>
Kita dapat meregistrasi beberapa blog sekaligus di panel sebelah kanan. Berikut adalah langkah-langkahnya:</p>
<ol>
<li>Pilih tipe blog</li>
</ol>
<p><a href="/images/uploads/2006/03/01.%20Select.png"><img src="/images/uploads/2006/03/01.%20Select.png" alt="Select Blog type " /></a></p>
<ol>
<li>Masukkan informasi tentang blog kita</li>
</ol>
<p><a href="/images/uploads/2006/03/02.%20URL.png"><img src="/images/uploads/2006/03/02.%20URL.png" alt="Blog URL " /></a></p>
<ol>
<li>Isi username dan password yang biasa digunakan untuk posting artikel</li>
</ol>
<p><a href="/images/uploads/2006/03/03.%20Username.png"><img src="/images/uploads/2006/03/03.%20Username.png" alt="Enter username and password " /></a></p>
<ol>
<li>Selesai</li>
</ol>
<p><a href="/images/uploads/2006/03/04.%20Finish.png"><img src="/images/uploads/2006/03/04.%20Finish.png" alt="Finished !!! " /></a></p>
<p>Setelah blog didaftarkan, kita bisa langsung menulis post di kotak yang telah disediakan. Kategori artikel juga dapat dipilih langsung di tab <strong>Categs</strong> di panel sebelah kanan.</p>
<p>Tulis artikel Anda, pilih kategori yang sesuai, lalu … klik Publish.
Kalau Anda bisa membaca artikel ini, berarti Performancing telah berhasil menunaikan tugasnya dengan benar.</p>
<p>Keren … :D</p>
Upgrade dan Migrasi2006-03-01T23:25:57+07:00https://software.endy.muhardin.com/life/upgrade-dan-migrasi<p>Sebetulnya sudah lama saya mau memindahkan hosting blog ini. Alasan pertama, blog ini menggunakan komputer kantor yang sewaktu-waktu bisa diminta yang punya. Kedua, kehadirannya di internet sangat ditentukan oleh sampai kapan saya mendapatkan IP Public. Kalau sewaktu-waktu pindah kantor, blog ini bisa terancam offline.</p>
<p>Jadi, mumpung masih ada akses internet bebas, bisa ftp sesuka hati, dan masih tidak diburu waktu, saya pindahkan **.artivisi.com sedikit demi sedikit ke <a href="http://www.icthosting.com">tempat yang baru</a>. <a href="http://tutorial.artivisi.com">tutorial.artivisi.com</a> sudah pindah duluan. Karena dia tidak butuh database, migrasinya lebih mudah, tinggal upload dan edit config sedikit, beres.</p>
<p>Untuk blog ini, sekalian pindah saya mau upgrade ke versi 2.0.1 yang terbaru. Untungnya di website Wordpress sudah tersedia dokumentasinya. Berikut langkah-langkah yang saya lakukan:</p>
<p>**1. Backup dan Replikasi. **</p>
<p>Duplikasi folder blog berikut isinya dan juga databasenya. Intinya adalah, saya ingin ada dua blog kembar yang berjalan bersamaan. Dengan demikian, tidak hanya backup yang dilakukan, tapi juga memastikan hasil backup bekerja dengan baik.</p>
<p><strong>2. Lakukan upgrade</strong></p>
<p>Upgrade saya lakukan di hasil <strong>duplikasi</strong>, bukan di blog asli. Jadi kalo terjadi sesuatu yang salah, blog asli tidak terpengaruh.</p>
<p>Langkah upgrade cukup mudah: matikan semua plugin, hapus semua file kecuali** wp-config.php**, ganti dengan file baru, browse ke halaman upgrade. Dengan satu kali tekan tombol, upgrade berjalan dengan sangat mulus. Luar biasa.</p>
<p><strong>3. Upload ke server baru</strong></p>
<p>Blog baru yang sudah versi 2.0.1 diupload ke tempat hosting baru dengan menggunakan FTP. Setelah itu, database dibuat dan diisi dengan menggunakan dump dari database di server lama (versi setelah upgrade). Sesuaikan file wp-config.php bila perlu (nama database, username, password biasanya berbeda).
<strong>4. Verifikasi</strong></p>
<p>Terakhir, kontak teman-teman anda untuk mencoba mengunjungi blog yang baru. Mudah-mudahan tidak ada masalah. :D</p>
<p>Selamat mencoba</p>
VMWare Gratis !!!2006-02-22T20:35:37+07:00https://software.endy.muhardin.com/aplikasi/vmware-gratis<p>Tahu <a href="http://www.vmware.com">VMWare</a>? Itu adalah aplikasi yang dapat membuat komputer baru di dalam komputer kita.</p>
<p>Jadi nanti, kalo diinstal, kita bisa bikin komputer palsu (virtual machine). Komputer palsu ini dapat dinyalakan dan dimatikan. Kalo dinyalakan, tampilannya benar-benar seperti komputer menyala, yaitu ada memory check, bisa masuk BIOS, bahkan bisa boot via jaringan segala.</p>
<p>Setelah dibuat, komputer palsu tersebut bisa kita instal sistem operasi apa saja. Jumlah komputer palsu yang dibuat juga bisa lebih dari satu. Setiap komputer palsu bisa kita kasi jatah memori dan harddisk sesuai keinginan. Jadi bisa saja terjadi, dalam satu komputer kita menjalankan tiga distro linux (Ubuntu, Debian, Fedora) dan dua versi windows (XP dan 2000) secara berbarengan.</p>
<p>Aplikasi VMWare ini sangat penting apabila kita mengembangkan aplikasi yang harus bisa bekerja dengan baik di berbagai sistem operasi dan konfigurasi.
Jadi kita tidak perlu punya banyak komputer untuk bisa mengetes aplikasi secara simultan.</p>
<p>Beberapa minggu yang lalu, VMWare menggratiskan <a href="http://www.vmware.com/news/releases/server_beta.html">aplikasi servernya</a>. Wah … langsung saya buru-buru <a href="http://www.vmware.com/download/server/">download</a> dan instal.</p>
<p>Instalasi di Linux (Debian Sarge) membutuhkan kernel header. Jadi, lakukan ini dulu:</p>
<p><code class="language-plaintext highlighter-rouge">apt-get install kernel-headers-2.6-686-smp</code></p>
<p>Baru kemudian instal VMWare.</p>
<p>Instalasi VMWare gampang, tinggal login sebagai root, jalankan skrip instalasi, dan next beberapa kali. Kita akan diminta memasukkan serial number. Serial number ini akan dikirim ke alamat email kita pada saat kita registrasi di websitenya. Untuk bisa mendownload kita harus teregistrasi terlebih dahulu.</p>
<p>Setelah VMWare terinstal, kita dapat mulai membuat komputer palsu sebanyak mungkin.
Sekarang saya bisa menggunakan Visio dan M$ Project di komputer Linux saya.
:D</p>
Turut Berduka Cita2006-02-01T18:53:59+07:00https://software.endy.muhardin.com/life/turut-berduka-cita<p>Dengan ini saya mengucapkan turut berbelasungkawa atas <a href="http://prayudi.blogs.friendster.com/iip_blog/2006/01/titik_nol.html">dilay-off</a> nya <a href="http://prayudi.web.id">rekan seperjuangan dari jaman kost di Adhyaksa</a> dulu.</p>
<p>Lay-off –atau bahasa Indonesianya Pemutusan Hubungan Kerja (PHK)– walaupun pahit, merupakan hal yang sangat wajar di dunia kerja. Saking wajarnya, sekarang sudah ada <a href="http://www.nbc.com/nbc/The_Apprentice/">reality show</a> yang mengeksploitasi momen-momen emosional pada waktu orang dipecat. Acara ini juga sudah dibuat <a href="http://www.indosiar.com/md/apprentice2.htm">versi Indonesianya</a>.</p>
<p>Meskipun wajar dan lumrah, ada beberapa hal yang saya sesalkan atas kejadian ini. Pertama, adalah tenggang waktunya yang begitu singkat. Kedua, masalah pesangon yang tidak jelas.</p>
<p>Kalau kita mengundurkan diri dari perusahaan (berhenti atas kemauan sendiri), umumnya kita diminta untuk menyampaikan pemberitahuan terlebih dahulu. Tenggang waktu antara pemberitahuan dan hari terakhir bekerja bervariasi. Ada perusahaan yang minta waktu dua minggu –seperti yang <a href="http://www.imdb.com/title/tt0313737/">dialami Sandra Bullock</a>–, dan ada juga yang sebulan.</p>
<p>Itu yang terjadi dari sisi perusahaan, apabila kita yang memecat perusahaan (alias mengundurkan diri). Tapi bagaimana jika sebaliknya?</p>
<p>Jika perusahaan butuh waktu untuk menggantikan karyawan yang resign, karyawan yang dipecat juga idealnya diberikan tenggang waktu yang wajar untuk berusaha menggantikan periuk nasinya. Tiga hari sangat tidak cukup untuk browsing dan memasukkan lamaran di <a href="http://www.jobsdb.com">website lowongan kerja</a>. Apalagi kalau memperhitungkan faktor apes seperti misalnya <a href="http://prayudi.blogs.friendster.com/iip_blog/2006/01/officially_jobl.html">insiden mati lampu</a>.</p>
<p>Bicara soal pesangon, saya jadi ingat pekerjaan pertama saya setelah lulus kuliah, mengajar aplikasi Office di sebuah politeknik D-1 di Surabaya. Walaupun gaji saya (sudah termasuk stock option) besarnya tidak lebih dari Rp. 300.000 (pada tahun 2001), tapi waktu mengundurkan diri (bukan dipecat) dengan masa kerja 3 bulan, saya mendapat pesangon SEPASANG SANDAL KULIT no less.</p>
<p>Jadi, sepertinya tidak berlebihan kalau saya berpendapat bahwa tidak memberikan pesangon, apalagi sampai <a href="http://prayudi.blogs.friendster.com/iip_blog/2006/01/officially_jobl.html">mengutang gaji terakhir</a>, adalah perilaku yang bukan saja tidak fair, tapi juga tidak sopan.</p>
<p>Tetap tabah pak, semoga pekerjaan berikutnya lebih baik.</p>
OpenOffice 2.02006-02-01T17:23:20+07:00https://software.endy.muhardin.com/aplikasi/openoffice-20<p>Tadi malam, saya baru saja mencoba OpenOffice 2.0. Ya ya ya .. saya tau .. telat banget ya. Ya mau bagaimana lagi, saya sudah merasa cukup dengan OpenOffice 1.1.3 yang terinstal menggunakan apt-get bawaan Debian Sarge. Saya tidak gunakan fitur-fitur canggih, dan semua berjalan dengan lancar.</p>
<p>Di rumah, istri saya sudah bolak-balik menyuruh saya untuk menghapus Windows permanently (tidak untuk diinstal lagi). Windows di rumah hanya digunakan untuk bermain game saja. Untuk kegiatan produktif saya menggunakan Linux. Istri saya juga menggunakan Linux untuk membuat laporan keuangan keluarga, membuka email, mengikuti mailing list, dan kadang-kadang chatting.</p>
<p>Masalahnya, adik saya masing sering bermain game di Windows. Jadi masih ada alasan untuk mempertahankan Windows. Ketika akhirnya si Windows ini terjangkiti Rontokbro, hilang sudah alasan saya untuk mempertahankannya.</p>
<p>Akhirnya saya instal Ubuntu terbaru ke partisi Windowsnya. Sorry Bill and Steve. Nothing personal, it’s all business.</p>
<p>Di Ubuntu, OpenOffice yang terinstal adalah versi 2.0. Sekilas tidak ada perbedaan signifikan selain beberapa perbedaan letak tombol dan toolbar. Saya coba untuk mengerjakan buku saya “Menggunakan Subversion” dengan OpenOffice 2.0.</p>
<p>Layout sudah selesai. Tinggal mencoba hasil printnya. Karena di rumah tidak ada printer, saya export ke PDF untuk diprint di kantor atau di rumah tetangga. Di sinilah baru terlihat kecanggihan OpenOffice 2.0.</p>
<p>PDF yang dihasilkan lengkap dengan Table Of Content. Lihat screenshot, sebelah kiri. Sudah ada Table of Contents yang bisa diklik. Output seperti ini belum bisa dihasilkan oleh OpenOffice 1.1.3.
<a href="/images/outputoo2.png"><img src="/images/outputoo2.png" alt="Screenshot Acrobat Reader with Bookmarks " /></a></p>
<p>Hmm… saya jadi ingin segera upgrade ke 2.0. Ada yang tau caranya instal 2.0 untuk Sarge?</p>
Aggregator on Windows2006-01-26T20:29:46+07:00https://software.endy.muhardin.com/aplikasi/aggregator-on-windows-2<p>Beberapa hari yang lalu saya bertanya kepada <a href="http://benny.telematicsindonesia.com/blog">Benny</a>, “Ben, kok RSS di blog loe gak dinyalain”. Jawabannya cukup ironis, mengingat dia adalah Very Senior, One and the Only, The Undisputed, Champion of the World, Lead Programmer di salah satu vendor e-learning terkemuka di Indonesia. Dia bilang, “Emang RSS itu apa, dan kenapa penting?”.</p>
<p>Alhasil saya jawab, “Ya penting, supaya pembaca (dalam hal ini saya), tidak perlu bolak balik ke blogmu setiap hari cuma untuk ngecek ada artikel baru atau ngga. Dengan menggunakan RSS Reader/Aggregator, kita bisa mendaftarkan RSS orang lain. Nanti dia akan periksa secara periodik ke semua website yang didaftarkan. Setiap ada artikel baru nanti kita dapat ringkasannya. Dengan cara ini, kita bisa ikuti bukan saja blog, tapi juga situs website berita yang penting. Misalnya <a href="http://www.theserverside.com">The Server Side</a>, yang membahas trend terbaru di dunia Java.”</p>
<p>Di Linux, saya sendiri biasanya menggunakan <a href="http://akregator.sourceforge.net/">Akregator</a>. Walaupun di komputer saya masih beta, tapi berfungsi dengan sangat baik dan handal. Dia juga bisa duduk manis docking di tray, di sebelah teman setianya KMail. Jadi tidak bikin penuh taskbar (tapi bikin penuh tray umpel-umpelan sama Kaffeine MP3 Player, Gaim, KGet, dan aplikasi background lain, sama aja boong :P).</p>
<p>Karena merasa tidak bertanggung jawab kasi informasi sepotong-sepotong, saya lalu Google untuk mencari padanan Akregator di Windows. Berikut hasil temuan saya:</p>
<ol>
<li>
<p>Ampheta</p>
</li>
<li>
<p>Feed Explorer</p>
</li>
<li>
<p>Feed Reader</p>
</li>
<li>
<p>Great News</p>
</li>
<li>
<p>Omea Reader</p>
</li>
<li>
<p>Fuzzy Duck</p>
</li>
</ol>
<p>dan masih banyak lagi tercantum di <a href="http://en.wikipedia.org/wiki/List_of_news_aggregators">Wikipedia</a>.</p>
<p>Dari yang saya list di atas, saya coba instal semuanya. Yang berhasil cuma dua, Feed Reader dan Great News. Sisanya gagal instal (Segmentation Fault) atau minta .Net diinstal dulu. Untuk catatan, saya pakai Windows 2000 Professional Service Pack terbaru dan tidak pernah ketinggalan Automatic Update.</p>
<p>Feed Reader sangat menyebalkan. Sulit digunakan. Great News lebih baik. Nyaman digunakan dan bisa masuk ke tray. Kelihatannya juga tidak terlalu berat.</p>
<p>Sebetulnya, kalau Anda menggunakan Mozilla Thunderbird, tidak perlu bingung cari aggregator, karena Thunderbird sudah mampu berlangganan RSS feed.</p>
<p>Kira-kira demikian tentang aggregator. Silakan mencoba. Semoga bermanfaat.</p>
Windows tak sulit lagi2006-01-25T19:59:25+07:00https://software.endy.muhardin.com/aplikasi/windows-tak-sulit-lagi<p>Akhirnya, setelah dua hari <a href="http://endy.artivisi.com/blog/aplikasi/windows-semakin-sulit/">bergulat dalam kompetisi Windows Wrestling Championship (WWC)</a>, saya menemukan titik terang.</p>
<p>Solusi akhirnya adalah:</p>
<ol>
<li>Gunakan <a href="http://www.apachefriends.org/en/xampp-windows.html">XAMPP</a> satu versi lebih rendah, yaitu 1.4.16. Versi ini dapat menjalankan Bugzilla dan Subversion tanpa masalah.</li>
<li>Mailserver bawaan XAMPP, yaitu Mercury, sangat sulit digunakan. Jadi -sesuai saran <a href="http://benny.telematicsindonesia.com">rekan saya</a>-, saya menggunakan <a href="http://www.hmailserver.com">hMailserver</a>. aplikasi mail server yang opensource dan sangat mudah digunakan. Membuat virtual domain dan user account sangat mudah. Cocok sekali kalau kita butuh mail server percobaan untuk coding.</li>
</ol>
<p>Jadi, dengan solusi ini saya dapat melakukan training dengan minimal instalasi di komputer peserta. Satu-satunya aplikasi yang harus diinstal adalah ActivePerl. Untungnya aplikasi ini mudah diuninstall sehingga tidak mengotori sistem.</p>
<p>Terima kasih untuk <a href="http://benny.telematicsindonesia.com">Benny</a> atas saran dan masukannya.</p>
Windows semakin sulit2006-01-24T23:36:45+07:00https://software.endy.muhardin.com/aplikasi/windows-semakin-sulit<p>Masih ingat cerita kemarin tentang <a href="http://endy.artivisi.com/blog/aplikasi/sulitnya-menggunakan-windows/">sulitnya menggunakan Windows</a>? Ternyata kesulitan itu belum berakhir.</p>
<p>Sedikit cerita latar belakang, saya ingin mengadakan training Bugzilla dan Subversion. Yang mana Bugzilla adalah aplikasi bug tracker berbasis web yang dibuat menggunakan Perl. Sedangkan Subversion adalah aplikasi version control yang salah satu metode aksesnya bisa melalui web.</p>
<p>Berikut dependensi Bugzilla:</p>
<ol>
<li>Perl, berikut modul-modul khusus untuk Bugzilla</li>
<li>Webserver (Apache)</li>
<li>Database server (MySQL)</li>
</ol>
<p>Dan dependensi Subversion + Akses HTTP adalah:</p>
<ol>
<li>Webserver (harus versi 2.0, 1.3 tidak bisa, 2.2 harus kompile sendiri)</li>
<li>mod_dav</li>
</ol>
<p>Nah sekarang, Perl sudah berhasil diinstal menggunakan <a href="http://aspn.activestate.com/ASPN/Downloads/ActivePerl/">ActivePerl</a>, MySQL dan Apache juga sudah bisa diinstal dengan <a href="http://www.apachefriends.org/en/xampp-windows.html">XAMPP</a>.</p>
<p>Masalahnya adalah, ternyata XAMPP itu menggunakan Apache 2.2. Dengan demikian, Bugzilla bisa jalan, Subversion tidak bisa.</p>
<p>Kemudian saya coba pakai <a href="http://www.uniformserver.com">Uniform server</a>, yang menggunakan Apache 2.0. Setelah dicoba, entah kenapa Subversion tetap tidak bisa diakses, walaupun versi Apachenya benar. Tidak ada error message ataupun petunjuk mengapa Uniform server mogok jalan (setelah ditambah konfigurasi Subversion).</p>
<p>Ada lagi aplikasi paket yang lain, yaitu <a href="http://www.server2go-web.de">Server2go</a>. Tetapi setelah diperiksa ternyata Apachenya 1.3. Gagal maning…. :(</p>
<p>Alternatif terakhir: fully manual.
Dijamin akan membuat peserta training histeris, karena harus:</p>
<ol>
<li>Instal Perl</li>
<li>Instal Apache</li>
<li>Instal MySQL</li>
<li>Instal PHP (agar bisa manage MySQL pakai PHPMyAdmin)</li>
<li>Instal Subversion</li>
<li>Konfigurasi mod_dav</li>
<li>Instal Bugzilla</li>
<li>Instal Mail Server (supaya Bugzilla bisa kirim email)</li>
<li>Setup file hosts atau Instal DNS (supaya mail servernya gak cuma localhost)</li>
</ol>
<p>Yah, kegiatan di atas akan memakan waktu 2 hari untuk dijelaskan ke peserta training. Belum lagi ada error. Dan itu baru kegiatan instalasi, belum masuk ke materi. Padahal alokasi waktu untuk training cuma 1 hari.</p>
<p>Sekarang saya lagi mau coba <a href="http://apache2triad.net/">ApacheTriad</a> dan XAMPP versi lebih rendah. Mudah-mudahan berhasil.</p>
<p>Seandainya para peserta itu pakai Linux saja ….</p>
URL bagus2006-01-24T02:15:00+07:00https://software.endy.muhardin.com/aplikasi/url-bagus<p>URL untuk website bagusnya tidak didikte oleh aplikasi maupun teknologi. Dengan demikian, kita bisa ganti engine website kita tanpa harus merusak URL.</p>
<p>Untuk itu, saya mengaktifkan fungsi Permalinks pada Wordpress. Jadi, URL yang tadinya seperti ini: <strong>http://endy.artivisi.com/blog/?p=7</strong> akan berubah menjadi seperti ini: <strong>http://endy.artivisi.com/blog/aplikasi/instalasi-subversion/</strong></p>
<p>Lebih canggih lagi, kalau kita browse ke <strong>http://endy.artivisi.com/blog/aplikasi/</strong> Wordpress akan menampilkan semua artikel yang ada dalam kategori <strong>aplikasi</strong>.</p>
<p>Ini akan sangat meningkatkan usability website.</p>
<p>Caranya, login ke control panel Wordpress, kemudian pergi ke Options > Permalinks.</p>
<p>Lalu baca manual dan tentang:</p>
<ol>
<li>Format URL yang digunakan Wordpress</li>
<li>Cara mengaktifkan .htaccess di Apache.</li>
</ol>
<p>Ini sebetulnya fitur lama dan umum digunakan orang, tapi saya belum sempat aktifkan sampai sekarang.</p>
Sulitnya menggunakan Windows2006-01-24T01:20:28+07:00https://software.endy.muhardin.com/aplikasi/sulitnya-menggunakan-windows<p>Bulan depan saya akan menjadi instruktur untuk training <a href="http://www.bugzilla.org">Bugzilla</a>, <a href="http://subversion.tigris.org">Subversion</a>, dan <a href="http://ant.apache.org">Ant</a>. Sebetulnya tidak ada hambatan berarti, karena di proyek sebelumnya saya sudah banyak menggunakan aplikasi tersebut. Cara instalasi dan konfigurasi juga sudah banyak saya tulis di blog ini, antara lain:</p>
<ul>
<li>
<p><a href="http://endy.artivisi.com/downloads/slide/Tutorial-Ant.pdf">Menggunakan Ant</a></p>
</li>
<li>
<p><a href="http://endy.artivisi.com/blog/?p=7">Cara Instalasi Subversion</a></p>
</li>
<li>
<p><a href="http://endy.artivisi.com/blog/?p=8">Konfigurasi Apache + Subversion + OpenLDAP</a></p>
</li>
</ul>
<p>Masalahnya adalah, sekarang saya harus mengajarkan cara menginstal dan konfigurasi aplikasi tersebut di Windows. Lalu, apa sulitnya? Banyak .. berikut saya berikan listnya:</p>
<ol>
<li>
<p>Apache dan extension Subversion, instalasi Apache di linux (apalagi debian) sangat mudah. Cukup ketik:</p>
<p><code># apt-get install apache2 subversion libapache2-svn</code></p>
</li>
<li>
<p>Bugzilla. Aplikasi ini membutuhkan Perl yang bisa diakses melalui Apache dengan mod_perl. Selain itu, kita perlu mendownload dan instal modul-modul tambahan. Lagi-lagi biasanya cukup:</p>
<p><code># apt-get install bugzilla</code></p>
</li>
<li>
<p>Mail Server. Bugzilla membutuhkan mail server untuk mengirim email notifikasi. Di linux, aplikasi mail server biasanya terinstal secara default. Kalaupun tidak ada, bisa diselesaikan dengan satu baris.</p>
</li>
</ol>
<p>Untungnya, seperti biasa telah ada orang lain di belahan dunia berbeda yang sudah pernah mengalami kepusingan yang sama, kemudian membuat solusinya.</p>
<p>Cukup instal saja <a href="http://www.apachefriends.org/en/xampp-windows.html">XAMPP</a>.</p>
<p>Paketnya memuat:</p>
<ul>
<li>
<p>Apache 2 dilengkapi WebDAV</p>
</li>
<li>
<p>Perl 5.8</p>
</li>
<li>
<p>Mail Server Mercury</p>
</li>
<li>
<p>FTP Server Filezilla</p>
</li>
</ul>
<p>Sekarang tinggal memikirkan bagaimana cara konfigurasi mod_perl, mod_dav, dan dav_svn. Setelah itu, baru mencoba install <a href="http://www.bugzilla.org">Bugzilla</a>. Wuah, sangat tidak praktis.</p>
<p>Kok masih ada orang yang berani-beraninya bilang pakai Windows lebih gampang ya? :D</p>
Kehilangan Laptop2005-12-23T23:04:54+07:00https://software.endy.muhardin.com/life/kehilangan-laptop<p>Teman saya baru saja kehilangan laptopnya kemarin. Kami pergi makan siang jam 12.10, dan kembali lagi ke kantor jam 12.40… dan laptopnya sudah tidak ada.</p>
<p>Setelah berkoordinasi dengan pihak keamanan gedung, kami berhasil mendapatkan rekaman CCTVnya. Berikut kronologi aktivitas sang maling seperti terekam di kamera.</p>
<ul>
<li>
<p>12.05 : Maling masuk di lobi lantai dasar</p>
</li>
<li>
<p>12.10 : Maling turun lift di lantai 8.</p>
</li>
<li>
<p>12.11 : Kami keluar dari kubikel dan menunggu lift. Sebenarnya berpapasan dengan sang maling. Tapi mana kami tau kalau dia mau mencuri?</p>
</li>
<li>
<p>12.13 : Lift datang, semua orang masuk lift kecuali saya, karena mau ke toilet dulu</p>
</li>
<li>
<p>12.14 : Saya selesai di toilet, menekan tombol lift. Sambil menunggu lift melongok ke kubikel untuk melihat keadaan. Sempat melihat orang itu, tapi tidak terlalu pay attention</p>
</li>
<li>
<p>12.25 : Saya turun dengan lift</p>
</li>
<li>
<p>12.25 : Sang maling meninjau situasi untuk terakhir kali, kemudian masuk ke ruangan kami (tidak tertangkap kamera)</p>
</li>
<li>
<p>12.30 : Keluar dari ruangan membawa laptop, ditenteng dengan tangan (dia tidak bawa tas) dan tidak ditutupi apa-apa. Kemudian menekan tombol lift. Sambil menunggu lift datang, mondar-mandir petantang-petenteng di depan satpam IndoNet</p>
</li>
<li>
<p>12.33 : Lift datang, dia masuk ke dalam lift</p>
</li>
<li>
<p>12.34 : Terlihat di kamera lobi lantai dasar, menenteng laptop di tangan dan berjalan dengan santai keluar gedung</p>
</li>
</ul>
<p>Hmm … begitu mudah, begitu cepat. Para pemilik laptop, jangan meninggalkan laptop Anda sembarangan. Bawalah kemana saja Anda pergi, ke toilet sekalipun.</p>
<p>Menurut pihak keamanan, maling yang sama telah menelan korban di lantai 10 bulan September lalu. Guess what, mereka juga punya rekaman videonya (makanya tau orangnya sama). What the f@#*, kalo sudah tau tampang malingnya kenapa gak waspada. Terlalu banyak makan gaji buta :( .</p>
Coding Java tanpa Instal Java2005-12-20T16:53:51+07:00https://software.endy.muhardin.com/java/coding-java-tanpa-instal-java<p><a href="http://oky.or.id">Teman saya</a> bekerja di salah satu bank terkemuka di Indonesia. Sebagai mantan programmer yang biasa menginstal ulang komputer sebulan sekali, tentunya akan sangat terpenjara bekerja di bank. Bagaimana tidak, instal <a href="http://messenger.yahoo.com">Yahoo Messenger</a> saja harus bikin form permohonan dulu.</p>
<p>Belakangan, teman saya ini sudah berhasil menginstal MySQL dan PHP tanpa akses Administrator. Ini dimungkinkan dengan penggunaan <a href="http://www.uniformserver.com">Uniform Server</a> yang tinggal extract dan jalankan. Nah, sekarang dia ingin coding Java.</p>
<p>Untunglah kita bisa coding Java tanpa harus instal. Cukup copy folder jre dari instalasi yang sudah ada (misalnya punya teman), dan masukkan di dalam folder <a href="http://www.eclipse.org">Eclipse</a>. Ternyata Eclipse ini hebat sekali, dia bisa compile source code tanpa harus instal <a href="http://java.sun.com/j2se/1.5.0/download.jsp">Java SDK</a>.</p>
<p>Voila, Eclipse siap digunakan. Mungkin ini salah satu alasan kenapa aplikasi bank dibuat dengan Java, developer tidak perlu akses Administrator untuk instal :P.</p>
<p>Selamat coding di bank ….</p>
Geek Personality Quiz2005-12-09T22:41:56+07:00https://software.endy.muhardin.com/life/geek-personality-quiz<p>Ada lagi kuis untuk membuang-buang waktu luang dan bandwidth Anda. Jangan ketinggalan!! Ketahuilah jenis personality Anda segera.</p>
<p>Ini hasil kuis saya :</p>
<p><a href="http://www.bbspot.com/News/2004/10/extension_quiz.php"><img src="http://www.bbspot.com/Images/News_Features/2004/10/file_extensions/exe.jpg" alt="You are .exe When given proper orders, you execute them flawlessly. You're familiar to most, and useful to all." /><br />
Which File Extension are You?</a></p>
Refactoring2005-11-28T18:55:21+07:00https://software.endy.muhardin.com/java/refactoring<p>Three times and you refactor !! Begitu kata Martin Fowler dalam bukunya Refactoring.</p>
<p>Sering terjadi dalam pembuatan aplikasi, kita akan menulis suatu rutin yang sudah pernah kita tulis sebelumnya, dengan sedikit perbedaan parameter dan variabel.
Biasanya, kalau menemui hal seperti ini, kita akan copy-paste dari kode yang sudah ada, melakukan sedikit modifikasi, dan selesai. Mudah dan sederhana, tapi terlalu rajin. Untuk jadi programmer yang efektif, kita perlu memiliki sifat malas.</p>
<p>Misalnya, kita punya UserDao, dengan kode seperti ini :</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">insert</span><span class="o">(</span><span class="nc">User</span> <span class="n">u</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// connect dulu</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">pass</span><span class="o">);</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"INSERT INTO tbl_user VALUES (?,?)"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Setelah itu, kita akan menambahkan method untuk delete:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">insert</span><span class="o">(</span><span class="nc">User</span> <span class="n">u</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// connect dulu</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">pass</span><span class="o">);</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"INSERT INTO tbl_user VALUES (?,?)"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span> <span class="o">(</span><span class="nc">User</span> <span class="n">u</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// connect dulu</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">pass</span><span class="o">);</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"DELETE FROM tbl_user WHERE id=?"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Nah, sekarang kita mau tambah method untuk search user berdasarkan id, tentunya kita akan tulis kode seperti ini :</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">insert</span><span class="o">(</span><span class="nc">User</span> <span class="n">u</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// connect dulu</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">pass</span><span class="o">);</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"INSERT INTO tbl_user VALUES (?,?)"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span> <span class="o">(</span><span class="nc">User</span> <span class="n">u</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// connect dulu</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">pass</span><span class="o">);</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"DELETE FROM tbl_user WHERE id=?"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">User</span> <span class="nf">search</span> <span class="o">(</span><span class="kt">int</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// connect dulu</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">pass</span><span class="o">);</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="s">"SELECT * FROM tbl_user WHERE id=?"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Stop !!!
Three times and you refactor. Artinya, begitu kita paste untuk kedua kali (berarti ada tiga kopi blok yang sama), itulah waktunya kita refactor.
Kita paste blok tersebut, masukkan ke dalam function/method sendiri, berikan parameter seperlunya. Ini untuk menghindari terjadinya duplikasi kode, sehingga untuk tiap perintah cuma ada satu versi.</p>
<p>Setelah direfactor, kode di atas menjadi :</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserDao</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nc">PreparedStatement</span> <span class="nf">prepareStatement</span><span class="o">(</span><span class="nc">String</span> <span class="n">sql</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// connect dulu</span>
<span class="nc">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">pass</span><span class="o">);</span>
<span class="k">return</span> <span class="n">conn</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">sql</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">insert</span><span class="o">(</span><span class="nc">User</span> <span class="n">u</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">prepareStatement</span><span class="o">(</span><span class="s">"INSERT INTO tbl_user VALUES (?,?)"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span> <span class="o">(</span><span class="nc">User</span> <span class="n">u</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">prepareStatement</span><span class="o">(</span><span class="s">"DELETE FROM tbl_user WHERE id=?"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">User</span> <span class="nf">search</span> <span class="o">(</span><span class="kt">int</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">prepareStatement</span><span class="o">(</span><span class="s">"SELECT * FROM tbl_user WHERE id=?"</span><span class="o">);</span>
<span class="c1">// ... dan seterusnya .... </span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Apabila kita menggunakan IDE canggih seperti Eclipse, IDEA, atau Netbeans, refactoring ini mudah saja dilakukan. Tinggal blok kode yang akan direfactor, kemudian klik kanan, dan pilih Extract Method. Method baru akan dibuatkan dan diisi dengan kode yang kita blok. Sedangkan kode asal yang kita blok sebelumnya akan diganti dengan pemanggilan method baru.</p>
<p>Extract Method hanya satu contoh kecil saja dari Refactoring. Masih ada Extract Classes, Inline Method, dan lain sebagainya. Buku wajib buat programmer Java.</p>
<p>Silahkan <a href="http://www.amazon.com/gp/product/0201485672/002-3388109-5046409?v=glance&n=283155&n=507846&s=books&v=glance">beli di Amazon</a> atau <a href="http://www.flazx.com/ebook790.php">download di Flazx</a>.</p>
Deadline !!!2005-11-25T01:28:55+07:00https://software.endy.muhardin.com/life/deadline<p>Deadline memang bikin stress.</p>
<p>Cangkir kopi bertebaran, dan kantung mata bergelayutan.
Tidak lupa bos mondar-mandir di belakang, terus menerus kirim email, telpon ke HP, kirim BUZZ lewat Y!, dan melakukan tindakan-tindakan lain yang menambah ketegangan.</p>
<p>Di tengah kesibukan deadline, jangan lupa untuk selalu berpikir positif, dan yang paling penting, jangan mudah tertipu. Beberapa jenis deadline sebetulnya palsu, merupakan kelihaian pihak-pihak tertentu yang menguasai teknik khusus.</p>
<p><a href="http://web.archive.org/web/20050207114630/http://www.thomsett.com.au/main/articles/hot/games.htm">Di website ini</a> dapat Anda baca beberapa teknik untuk menciptakan nuansa deadline di lingkungan Anda.</p>
<p>Selamat mengejar deadline, awas maag kumat. :P</p>
Subversion backup dan restore2005-11-22T21:59:04+07:00https://software.endy.muhardin.com/aplikasi/subversion-backup-dan-restore<p>Setelah repository terpasang dan telah terisi struktur folder awal, hal pertama yang wajib dilakukan adalah memasang perangkat backup otomatis. Berikut adalah checklistnya :</p>
<ol>
<li>
<p>Buat folder khusus backup (misalnya /home/endy/backup-svn)</p>
</li>
<li>
<p><a href="http://pragmaticprogrammer.com/titles/svn/code.html">Download script backup</a> dari buku <a href="http://pragmaticprogrammer.com/titles/svn/index.html">Pragmatic Version Control Using Subversion</a>.
Ada dua script yang didapat di sini, weekly-backup.pl dan daily-backup.pl
Kedua script butuh instalasi Perl untuk dapat dijalankan.</p>
</li>
<li>
<p>Letakkan script di folder backup</p>
</li>
<li>
<p>Sesuaikan permission :</p>
</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ chmod 755 *.pl
</code></pre></div></div>
<ol>
<li>Test eksekusi script</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./weekly-backup.pl
$ ./daily-backup.pl
</code></pre></div></div>
<p>Seharusnya akan ada dua file : weekly-full-backup.yyyyMMdd.gz dan daily-incremental-backup.yyyyMMdd.gz</p>
<ol>
<li>Otomasi dengan cron</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Repository Weekly Backup
0 6 * * 1 /home/endy/backup-svn/weekly-backup.pl
# Dijalankan setiap Senin jam 6 pagi.
# Repository Daily Backup
0 23 * * * /home/endy/backup-svn/daily-backup.pl
# Dijalankan setiap hari jam 23 malam
</code></pre></div></div>
<ol>
<li>Setiap Senin pagi, tulis semua file dari Senin minggu lalu sampai Minggu malam ke dalam CD. Simpan CD di lokasi yang berbeda (gedung lain, kota lain, negara lain, planet lain bila perlu).</li>
</ol>
<p>Bila semua ini dijalankan, cuma butuh 10 menit, sudah termasuk membaca artikel ini.
Jangan sia-siakan hidup Anda hanya karena malas investasi 10 menit.</p>
<p>Apabila terjadi bencana, dan repository rusak, kita tinggal ambil CD terbaru, dan dump dengan cara :</p>
<ol>
<li>Install ulang Subversion</li>
<li>Buat repository</li>
<li>
<p>Unzip dump file</p>
<p>$ gunzip *.gz</p>
</li>
<li>Load ke repository baru</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ svnadmin load newrepos < weekly-full-backup.yyyyMMdd
$ svnadmin load newrepos < daily-incremental-backup.yyyyMMdd
</code></pre></div></div>
<p>Dimulai dari tanggal paling lama, sampai tanggal terbaru.</p>
Testing Aplikasi2005-11-16T00:15:01+07:00https://software.endy.muhardin.com/java/testing-aplikasi<p>Sebelum diinstal di tempat customer, aplikasi yang kita buat harus ditest. Itu jelas. Semua orang melakukannya. Hanya saja, teknik melakukan testing akan membedakan antara yang pro dan belajaran (apalagi yang baru lulus kuliah :P)</p>
<p>Mari kita ambil contoh nyata. Misalkan kita punya class Calculator, dengan satu method divide yang tugasnya membagi dua bilangan. Berikut kodenya :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Calculator {
public double divide (double a, double b) {
return 0;
}
}
</code></pre></div></div>
<p>Programmer pemula (atau pro yang kurang wawasan), hampir pasti akan segera menulis</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static void main(String[] xxx) {
Calculator c = new Calculator();
System.out.println("10 dibagi 2 adalah : " +c.divide(10,2)); // hasilnya harus 5
}
</code></pre></div></div>
<p>Hmm… primitif sekali. Dengan segala kecanggihan teknologi komputer, kita yang berada di tengah dunia komputer, membuat program komputer, masih menggunakan teknologi jaman DOS prompt untuk melakukan test.</p>
<p>Apa yang salah dengan kode test di atas?
Salah, karena programmer harus mengeksekusi kode di atas, melihat bahwa tampilan yang dihasilkan seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>10 / 2 adalah : 0
</code></pre></div></div>
<p>membetulkan kode, kemudian lihat lagi, memastikan kali ini tampilannya seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>10 / 2 adalah : 5
</code></pre></div></div>
<p>Mengapa salah?
Karena ini tidak otomatis.
Bagaimana kalau kita punya 100 method?
Bagaimana kalau tiap method punya 2 kemungkinan yang harus ditest (misalnya: pembagian dengan 0, pembagian dengan bilangan negatif)?
Berarti ada sekitar 200 baris yang harus diteliti. Dan ini akan menjadi membosankan dan melelahkan.</p>
<p>Programmer profesional (baca: menghidupi anak istri dari coding) yang berwawasan luas akan menggunakan <a href="http://www.junit.org">Automated Unit Testing</a>. Tools ini akan memungkinkan kita untuk melakukan test secara :</p>
<ul>
<li>
<p>Cepat</p>
</li>
<li>
<p>Otomatis</p>
</li>
<li>
<p>Dapat dieksekusi dengan tekan satu tombol</p>
</li>
</ul>
<p>Programmer profesional yang berwawasan tersebut akan menulis test seperti ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void testDivide() {
Calculator c = new Calculator();
assertEquals(5, c.divide(10,2));
}
</code></pre></div></div>
<p>Dengan cara ini, komputerlah yang akan memeriksa kode kita. Pada waktu kita jalankan, komputer akan mencetak tanda titik bila sukses, huruf F bila gagal, dan huruf E bila terjadi Exception. Tidak perlu lagi periksa secara manual.
200 test? Tidak perlu khawatir. Tinggal jalankan dan lihat. Berapa huruf yang muncul, itulah yang diperbaiki.</p>
<p>Bagusnya lagi, semua test tadi dapat dikonfigurasi sehingga dapat dijalankan sekaligus. Jadi dengan sekali tekan tombol, kita dapat menjalankan semua test yang telah kita buat dari sejak project dimulai.</p>
<p>Proses ini dapat diotomasi lebih jauh lagi, apabila kita menggunakan Ant, atau plugin IDE (misalnya Eclipse). Proses testing dapat diintegrasikan. Sehingga kalau biasanya setelah melakukan perubahan di source code kita lakukan save dan compile, dengan sedikit konfigurasi, kita ubah menjadi save-compile-test.
Dengan sekali tekan tombol, kode yang baru saja kita edit telah ditest.</p>
<p>Ingatlah, programmer profesional mencari nafkah dari coding. Semakin cepat menyelesaikan aplikasi, semakin cepat dapat mengirimkan tagihan ke customer, dan semakin cepat pula istri akan tersenyum.</p>
<p>Tulisan ini merupakan bagian pertama dari rangkaian tulisan tentang Otomasi Pembuatan Aplikasi.</p>
Lebaran dan Mudik2005-11-15T17:30:03+07:00https://software.endy.muhardin.com/life/09<p>Selamat Lebaran Idul Fitri 1426 H.
Mohon maaf lahir dan batin.</p>
<p>Seperti tahun yang lalu, lebaran ini diramaikan dengan kegiatan mudik. Ini adalah rutinitas baru sejak menikah. Maklum, sebelumnya saya tidak punya kampung, karena lahir dan besar di Jakarta. Pada waktu orang-orang pulang kampung, saya malah pulang kota.</p>
<p>Transportasi tahun ini jauh lebih baik daripada tahun kemarin. Primajasa baru saja membuka pool bis di Jl. Mayjend Sutoyo, beberapa ratus meter jalan kaki dari rumah. Ruang tunggu AC, air mineral gratis, pelayanan yang ramah, dan yang paling penting, calon penumpang yang jauh lebih sedikit (daripada di terminal umum). Bis jurusan Garut cuma ada satu jenis, yaitu AC Ekonomi. Bagi pembaca yang awam dengan urusan per-bis-an, AC Ekonomi adalah bis yang dilengkapi dengan AC, tapi memiliki konfigurasi tempat duduk seperti bis Ekonomi, yaitu dua di kiri dan tiga di kanan dengan sandaran bangku yang terima apa adanya alias tidak bisa diatur kemiringannya.</p>
<p>Bandingkan dengan terminal Kampung Rambutan (yang biasanya kami gunakan). Baru sampai pintu gerbang, ‘keramahan’ calo sudah bisa dinikmati. Berhasil menerobos kerumunan calo, kami harus menunggu di ruang tunggu masal, yang dikelilingi bis ngetem (tentunya sambil menyalakan mesin). Pilihan bis juga terbatas. Sampai tahun lalu, bis yang menuju Garut cuma Karunia Bakti. Dan kita tidak bisa memilih AC atau Ekonomi, karena Karunia Bakti cuma punya satu jalur antrian. Jika kebetulan yang dapat giliran bis Ekonomi, pilihannya adalah naik, atau menunggu bis berikutnya (yang belum tentu AC).</p>
<p>Simply said, pool Primajasa yang baru ini benar-benar suatu berkah yang wajib disyukuri.</p>
<p>Dari sisi transportasi, memang menjadi lebih mudah. Tapi dari sisi peserta mudik, lain ceritanya. Tahun kemarin, Khalisa masih di dalam perut Ambu. Tapi tahun ini, dia sudah bisa duduk sendiri. Ini menimbulkan tantangan tersendiri.
<a href="wp-content/dsc01286.jpg"><img src="wp-content/dsc01286.jpg" alt="Khalisa Laughing " /></a></p>
<p>Sebulan sebelum lebaran, kami sudah ujicoba membawa Khalisa ke Garut, dan berjalan dengan lancar. Dia menikmati perjalanan, dan tampaknya senang melihat pemandangan di jalan. Dengan total durasi perjalanan Jakarta- Garut yang cuma 4 jam saja, nampaknya dia tidak terlalu bosan. Tapi mudik berbeda, walaupun Jakarta-Cileunyi dapat ditempuh dalam waktu 3 jam, Nagrek tidak.</p>
<p><img src="wp-content/kimi.jpg" alt="Kimi Raikonnen" /> Para pembaca tentunya sudah pernah mendengar tentang Nagrek. Tanjakan curam yang dilengkapi dengan beberapa tikungan berbentuk huruf U, yang bahkan dapat membuat miris <a href="http://www.racecar.net/kimi/">Kimi Raikonnen</a> sekalipun.
Nagrek sudah lama menjadi momok para pemudik. Kendaraan yang kondisi kesehatannya minim jangan harap selamat. Kendaraan yang sangat sehat juga harus dilengkapi dengan pengemudi yang handal.</p>
<p>Sebetulnya, pada kondisi normal, Nagrek mudah dilalui. Memang membutuhkan keterampilan tinggi, tapi tidak terlalu menakutkan. Yang membuat Nagrek menjadi <em>bottleneck</em> adalah, pada saat mudik banyak bis yang lewat berbarengan.
Bis, seperti kita tahu, lemah di tanjakan, sehingga kita akan banyak menghadapi logam sebesar rumah berbobot ratusan ton yang merangkak tertatih-tatih sambil menyemburkan bakaran solar hitam di belakangnya.
Menurut saya, inilah faktor utama yang membuat KO kendaraan berkondisi pas-pasan. Mereka dipaksa menanjak pelan-pelan (kadang berhenti) di belakang bis. Suhu panas, putaran mesin tinggi, intensitas pengereman yang tinggi, semua ini sangat menyiksa mesin. Seandainya jalan lancar, semua ini bisa dihindari.</p>
<p>Alhasil, pada kesempatan mudik kali ini, Nagrek berhasil kami lalui dalam waktu 4 jam. Lepas dari Nagrek, jalanan lancar dan sepi. Khalisa sudah lelah dan bosan. Walaupun demikian, dia tetap shalehah dan tidak terlalu rewel. Sementara itu, anak tetangga sebelah sudah menangis menjerit-jerit. Segala daya upaya sudah dikerahkan, tetapi nampaknya perjalanan mudik terlalu berat untuknya. Menurut keterangan yang berhasil dikumpulkan, bocah tersebut berusia 9 bulan. Satu bulan lebih tua daripada Khalisa. Narasumber tersebut (yang disinyalir adalah bapaknya) tidak bersedia berkomentar lebih lanjut dengan alasan sibuk. Setelah itu, dia terlihat sibuk meracik susu formula untuk anaknya yang histeris tersebut.</p>
<p>Alhamdulillah Khalisa adalah pengguna ASI yang loyal. Ini sangat memudahkan orang tua pada waktu mudik. Kami tidak perlu membawa kaleng susu, termos, perangkat dot, dan segala macam sanitizer. Cukup cari tempat duduk yang memiliki sedikit privasi.</p>
<p>Sebagai referensi, berikut saya tuliskan daftar perlengkapan Khalisa yang harus tersedia dalam jangkauan tangan :</p>
<ul>
<li>
<p>Mainan</p>
</li>
<li>
<p>Bubur instan</p>
</li>
<li>
<p>Piring makan</p>
</li>
<li>
<p>Sendok makan</p>
</li>
<li>
<p>Gelas</p>
</li>
<li>
<p>Tisu basah (untuk membersihkan sisa makanan di muka)</p>
</li>
<li>
<p>Tisu kering (untuk membersihkan peralatan makan)</p>
</li>
<li>
<p>Air mineral (untuk diminum, dan untuk mencairkan bubur instan)</p>
</li>
</ul>
<p>Sedangkan kostum yang digunakan Khalisa selama perjalanan (dengan bis AC selama 7-8 jam) adalah :</p>
<ul>
<li>
<p>Celana panjang</p>
</li>
<li>
<p>Baju lengan panjang</p>
</li>
<li>
<p>Kaus dalam (singlet)</p>
</li>
<li>
<p>Jaket</p>
</li>
<li>
<p>Kaus kaki</p>
</li>
<li>
<p>Jilbab</p>
</li>
<li>
<p>Lampin sekali pakai</p>
</li>
</ul>
<p>Jangan lupa siapkan tiga set pakaian seperti di atas untuk berjaga-jaga.</p>
<p>Demikian laporan mudik tahun ini. Mudah-mudahan masih ada cukup umur untuk mudik di tahun depan.</p>
<p><a href="wp-content/dsc01278.jpg"><img src="wp-content/dsc01278.jpg" alt="Endy-Maya-Khalisa" /></a></p>
Menggunakan Version Control untuk Dokumen2005-10-25T02:58:03+07:00https://software.endy.muhardin.com/aplikasi/menggunakan-version-control-untuk-dokumen<p>Aplikasi version control yang bagus dapat mengelola binary file (misalnya jpg, doc, pdf, dsb) dengan baik, seperti halnya mengelola source code yang berformat text. Salah satu diantaranya adalah <a href="http://subversion.tigris.org">Subversion</a>.</p>
<p>Dalam konteks project software, feature ini dapat dimanfaatkan untuk menyimpan dokumen lain selain source code, misalnya kontrak kerja sama, dokumen analisa, dsb.</p>
<p>Bahkan untuk kasus ekstrim, Subversion juga dapat digunakan untuk mengelola dokumen pribadi kita, <a href="http://www.onlamp.com/pub/a/onlamp/2005/01/06/svn_homedir.html">seperti yang dilakukan oleh Oom Joey Hess</a>. Sebelumnya dia <a href="http://kitenet.net/~joey/cvshome.html">menggunakan CVS</a>. Tetapi akhirnya dia beralih ke Subversion.</p>
Autobackup to Gmail2005-10-25T02:50:42+07:00https://software.endy.muhardin.com/aplikasi/autobackup-to-gmail<p>Wordpress memang hebat, ada <a href="http://www.lifehut.org/2005/08/17/wordpress-google-killer-combo/">plugin untuk membackup isi blog (isi tabel MySQL) secara periodik ke email</a>.
Email yang kita gunakan bisa ditentukan di konfigurasi. Gunakan <a href="http://www.gmail.com">Gmail</a> untuk kapasitas yang besar.</p>
<p>Sekarang kita bisa memikirkan tulisan dan tidak perlu mengkhawatirkan backup.</p>
Screen Recording2005-10-19T23:39:23+07:00https://software.endy.muhardin.com/aplikasi/screen-recording<p>Jaman sekarang orang bikin tutorial udah gak PDF lagi. Ketinggalan jaman oom.
Sekarang musimnya tutorial pake movie, baik AVI, MPG, atau SWF.
Jangan sampe kalah gaya sama situs porno.</p>
<p>Di windows ada aplikasi gratis untuk merekam screen, namanya <a href="http://www.bobyte.com/AviScreen/index.asp">AviScreen</a>. Tapi masalahnya, saya sudah selamat tinggal sama sistem operasi yang ada <a href="http://www.microsoft.com/windows/default.mspx">jendela</a>nya. Bukan apa-apa, namanya ada jendela, pasti debu dan serangga masuk membawa virus. Jadi, AviScreen tidak bisa digunakan.</p>
<p>Untungnya di Linux, pilihan lebih banyak. Ada <a href="http://www.unixuser.org/~euske/vnc2swf/">vnc2swf</a> atau <a href="http://www.sodan.org/~penny/vncrec/">vncrec</a>. vnc2swf outputnya Flash Movie, sedangkan vncrec punya format sendiri yang cuma dia sendiri yang bisa baca.</p>
<p>Update (6 Juni 2006): Ternyata ada lagi aplikasi yang namanya <a href="http://www.debugmode.com/wink/">Wink</a>. Tersedia untuk Windows maupun Linux.</p>
<p>Ada beberapa artikel yang membahas tentang cara penggunaan dan perbandingan aplikasi ini. <a href="http://www.newsforge.com/article.pl?sid=04/08/16/2128226">Newsforge</a> dan <a href="http://www.linux.com/article.pl?sid=04/07/26/1815242">Linux.com</a> membahas secara lengkap tentang cara penggunaanya.</p>
<p>Berbeda dengan sistem operasi Jendela yang cuma bisa punya satu desktop, di linux desktop bisa sebanyak-banyaknya. Ini bisa menyenangkan (bagi yang sudah tahu), dan bisa juga membingungkan (bagi yang belum tahu). Jadi, kalo AviScreen kita cukup tekan tombol Record, kalau di linux ada beberapa langkah sebelum tekan tombol record, yaitu:</p>
<ol>
<li>
<p><strong>Jalankan VNC server.</strong>
VNC server adalah aplikasi yang bertugas untuk mempublish desktop kita. Pada saat dijalankan, dia akan menjalankan desktop baru yang berbeda dengan screen yang kita hadapi sekarang.</p>
</li>
<li>
<p><strong>Jalankan vnc2swf.</strong>
Setelah itu, kita bisa jalankan vnc2swf untuk connect ke VNC server yang telah kita publish pada langkah pertama.</p>
</li>
<li>
<p><strong>Mulai merekam.</strong>
Setelah terhubung dengan VNC server, kita bisa langsung mulai merekam.</p>
</li>
</ol>
<p>Jebakan VNC server :
VNC server akan menjalankan desktop baru. Jadi, yang akan direkam adalah desktop baru tersebut, bukan desktop yang sekarang kita gunakan. Jadi kita harus menampilkan dulu desktop baru tersebut, baru mulai merekam.
Untuk menampilkan desktop baru tersebut, kita bisa gunakan Remote Desktop Connection yang ada di KDE.</p>
<p>Kalau kita mau merekam desktop yang sedang kita gunakan saat ini, kita harus mempublish desktop dengan aplikasi lain yang namanya x11vnc. Jika dijalankan, aplikasi ini akan mempublish aktivitas desktop kita ke vnc server.</p>
<p>Demikian cara merekam aktivitas desktop.
Selamat mencoba.</p>
Keanehan konfigurasi OpenLDAP2005-10-14T19:58:59+07:00https://software.endy.muhardin.com/aplikasi/keanehan-konfigurasi-openldap<p>Satu hal yang sangat mengganggu dalam OpenLDAP adalah, secara default dia mengijinkan anonymous login. Dan parahnya lagi, user yang login secara anonymous dapat melihat daftar username yang terdaftar.
Walaupun aksesnya cuma readonly, tapi tetap saja ini adalah potensi bahaya.</p>
<p>Saya coba mendisable account anonymous dengan cara menambahkan baris ini :
<code class="language-plaintext highlighter-rouge">disallow bind_anon </code>
ke dalam <code class="language-plaintext highlighter-rouge">/etc/ldap/slapd.conf</code>.</p>
<p>Berhasil, sekarang orang tidak bisa otentikasi secara anonymous. Tapi muncul masalah berikutnya. Subversion WebDAV juga ternyata tidak bisa login.
Setelah ditrace ke lognya apache, dia bilang tidak bisa menggunakan mekanisme simple_bind untuk otentikasi. Berikut pesan errornya :</p>
<p><code class="language-plaintext highlighter-rouge">[LDAP: ldap_simple_bind_s() failed][Inappropriate authentication]</code></p>
<p>Sepertinya, dengan mendisable anonymous, metode otentikasi simple juga di-disable. Sampai saat tulisan ini diposting, saya belum menemukan cara untuk mematikan anonymous tapi tidak mematikan simple_bind.</p>
<p>Akhirnya, workaroundnya adalah, mengaktifkan anonymous, tapi melucuti semua ijin aksesnya.
Ini dilakukan dengan cara mengubah konfigurasi akses (/etc/ldap/slapd.conf) dari</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>access to *
by * read
</code></pre></div></div>
<p>menjadi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>access to *
by anonymous none
by * read
</code></pre></div></div>
<p>Dengan demikian, sekarang anonymous bisa login, tapi tidak bisa lihat apa-apa.</p>
<p>**Update 17 Oktober 2005 : **
Ternyata selain anonymous tidak bisa lihat apa-apa, WebDAV juga gak bisa lihat apa-apa. Sepertinya, kalau dia mau otentikasi username, dia akan login anonymous dulu untuk melihat daftar user yang terdaftar.
Dengan mematikan anonymous, para user Subversion jadi tidak bisa login dengan pesan error : username not found. Jadi, sementara account anonymous terpaksa dibiarkan seperti defaultnya OpenLDAP.</p>
<p>**Update 18 Oktober 2005 : **
Setelah google kesana kemari, akhirnya saya menemukan <a href="http://www.openldap.org/lists/openldap-software/200401/msg00034.html">orang yang mengalami masalah yang sama</a>. Tentu saja masalah ditemukan bersama dengan <a href="http://www.rudedog.org/auth_ldap/1.6/auth_ldap.html#dir:AuthLDAPBindDN">solusinya</a>.
Sekarang kita bisa mendisable account anonymous, dan apache tetap bisa mengotentikasi user Subversion dengan tenang. Tidak perlu lagi workaround untuk melucuti akses anonymous.</p>
Otentikasi Apache menggunakan LDAP2005-10-14T18:15:50+07:00https://software.endy.muhardin.com/aplikasi/otentikasi-apache-menggunakan-ldap<p>Pada tulisan sebelumya, saya telah menceritakan cara instalasi dan konfigurasi <a href="http://subversion.tigris.org">Subversion</a>. Juga telah diceritakan cara mengaktifkan mode akses HTTP melalui <a href="http://httpd.apache.org">Apache 2 + WebDAV</a>.</p>
<p>Setelah sistem berjalan, ternyata ada kekurangnyamanan dalam mengelola repository. Seperti kita ketahui, yang menggunakan repository adalah para programmer dan tim ofisialnya, semuanya terlibat dalam suatu project. Project datang dan pergi, demikian juga username bertambah dan berkurang.</p>
<p>Cara manajemen username melalui htpasswd tidak scalable. Tambah user harus lapor, ganti password harus dibantu, hapus user juga sama, semua tidak bisa diotomasi. Setidaknya saya belum tau cara mengelola file htpasswd yang lebih efisien. Keinginan saya adalah, untuk menambah dan menghapus username dilakukan administrator, tapi untuk urusan ganti password silahkan ganti sendiri melalui web.</p>
<p>Setelah google ke sana kemari, metode yang umum digunakan orang adalah otentikasi melalui LDAP server. Dan ternyata, memindahkan otentikasi dari file htpasswd ke LDAP tidak sulit, cukup ganti konfigurasi <code class="language-plaintext highlighter-rouge">dav_svn.conf</code> dalam folder <code class="language-plaintext highlighter-rouge">/etc/apache2/mods-available</code> ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AuthUserFile /etc/apache2/htpasswd
</code></pre></div></div>
<p>menjadi ini :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AuthBasicProvider ldap
AuthLDAPURL ldap://localhost/dc=artivisi,dc=com?uid?sub?
AuthLDAPBindDN uid=apache,dc=artivisi,dc=com
AuthLDAPBindPassword "passwd-user-apache-dalam-ldap"
</code></pre></div></div>
<blockquote>
<p>Perhatian !!! Konfigurasi ini berjalan di Ubuntu 7.04 dengan Apache 2.2. Bila Anda menggunakan distro lain atau Apache 2.0, konfigurasi ini mungkin harus dimodifikasi agar bisa berjalan dengan baik.</p>
</blockquote>
<p>Kita membutuhkan modul <code class="language-plaintext highlighter-rouge">mod\_authnz\_ldap</code> agar Apache bisa melakukan otentikasi ke LDAP.</p>
<p>Lebih lanjut tentang otentikasi LDAP ini bisa dilihat di <a href="http://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html">manualnya Apache</a>.</p>
<p>Untuk mengaktifkan modul ini, cukup gunakan perintah berikut:</p>
<p><code class="language-plaintext highlighter-rouge">a2enmod authnz\_ldap</code></p>
<p>Nah, di sisi Apache tidak sulit. Instalasi LDAP di Debian juga sama mudahnya, cukup :</p>
<p><code class="language-plaintext highlighter-rouge"># apt-get install slapd</code></p>
<p>untuk menginstal OpenLDAP, dan</p>
<p><code class="language-plaintext highlighter-rouge"># apt-get install phpldapadmin</code></p>
<p>untuk menginstal <a href="http://phpldapadmin.sourceforge.net">PHPLDAPAdmin</a>. Ya benar … LDAP Admin, bukan phpmyadmin.</p>
<p>Ternyata merek phpXXadmin ini telah sangat kuat. Mungkin kalau <a href="http://www.hermawankartajaya.com">Hermawan Kartajaya</a> pernah menggunakan <a href="http://www.mysql.org">MySQL</a>, <a href="http://www.postgresql.org">PostgreSQL</a>, dan <a href="http://www.openldap.org">OpenLDAP</a>, dia juga akan membahas mengenai kekuatan merek phpXXadmin ini. Sejauh ini saya telah menggunakan <a href="http://www.phpmyadmin.net">phpmyadmin</a> dan <a href="http://phppgadmin.sourceforge.net">phppgadmin</a>. Dan sekarang ada <a href="http://phpldapadmin.sourceforge.net">phpldapadmin</a>. Luar biasa.</p>
<p>Instalasi <a href="http://www.openldap.org">OpenLDAP</a> mudah sekali, kita hanya ditanya nama domain yang akan disimpan, saya jawab <strong>artivisi.com</strong>. Kemudian ditanya password untuk administrator dan metode enkripsinya. OpenLDAP menyediakan clear, crypt, dan md5.
Setelah itu … selesai.</p>
<p>Instalasi phpldapadmin sedikit bermasalah. Masalah utama adalah permission. Entah kenapa, debian menginstalnya dengan permission yang tidak sesuai.
Ini menyebabkan beberapa halaman tidak bisa diakses karena web server tidak diijinkan untuk membacanya.
Untuk mudahnya, saya melakukan :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># chown -R www-data.www-data /etc/phpldapadmin
# chown -R www-data.www-data /usr/share/phpldapadmin
</code></pre></div></div>
<p>Ok, selesailah sudah.
Sekarang untuk menambah user dapat dilakukan melalui web. Konfigurasi default OpenLDAP juga sangat bagus. Hanya admin yang dapat memanipulasi data. User biasa (uid) hanya bisa mengganti atributnya sendiri (termasuk password).</p>
<p>Jadi berikutnya, kalau ada user baru, tinggal tambahkan melalui web dengan password standar. Kemudian suruh user tersebut login dan ganti password. Beres …</p>
<p>Referensi :<br />
<a href="http://www.google.com">Google</a></p>
<p><a href="http://www.scratchbox.org/documentation/docbook/svn.html">Scratchbox</a></p>
Instalasi Subversion2005-10-13T00:38:20+07:00https://software.endy.muhardin.com/aplikasi/instalasi-subversion<p>Setelah pindah ke rumah baru, tugas pertama adalah instalasi <a href="http://subversion.tigris.org">Subversion</a> lengkap dengan akses melalui http.</p>
<p><a href="http://subversion.tigris.org">Subversion</a> adalah aplikasi version control system. Cara instalasi detail dan tutorial penggunaan sedang saya susun dalam sebuah tutorial terpisah. Sementara ini, berikut cara cepat instalasi Subversion dan aktivasi akses melalui WebDAV.</p>
<p>Untuk mengaktifkan akses WebDAV, dibutuhkan webserver <a href="http://httpd.apache.org">Apache 2</a>. Apache 2 digunakan (bukan Apache 1) karena telah menyediakan dukungan terhadap WebDAV. WebDAV sendiri adalah tambahan pada protokol HTTP yang memungkinkan aplikasi client memodifikasi file di server. Tanpa WebDAV, aplikasi client (misalnya Internet Explorer atau <a href="http://mozilla.org">Mozilla Firefox</a>) hanya bisa membaca data dari webserver dan tidak bisa menyimpan data.</p>
<p>Berikut adalah langkah-langkahnya :</p>
<ol>
<li>
<p>Install Subversion, Apache2 dan WebDAV module :</p>
<p># apt-get install subversion apache2 libapache2-svn</p>
</li>
<li>Khusus Linux, kita harus membuat wrapper script untuk :
<ul>
<li>svn</li>
<li>svnadmin</li>
<li>svnlook</li>
<li>svnserve</li>
</ul>
<p>Gunanya agar folder repository dibuat dengan permission yang benar, yaitu 664. Artinya, user/pembuat repository bisa baca-tulis(rw=6), groupnya bisa baca-tulis, dan orang lain bisa baca(r=4).</p>
<p>Caranya adalah dengan mengganti script yang asli dengan wrappernya. Pertama, ganti dulu nama script yang asli sebagai berikut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # mv svn svn-original
# mv svnadmin svnadmin-original
# mv svnlook svnlook-original
# mv svnserve svnserve-original
</code></pre></div> </div>
<p>Kemudian buat file text baru untuk wrappernya, jangan lupa untuk membuat file tersebut executable.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # touch svn svnadmin svnlook svnserve
# chmod 755 svn svnadmin svnlook svnserve
</code></pre></div> </div>
<p>Berikut adalah contoh wrapper script untuk svn.
File svnadmin, svnlook, dan lainnya mirip, cuma berbeda di baris keempat saja.</p>
<p>#!/bin/sh</p>
<p>umask 002
/usr/bin/svn-original “$@”</p>
</li>
<li>
<p>Buat repository, misalnya di folder /home/endy/svnrepo :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ svnadmin create --fs-type fsfs /home/endy/svnrepo
</code></pre></div> </div>
</li>
<li>
<p>Buat group untuk mengakses repository, misalnya group svnusers</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # groupadd repousers
</code></pre></div> </div>
</li>
<li>
<p>Ubah kepemilikan repository menjadi milik group</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ chgrp -R repousers /home/endy/svnrepo
</code></pre></div> </div>
</li>
<li>
<p>Masukkan semua user yang akan mengakses repository ke dalam group repousers. Sekarang repository siap diakses secara ssh.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ svn list svn+ssh://localhost/home/endy/svnrepo
</code></pre></div> </div>
</li>
<li>
<p>Edit Apache 2 agar berjalan dengan group repousers.
Instruksi dibawah berlaku untuk Debian. Untuk distro lain silahkan lihat manualnya masing-masing. Pengguna Windows tidak perlu pusing-pusing tentang group, di OS anda fitur ini diabaikan :P</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mc -e /etc/apache2/apache2.conf
</code></pre></div> </div>
<p>ganti baris</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Group www-data
</code></pre></div> </div>
<p>menjadi</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Group repousers
</code></pre></div> </div>
</li>
<li>
<p>Buat username dan password untuk mengakses repository melalui web. Debian menggunakan script htpasswd2. Distro Anda mungkin berbeda.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # htpasswd2 -mc /etc/apache2/svnusers endy
</code></pre></div> </div>
<p>Lihat manual htpasswd untuk lebih jelasnya.</p>
</li>
<li>
<p>Edit konfigurasi dav_svn. Intinya adalah, kita harus menambahkan Virtual Directory di Apache. Teknik konfigurasi yang ‘benar’ berbeda di masing-masing distro.
Untuk cara paling mudah, tulis saja langsung di bagian paling bawah file httpd.conf.
Di Debian, cara paling ‘bersih’ adalah membuat file konfigurasi sendiri di folder /etc/apache2/mods-available.</p>
<p>Berikut adalah konfigurasi dav_svn. Pastikan file ini diload oleh Apache.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <Location /svn>
DAV svn
SVNPath /home/endy/svnrepo
AuthType Basic
AuthName "Subversion Web Authentication"
AuthUserFile /etc/apache2/svnusers
AuthzSVNAccessFile /etc/apache2/dav_svn.authz
Require valid-user
</Location>
</code></pre></div> </div>
</li>
<li>
<p>Mengaktifkan koneksi https</p>
<ul>
<li>
<p><a href="http://www.eclectica.ca/howto/ssl-cert-howto.php">Buat sertifikat</a>, menghasilkan file svn.cert.pem dan svn.key.pem</p>
</li>
<li>
<p>Aktifkan mod_ssl pada Apache. Caranya berbeda sesuai distro. Di debian adalah dengan cara dengan cara membuat symlink mod_ssl.conf dan mod_ssl.load ke folder /etc/apache2/mods-enabled</p>
</li>
<li>
<p>Edit konfigurasi default website (/etc/apache2/sites-available/default).</p>
<p>SSLEngine on
SSLCertificateFile /etc/apache2/ssl/svn.cert.pem
SSLCertificateKeyFile /etc/apache2/ssl/svn.key.pem
SetEnvIf User-Agent “.<em>MSIE.</em>” nokeepalive ssl-unclean-shutdown</p>
</li>
<li>
<p>Buka koneksi di port 443 dengan cara mengedit konfigurasi Apache dan menambahkan baris berikut :</p>
</li>
</ul>
<p>Listen 443</p>
<ul>
<li>Restart apache</li>
</ul>
<p>Seharusnya sekarang repository kita sudah bisa :</p>
<ul>
<li>diakses melalui ssh</li>
<li>diakses melalui https dengan memasukkan username dan password yang benar</li>
</ul>
</li>
</ol>
<p>Selamat mencoba.</p>
<p>Referensi :</p>
<ul>
<li>
<p><a href="http://www.debian-administration.org/articles/31">Setting up a secure server with Apache and mod-ssl</a></p>
</li>
<li>
<p><a href="http://www.ianmiller.net/article.php?id=13">Debian, Apache2 and SSL</a></p>
</li>
</ul>
Rumah baru II2005-10-10T22:07:50+07:00https://software.endy.muhardin.com/life/rumah-baru-ii<p>Hari ini saya dapat komputer baru. Spesifikasinya lumayan banget, Prosesor P-IV 3 GHz, memori 1GB, dan harddisk SATA 80GB, plus DVD-ROM. Rencananya, mesin ini selain untuk office work, juga akan jadi server sementara.</p>
<p>Kegiatan pertama, bagi sebagian besar orang adalah instal sistem operasi. Buat saya, ada satu kegiatan lagi sebelum instal, yaitu pilih sistem operasi.</p>
<p>Sudah jelas, yang akan saya instal adalah Linux. Sejak meninggalkan Windows tahun 2002 yang lalu, saya merasa seperti naik baswei (baca: busway) jam 5 sore. Melihat ke kiri jalan, kemacetan panjang sekali seperti ular naga, tetapi saya tetap bisa melaju kencang menuju Blok M dalam waktu 30 menit saja. Di dalam busway Linux, saya tetap bisa produktif bekerja pada saat orang lain repot mengupdate antivirus (instal saja tidak cukup, harus up to date juga dong :P), membuang spyware, melakukan defrag, bersih-bersih registry, dan kegiatan non-produktif lainnya.</p>
<p>Nah, pertanyaannya, Linux yang mana?
Saya adalah fans berat <a href="http://www.debian.org">Debian</a>. <a href="http://fedora.redhat.com">Fedora</a> terlalu sulit buat saya. <a href="http://www.mandriva.org">Mandriva</a>, mudah bagi pemula, tapi justru menyulitkan bagi pengguna veteran.
Tapi di debian sendiri juga banyak variannya, misalnya <a href="http://www.knoppix.org">Knoppix</a>, <a href="http://www.mepis.org">Mepis</a>, <a href="http://ubuntulinux.org">Ubuntu</a>, dan lainnya.</p>
<p>Knoppix, bagus untuk LiveCD, tapi jelek sekali kalau diinstal di harddisk. Struktur foldernya jelek dan sudah tidak standar lagi. Mungkin karena memang dioptimasi untuk kemudahan remastering.</p>
<p>Mepis bagus, tapi repositorynya tidak kompatibel dengan debian. Untuk pengguna desktop sangat bagus, tapi karena saya mau instal buat macam2 server, di belakang hari menyulitkan.</p>
<p>Ubuntu, sangat bagus … untuk end user. Buat saya, masalah sudo sangat merepotkan. Default instalasinya juga Gnome, sedangkan saya pengguna KDE. Saya sudah berusaha membiasakan diri dengan Gnome, tapi aplikasi-aplikasi Gnome masih kalah (menurut saya) dengan KDE. Saya lebih suka pakai KMail, Akregator, Konqueror daripada Evolution dan Nautilus.</p>
<p>Akhirnya, saya back to basic. Pakai debian murni saja. Toh di sini koneksi IIX laksana LAN. Jadi bisa ambil ke servernya <a href="http://komo.vlsm.org">Komo</a> dengan cepat.</p>
<p>Nah, sekarang instalasi. Saya tidak punya CD Writer, jadi walaupun bisa donlod ISO dengan cepat, tetap tidak bisa instal. Akhirnya pakai cara curang sbb :</p>
<ol>
<li>
<p><a href="http://komo.vlsm.org/debian/dists/Debian3.1r0/main/installer-i386/current/images/netboot/debian-installer/i386/2.6/">Download</a> image instalasi jaringan dari Debian (ingat, gunakan kernel 2.6)</p>
</li>
<li>
<p>Instal Ubuntu</p>
</li>
<li>
<p>Kopikan image instalasi ke Ubuntu</p>
</li>
<li>
<p>Konfigurasi GRUB untuk menambahkan image instalasi</p>
</li>
<li>
<p>Restart, dan mulai instalasi.</p>
</li>
</ol>
<p>Cara ini diinspirasi dari <a href="http://marc.herbert.free.fr/linux/win2linstall.html">teknik menginstal Linux tanpa CDROM, Floppy, USB, atau media lainnya</a>.</p>
<p>Setelah itu, berikut beberapa aplikasi wajib yang akan diinstal :</p>
<ol>
<li>Midnight Commander</li>
<li>SSH</li>
<li>MySQL</li>
<li>PostgreSQL</li>
<li>Apache 2</li>
<li>PHP 4</li>
<li>Postfix Mail Server</li>
<li>Subversion Version Control</li>
<li>CVS Version Control</li>
<li>Firestarter (Firewall Front End)</li>
<li>KDE</li>
</ol>
<p>Selanjutnya, tinggal mengkonfigurasi.</p>
<p>Satu lagi, dengan menggunakan Linux, tidak perlu cari satu-satu, download, klik next beberapa kali, dan restart komputer.
Untuk menginstal semua di atas, cukup ketik saja :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install mc kde mysql-server postgresql apache2 php4 postfix firestarter subversion cvs kde -y
</code></pre></div></div>
<p>sebagai root.
Lalu … sit back and relax …. literally.</p>
Mencoba Wordpress2005-10-07T16:45:15+07:00https://software.endy.muhardin.com/lain/mencoba-wordpress<p>Kalau kemarin pakai mambo, sekarang coba pakai Wordpress. Kata orang sih, ini adalah aplikasi yang terhebat di kelasnya (kelas weblog maksudnya). Buat saya, aplikasi weblog yang hebat haruslah bisa menampilkan kode program dengan bagus. Soalnya, saya sering menjadikan blog sebagai website tutorial.
Menampilkan dengan bagus artinya adalah :</p>
<ul>
<li>
<p>Mengkonversi huruf menjadi fixed-width, misalnya Courier</p>
</li>
<li>
<p>Dapat menampilkan screenshots</p>
</li>
<li>
<p>Dapat menampilkan baris yang panjang</p>
</li>
<li>
<p>Tidak memakan tag XML atau HTML</p>
</li>
</ul>
<p>Kode program yang akan ditampilkan tidak jauh dari Java, XML, dan PHP.
Ok, mari kita coba saja.</p>
<p>Berikut kode program Java :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.artivisi;
public class HelloWorld {
public static void main(String[] args) {
try {
System.out.println("This is a very long string, let's see whether Wordpress able to render well");
} catch (IllegalArgumentException err) {
System.out.println("Error ... ");
}
}
}
</code></pre></div></div>
<p>Kemudian build.xml</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><project default="compile" name="test-wordpress">
<target name="compile">
<javac srcdir="src" destdir="bin"></javac>
</target>
</project>
</code></pre></div></div>
<p>Dan, terakhir PHP :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><code>
</code>
</code></pre></div></div>
<p>Semua dibuat hanya dengan membungkus kode program seperti ini :</p>
<pre><code>
.. tulis kode di sini ...
</code></pre>
<p>Let’s see …
Ternyata :</p>
<ol>
<li>
<p>Baris panjang ditampilkan seadanya, ini bisa bagus, bisa juga jelek. Kadangkala kita memang tidak mau memotong baris, karena takut disalah-artikan. Dilain pihak, kalau tidak dipotong, akan merusak tampilan.</p>
</li>
<li>
<p>String langsung diescape dengan backslash. “ ditampilkan menjadi " Very bad. Saya mengerti ini adalah tindakan pencegahan untuk SQL Injection. Tapi seharusnya string dikembalikan seperti semula waktu ditampilkan kembali.</p>
</li>
</ol>
<p>Mas Priyadi pernah membahas tentang <a href="http://priyadi.net/archives/2005/09/27/wordpress-plugin-code-autoescape/">plugin untuk mengatasi masalah ini</a>.</p>
<p>Setelah dipasangi plugin, string kembali seperti semula.
Great work mas Priyadi.</p>
Mail server up and running2005-09-24T14:14:06+07:00https://software.endy.muhardin.com/life/mail-server-up-and-running<p>Postfix akhirnya berhasil dikonfigurasi dengan benar. Sekarang endy at artivisi dot com sudah bisa digunakan lagi.
Tapi sepertinya perlu pasang spam filter, soalnya alamat email endy at artivisi dot com terlanjur tersebar ke para spammer.
Firewall juga sudah aktif. Bagi para pengguna Linux, gunakan Firestarter. Mudah dan cepat.</p>
Rumah baru2005-09-23T21:31:30+07:00https://software.endy.muhardin.com/lain/rumah-baru<p>Setelah bencana alam HDD jebol beberapa waktu lalu, saya berusaha me-restore http://endy.artivisi.com.
Ini salah satu penghuni pertama rumah baru, blog.
Tutorial, presentasi, dan file-file lainnya akan segera menyusul… sabar ya :D</p>