Order pada Yuk — Recycle

Endrawan Andika Wicaksana
PPL A-4 YUK RECYCLE
5 min readApr 30, 2019

Halo, perkenalkan nama saya Endrawan Andika Wicaksana, bisa dipanggil Endrawan atau Endra. Pada mata kuliah Proyek Perangkat Lunak (PPL) Fasilkom UI, saya berada pada proyek Yuk — Recycle dan berperan sebagai Hacker.

Pada blog kali ini saya akan memberikan penjelasan implementasi order pada Yuk — Recycle. Blog ini akan mencakup tentang Order Flow, Event Driven Architecture, dan Worker Design Pattern.

Untuk istilah Event Driven Architecture dan Worker Design Pattern mungkin kurang tepat untuk implementasi yang kami dilakukan, apabila terdapat istilah yang lebih cocok bisa komentar pada post ini ya.

Yang perlu saya jelaskan pertama yaitu Order Flow. Order Flow perlu dijelaskan terlebih dahulu karena merupakan masalah yang ingin diselesaikan.

Pada Yuk — Recycle terdapat Order Flow seperti berikut:

Gambar 1. Order Flow

Berikut detail perubahan order status:

  • Ketika sedang mencari mitra, terdapat 3 kemungkinan yaitu tidak ada mitra mengambil order, tidak ada mitra di dekat customer, dan ada mitra mengambil. Ketika terdapat mitra mengambil maka akan lanjut untuk dischedule pada hari sesuai pengaturan pada mitra.
  • Ketika order sudah dischedule, terdapat 3 kemungkinan yaitu dibatalkan mitra, dibatalkan customer, dan akan dilakukan pick up.
  • Ketika order (sampah) akan dipick up, terdapat 3 kemungkinan yaitu dibatalkan mitra, dibatalkan customer, dan penyelesaian order. Pada saat sedang pick up juga dapat dilakukan adjust untuk mengubah detail sampah yang akan dipick up (misalnya menambahkan jumlah sampah botol yang dibawa).

Permasalahan yang harus diselesaikan yaitu:

  1. Bagaimana backend berkomunikasi dengan mitra untuk menentukan mitra mana yang akan diassign ke seorang customer ketika customer membuat order? (mitra dapat menolak tawaran order).
  2. Untuk setiap perubahan order status, harus dilakukan pemberitahuan ke pihak yang lain secara real time (langsung diterima ketika melakukan perubahan) yaitu misalnya mitra akan melakukan pick up maka customer akan tahu bahwa mitra akan melakukan pick up. Bagaimana melakukan hal tersebut?

Perhatikan kasus berikut:

  • Customer membuat order dan semua mitra sedang tidak membuka aplikasi.
  • Mitra melakukan pick up dan customer sedang tidak membuka aplikasi.
  • Aplikasi mitra / customer tidak tahu kapan harus melakukan request ke backend untuk melakukan sesuatu.

Permasalahan yang ada yaitu suatu operasi (misalnya pop up form apakah suatu order akan diterima atau ditolak mitra) dipengaruhi oleh suatu event (misalnya ketika customer melakukan order). Jadi kita butuh Event Driven Architecture.

Solusi Event Driven Architecture yang mungkin dilakukan yaitu:

  1. Buat background service sendiri pada mobile app yang akan melakukan request ke backend pada setiap interval tertentu untuk mengetahui apakah terdapat operasi yang harus dilakukan.
  2. Buat koneksi yang tidak diputuskan ke backend misalnya menggunakan Web Socket.
  3. Menggunakan Push Notification.

Solusi yang kami pilih yaitu nomor 3 (menggunakan push notification) karena lebih mudah (menggunakan library yang sudah ada), lebih efisien, dan karena untuk permasalahan kedua diminta menggunakan push notification (haha :p). Untuk Push Notification kami menggunakan Firebase Cloud Messaging (FCM).

Pada FCM, terdapat 2 tipe pesan yaitu Notification Message dan Data Message. Notification Message adalah pesan yang dilihat oleh user (misalnya seperti aplikasi chat terdapat nama dan pesan terakhir yang masuk pada notification tray). Data Message adalah pesan yang dapat diproses oleh aplikasi.

Karena menggunakan push notification, komunikasi menjadi real time jadi permasalahan kedua terselesaikan. Kami menggunakan Data Message untuk menyelesaikan permasalahan pertama. Implementasi kami yaitu mengirimkan notification (berisikan data order id) ke mitra yang sesuai satu per satu yang akan menampilkan pop up form menerima atau menolak order yang akan melakukan request ke backend. Jadi permasalahan pertama terselesaikan.

Berikut potongan code pada bagian frontend (menerima push notification), menggunakan flutter:

class PushNotification {
final DataMessage dataMessage;

PushNotification({this.dataMessage});

factory PushNotification.fromJson(Map<String, dynamic> data) {
return PushNotification(
dataMessage: DataMessage.fromJson(data['data']),
);
}
}
class DataMessage {
String type;
int orderId;
DataMessage({this.type, this.orderId});

factory DataMessage.fromJson(dynamic data) {
return DataMessage(
type: data['type'],
orderId: int.parse(data['order_id']),
);
}
}
FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
void configureFirebaseCloudMessaging() {
var callback = (Map<String, dynamic> message) => messagingCallback(message);
_firebaseMessaging.configure(
onMessage: callback,
onResume: callback,
onLaunch: callback,
);
}
Future<dynamic> messagingCallback(Map<String, dynamic> message) async {
PushNotification pushNotification = PushNotification.fromJson(message);
DataMessage dataMessage = pushNotification.dataMessage;
String type = dataMessage.type;
if (type == 'RECEIVE_ORDER') {
showPickUpMessage(dataMessage.orderId);
}
}

Sebelum trigger code di atas tentu perlu dilakukan pengiriman fcm token ke backend agar backend mengetahui user mana yang akan dilakukan pengiriman notification. Untuk implementasi kami mengirimkan token ketika user login. Berikut potongan code untuk pengiriman fcm token ke backend:

FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
_firebaseMessaging.getToken().then((token) {
client.post(updateFCMTokenUrl, headers: header, body:json.encode({
'token': token,
}));
});

Berikut potongan code pada bagian backend (mengirim push notification), menggunakan go (dengan http://github.com/NaySoftware/go-fcm):

type NotificationData struct {
ClickAction string `json:"click_action,omitempty"`
TypeData string `json:"type,omitempty"`
OrderID uint `json:"order_id"`
}
func SendNotification(clientTokens []string,
payload *fcm.NotificationPayload,
data *NotificationData) (*fcm.FcmResponseStatus, error) {

fcmClient := fcm.NewFcmClient(os.Getenv("fcm_server_key"))
fcmClient.SetNotificationPayload(payload)
fcmClient.SetMsgData(data)
fcmClient.AppendDevices(clientTokens)
status, err := fcmClient.Send()
return status, err
}
SendNotification(
[]string{mitra.NotificationToken},
&fcm.NotificationPayload{
Title: "Incoming Order",
Body: "Click here to accept or reject the order",
},
&notificationServices.NotificationData{
ClickAction: "FLUTTER_NOTIFICATION_CLICK",
TypeData: "RECEIVE_ORDER",
OrderID: order.ID,
},
)

Walaupun semua permasalahan tentang order sudah diselesaikan, namun masih terdapat permasalahan lain yang perlu dipertimbangkan.

Perhatikan urutan suatu kasus berikut:

  1. Customer membuat order.
  2. Backend mengirim push notification ke satu mitra.
  3. Mitra sedang tidak membuka aplikasi atau sengaja untuk tidak menerima atau menolak order.
  4. Backend mencari mitra lain yang belum pernah dikirimi push notification.
  5. Kembali ke urutan nomor 2 hingga semua mitra yang sesuai dikirimi push notification.

Misalkan urutan nomor 3 ke 4 terjadi ketika mitra tidak merespon selama 30 detik dan terdapat 6 mitra yang harus dikirimi push notification. Maka diperlukan 180 detik atau 3 menit untuk membuat order. Operasi tersebut cukup lama. Apabila melakukan HTTP POST untuk membuat order dan koneksi HTTP terputus maka aplikasi customer tidak mengetahui respon yang didapatkan apakah order yang dibuat berhasil atau tidak ada mitra yang mengambil order.

Solusi yang kami lakukan untuk menyelesaikan masalah tersebut adalah menjalankan operasi 3 menit tersebut di thread yang berbeda dengan thread yang menghandle HTTP Request. Untuk respon yang seharusnya dikirim melalui HTTP Request diganti menjadi dikirimkan menggunakan push notification. Untuk membuat thread baru kami menggunakan go-routine. Untuk operasi yang akan berjalan di thread yang berbeda dengan thread yang menghandle HTTP Request kami namakan sebagai Worker.

Berikut potongan code implementasi Worker:

func Work(makeOrderRequest *MakeOrderRequest) {
go func() {
MakeOrder(makeOrderRequest)
}()
}
func MakeOrderHandler(w http.ResponseWriter, r *http.Request) {
req := decodeMakeOrderRequest(r)
Work(req)
writeEncoded(w, &MakeOrderResponse{
Status: "order has been processed",
})
}

Code di atas walaupun fungsi Work belum selesai namun bisa langsung memberikan HTTP Response dan fungsi Work masih berjalan di background selama fungsi main tidak mati (server backend tidak mati).

Berikut ilustrasi sebelum dan sesudah menggunakan Worker:

Gambar 2. Make Order Sebelum Menggunakan Worker
Gambar 3. Make Order Sesudah Menggunakan Worker

Walaupun sudah menyelesaikan permasalahan dan melakukan pengurangan durasi http request, pendekatan kami masih dapat ditingkatkan lagi misalnya menggunakan Message Queue seperti RabbitMQ dengan membuat Job pada setiap membuat order.

Sekian blog dari saya. Terima kasih sudah membaca.

--

--