Request-Response Pattern
Kerja as a team memerlukan pattern yang bagus agar hasil yang dicapai sempurna. Namun, kerja dengan framework tidaklah cukup. Kita juga harus memahami kemampuan setiap anggota tim dalam project.
Untuk membuat aplikasi Android yang kompleks, dibutuhkan 2 developer. Hal ini dengan harapan target tercapai tepat waktu. Namun, perlu diketahui kemampuan dari setiap developer berbeda. Ada yang 70% skill-nya di UI dan ada yang 70% skill-nya di logika. Belum lagi satu dari dua developer tersebut adalah junior yang baru menguasai dasar-dasar Java atau baru mengenal UI. Hal ini menyebabkan ketimpangan beban project.
Bila kita membagi pengerjaan apps based on screen, dengan contoh sebagai berikut:
- screen Login: developer A
- screen Register: developer A
- screen Dashboard: developer B
- screen Detail: developer B
- dst…,
pengerjaan apps akan terjadi hal-hal berikut:
- ada saat ketika saling tunggu
- hasil merge berantakan
- task jadi pilah pilih
- UI dan logika jadi berbeda hasil. Jika developer A mengerjakan UI dan developer B mengerjakan UI asal selesai, yang terjadi kemudian adalah developer A wajib membenahi UI developer B, begitu juga dengan logikanya.
Berkaca dari hal tersebut, GITS Indonesia membagi tim menjadi dua, yaitu Android UI dan Android Logic. So, apa yang terjadi selanjutnya? Hasil UI jadi seragam sama rapi dan bagian Logic jadi seragam sama rapi. Namun, masih terjadi kendala karena kita masih ada waktu tunggu. Ya, waktu tunggu ini hingga UI selesai, baru kemudian bagian Logic bisa dikerjakan.
So, apa yang kami lakukan untuk mengatasi hal tersebut?
Kami membuat pattern yang mendukung kasus tersebut.
Hipotesis dengan Konsep Request-Response
Apa itu Request-Response? Berkaca dari clean-swift.org yang menerapkan VIP cycle, penggunaannya dirasa cukup clean untuk pekerjaan as a team. Maka muncullah ide: bagaimana bila kita menyederhanakan lagi dari clean-swift?
Clean Swift terdiri atas beberapa file:
- View + interface: untuk view
- Present + interface: data yang akan ditampilkan ke view
- Interactor + interface: semua logic API dan lain sebagainya diletakkan di sini
- Route: routing antar-view
- Configurator: config dari semua class
- Worker: digunakan untuk task yang bersifat storage atau background processing (jarang dipakai)
- Model: model yang akan dipakai untuk request dan response
Kemudian, kami berinisiatif melakukan pemangkasan sehingga menjadi berikut:
- View: segala macam view terjadi di sini
- Present: segala macam logic terjadi di sini
- RequestInterface: request method
- ResponseInterface: response method
- Configurator: config semua file dan hanya diinisiasi satu kali
- Model: model yang digunakan untuk request dan response
So, kodenya gimana? Berikut contoh dari base apps
BaseActivity
public abstract class BaseActivity extends AppCompatActivity{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
init();
}
protected abstract @LayoutRes int getLayout();
protected abstract void init();
}
Sebelum ke tahapan selanjutnya, di sini saya membagi menjadi dua task:
UI; scope pengerjaan meliputi:
- XML
- Activity
- Definisi Request
- Definisi Response
- Membuat Present
- Membuat Todo
Logic; scope pengerjaan meliputi:
- Membaca parameter dari Request
- Membuat logic di Present
- Melempar data ke Response untuk ditampilkan di Activity
- Finish Todo
Jika digambarkan dengan diagram, akan seperti berikut:
Konsepnya sederhana. Jika kalian pernah menggunakan HTTP request, ini serupa dengan konsep tersebut. Kita me-request dengan parameter kemudian mendapatkan response, serta tidak tahu betapa rumitnya Logic yang digunakan di backend.
Yang menjadi perhatian adalah:
UI hanya me-request dan menerima hasil akhir. Bukan untuk memproses request dan memproses kembali data yang sudah diproses oleh Logic.
Artinya, segala macam bentuk validasi, HTTP Request, Session, Storage, Logic, Sync Data, dan lain-lain, semua berada di Logic. Jangan sampai salah, ya, guys….
Request dan Response
interface MainRequest extends BaseRequest{
void doPrintNameApps(boolean isMVP);
}
interface MainResponse extends BaseResponse{
void displayNameApps(String nameApps);
}
Request dan Response digunakan sebagai perantara antara UI dan Logic. Untuk pemberian nama method, gunakan kata do sebagai request dan display untuk melakukan response. Tujuannnya agar mudah dibaca dan mudah dimengerti oleh developer. Sangat berguna juga buat test-driven development (TDD).
Ingat, Do dan Display
Activity sebagai View
public class MainActivity extends BaseActivity implements MainResponse {
public MainRequest input;
@Override
protected int getLayout() {
return R.layout.activity_main;
}
@Override
protected void init() {
ConfiguratorActivity.newInstance().configMain(this);
input.doPrintNameApps(!false);
}
@Override
public void onError(String error, Object object) {
Log.e("this error",error);
}
@Override
public void displayNameApps(String nameApps) {
Log.d("this apps",nameApps);
}
}
Activity mengimplementasi (implements) dari interface Response. Tujuannya agar dapat menampilkan hasil yang sudah diproses oleh Logic. Di sini jjuga dideklarasikan variabel input dari interface Request. Tujuannya agar UI dapat me-request logic.
Present sebagai Logic
public class MainPresent implements MainRequest{
public MainResponse output;
private MainPresentExtension controller;
public MainPresent() {
controller = new MainPresentExtension();
}
/**
* TODO As a <type of process>, I want <some process> so that <some return process>.
*/
@Override
public void doSomething(Object object) {
}
/**
* TODO As a <type of process>, I want <some process> so that <some return process>.
* @param isMVP
*/
@Override
public void doPrintNameApps(boolean isMVP) {
if (isMVP) {
output.displayNameApps("IS SUCCESS");
}else{
output.onError("IS ERROR",null);
}
}
}
Present digunakan untuk menampikan request dari UI dan memprosesnya. Present melakukan implement dari interface Request dan mendeklarasikan class Response. Perlu diketahui pula UI diwajibkan menggunakan TODO.
TODO, Gengs.… Iya, TODO.…
Jarang sekali kita menggunakan TODO. Fitur TODO digunakan agar antar-developer bisa saling berkomunikasi. Tutorial penggunaan TODO dapat dilihat di sini: https://www.jetbrains.com/help/idea/2016.2/using-todo.html. Yang menarik adalah penulisan TODO tidak boleh sembarangan. Penulisannya menggunakan metode User Story di scrum management.
TODO As a <type of process>, I want <some process> so that <some return process>.
Contoh:
TODO As a Login Process, I want Request With username & password so that result with UserLoginModel.
Contoh lainnya:
TODO As a Profile User Process, I want display profile user from share preference so that result with UserDao if success & go To Login if fail.
Diharapkan dengan penulisan tersebut dapat memudahkan developer saling berkomunisaksi antara UI dan Logic. Gampang, bukan?
Configurator sebagai Jembatan
public class ConfiguratorActivity{
public static ConfiguratorActivity newInstance;
public static ConfiguratorActivity newInstance() {
if (newInstance == null){
newInstance = new ConfiguratorActivity();
}
return newInstance;
}
public void configMain(MainActivity activity){
MainPresent present = new MainPresent();
activity.input = present;
present.output = activity;
}
}
Configurator digunakan sebagai jembatan atau penghubung antara View, Present, Response, dan Request. Class ini dapat dipergunakan untuk banyak screen atau banyak kasus. Selain itu, bisa juga di-join-kan menggunakan Dagger.
Gampang, bukan?
Penamaan Package
Selama bekerja sebagai developer GITS Indonesia saya menyimpulkan penamaan package menyesuaikan dengan screen. Contoh, untuk halaman Login, penamaan package adalah id.gits.nameapps.login. Di dalam package login nantinya terdiri atas beberapa file, seperti activity; fragment; present, request; response; dan sebagainya yang berhubungan dengan login. Namun, untuk API kami menggunakan module. Dengan penamaan package tersebut, kami dapat dengan mudah menemukan dan mengelompokkan file yang dibutuhkan oleh screen. Kembali lagi, ini masalah selera.
GIT Repo dengan GITFlow
GITFlow adalah mekanisme branching yang memudahkan developer untuk saling berkolaborasi. Bisa dibaca di sini, guys: http://danielkummer.github.io/git-flow-cheatsheet/. Sedangkan untuk tools, saya menggunakan Source Tree dan bila di Linux bisa menggunakan Git Kraken.
GITFlow, Gengs.…
Kembali ke topik, di GITFlow ada branch feature. Untuk konsep ini, penamaan feature harus diatur agar memudahkan developer mengetahui apa saja yang sudah dikerjakan berdasarkan jobdesk-nya. Untuk UI penamaan feature-nya <namafeature>_view dan untuk Logic <namafeature>_logic.
Mudah, bukan, penggunaan pattern Request dan Response? Tapi wait…, ada yang kurang. Apa itu?
- Logic menunggu pekerjaan UI selesai. Hal ini menyebabkan Logic ada waktu tunggu
- Sering terjadi bentrok ketika memberikan data dummy oleh UI. Biasanya terubah oleh Logic.
Solusinya adalah EXTENSION
Whaattt.… Extension? Yups, extension adalah class yang digunakan sebagai perpanjangan tangan dari Present. Tujuannya adalah menghindari waktu tunggu Logic untuk mengerjakan jobdesk-nya dan UI dapat menampilkan data dummy serta meminimalisir bentrok.
Extension sebenernya hanya ada di Swift. Namun, kita bisa menggunakan Class sebagai penggantinya.
public class MainPresentExtension {
public void getNameApps(boolean isResult,MainResponse output){
if (isResult) {
output.displayNameApps("MVP");
}else {
output.onError("NOT MVP",null);
}
}
}
Dan jika Logic sudah selesai mengerjakan task-nya, di Present wajib mengganti proses yang ada di method do dengan method yang ada di class extension. Selain itu, TODO diganti menjadi DONE.
/**
* DONE As a <type of process>, I want <some process> so that <some return process>.
* @param isMVP
*/
@Override
public void doPrintNameApps(boolean isMVP) {
controller.getNameApps(isMVP,output);
}
Dengan demikian, developer Logic dapat mengerjakan logic yang lain sembari UI menyelesaikan pekerjaannya. Begitu juga UI dapat memberikan data dummy sembari menunggu pengerjaan logic selesai.
Mudah, bukan?
Pattern ini bisa digabungkan dengan Data Binding, Rx, Dagger, dan lain-lain. Anda bisa eksplor lebih jauh tentang hipotesis saya ini.
Semoga, tulisan ini dapat bermanfaat bagi developer Indonesia. Dan perlu diingat, tulisan ini masih hipotesis dan masih dalam kajian. Dan, GITS Indonesia sedang mengkaji itu.
Sudaryatno adalah Chief of Technology di GITS Indonesia. Seorang developer senior yang sedang membawa para programmer GITS terus level up tak hanya kemampuan coding-nya, tapi juga dalam membagikan ilmu mereka kepada orang banyak.