Overview
Borabora Content Management System adalah sebuah project yang saya kerjakan di DANA. System ini berupa dashboard yang bisa di kelola oleh admin dan merchant yang ada di Borabora.
Apa sih itu Borabora ?
Buat yang belum tau, Borabora adalah sebuah platform social commerce yg menyediakan layanan Group Buying atau belanja secara group, atau Borong Rame Rame. Jadi kamu dan teman kamu bisa beli barang dengan harga grosir.
Buat kamu yang penasaran, bisa klik link berikut (Android atau IPhone).
Problem Statements
Saat nge-develop sebuah aplikasi, pastinya kita memerlukan sebuah sistem yang bisa melakukan management data. Hal ini dikarenakan akan sangat sulit jika kita melakukan inject kedalam database secara terus menerus. Oleh sebab itu, dibuatlah sebuah dashboard agar tim operations (atau biasa disebut tim Ops) bisa dengan mudah melakukan management data penjualan dengan baik.
Several Things I Did ...
Lumayan banyak hal hal yang saya kerjakan, dan sampai saat ini juga saya masih mengerjakannya. Berikut beberapa hal yang sudah saya kerjakan dan jalan di production untuk saat ini adalah :
1. Versioning
Sebelumnya, versioning pada API dilakukan di routing secara manual. Berikut contohnya :
1package main
2
3func main(){
4 router := fiber.New()
5
6 // lakukan version secara manual
7 router.Get("/v1/products", ...)
8 router.Get("/v1/products/:id", ...)
9 router.Post("/v1/products", ...)
10}Jika kita melakukannya seperti diatas, dan ada perubahan versi, maka developer akan kesulitan melakukan upgrade setiap versi tersebut. Karena kita harus melakukan update secara manual dan akan sulit untuk nge-manage nya.
Oleh sebab itu, saya melakukan beberapa refactoring agar kita bisa melakukan management API Version dengan lebih mudah. Berikut contohnya :
1package main
2
3func main(){
4 router := fiber.New()
5
6 // infrafiber adalah package internal yang saya develop
7 version := infrafiber.NewVersion("products", router)
8
9 // akan running di v1 dan v2
10 version.Run(func(apiWithVersion fiber.Router){
11 apiWithVersion.Get("/", handler.GetProducts)
12 apiWithVersion.Get("/:id", handler.GetDetailProduct)
13 apiWithVersion.Post("/", handler.CreateProduct)
14 }, "v1", "v2")
15
16 // akan running di versi 3
17 version.Run(func(apiWithVersion fiber.Router){
18 apiWithVersion.Post("/", handler.CreateProductV3)
19 }, "v3")
20
21}Pada contoh kode diatas, saya membuat sebuah method yaitu `Run` yang akan di pakai untuk menjalankan API dengan version-nya.
- API Get Products akan running di `v1` dan `v2`
- API Get Detail Product akan running di `v1` dan `v2`
- API Create Product akan running di `v1`, `v2`, dan `v3`. Namun pada version 3, akan ada perbedaan dibanding v1 dan v2 dikarenakan kita telah meng-upgrade versionnya ke yang lebih baru, dan ditandai juga dengan perbedaan handler yang digunakan.
Jadi developer bisa lebih mudah dan dapat menentukan API mana saya yang akan di upgrade ke versi yang lebih tinggi.
Untuk detail implementasinya, akan saya bahas lebih jauh di blog NooBeeID yaa...
2. Improve Search Process
Salah satu fitur menarik di dashboard ini adalah, Search Orders. Saat itu, tim bisnis dan tim product request agar search ordernya bisa di search berdasarkan beberapa field, seperti :
- Product Name
- Transaction Id
- Courier
- Dan lain sebagainya.
Namun, tantangannya adalah proses nya hanya dari 1 input field. Artinya, kita harus bisa menebak, data yang dimasukkan itu akan cocok di Product Name, atau Transaction Id, atau yang lainnya. Dan juga, diminta fitur tersebut bisa melakukan typo tollerance dan full text search :')
Ada 3 solusi yang terpikirkan, yaitu Pertama kita bisa nge developnya dengan menggunakan fitur full-text search di PostgreSQL. Lalu yang kedua, kita bisa menggunakan tools lain seperti Elastic Search untuk membantu process tersebut. Dan yang ke tiga, kita lakukan search manual menggunakan regex (tapi, ini langsung di skip... siapa coba yang mw ngerjainnya pakai regex T_T )
Setelah melakukan riset, akhirnya saya menggunakan solusi ke 2, yaitu menggunakan tools pihak ke 3. Kenapa tidak memanfaatkan postgre nya saja? Sangat ribet menggunakan postgre untuk full-text search, karena kita harus melakukan extract data, dan membentuk data ke sedemikian rupa agar bisa dilakukan search-nya. Dan hasilnya juga kurang memuaskan, karena lumayan costly disisi performance.
Pada solusi ke 2, muncul beberapa opsi yaitu :
- Elastic Search => Ini costly, karena based on Java :) yang lumayan memakan resource.
- Meilisearch => ini lumayan menarik, karena data disimpan di disk, tetapi tetap bisa melakukan query data dengan cepat. Di built dengan rust dan open source
- Typesense => ini juga menarik, namun cost nya lebih tinggi karena data disimpan di RAM sehingga memmory usage nya akan terus meningkat. Di built dengan C++
Setelah melakukan sedikit riset kecil kecilan, maka diputuskannya kalau akan menggunakan Meilisearch sebagai tools utama untuk melakukan searching. Hal ini dilakukan, karena kita ingin menghemat cost sebisa mungkin. Karena data yang akan disimpan di Search Engine tersebut akan sangat banyak (seluruh data order), jadi tentu ini akan sangat berbahaya jika datanya disimpan di RAM (servernya bisa nangis nih). Jika ingin lihat perbandingan Meilisearch dengan yang lain, bisa cek disini yaa
3. Create Refund Process
Fitur ini sebenarnya tidak terlalu mendatangkan revenue ke perusahaan, karena kita mengembalikan uang dari customer. Namun, fitur ini sangat berguna buat operasional dan bisa menambahkan kepercayaan user. Karena masih banyaknya customer yang tidak terlalu mengerti tentang konsep Group Buying, maka mereka terkadang meminta untuk melakukan refund / cancel order.
Pada konsep Group Buy, barang tidak langsung di proses. Barang akan di proses jika campaign untuk melakukan Group Buy tersebut terpenuhi. Misalnya, Borabora menjual barang product A dengan harga Rp. 10.000 dengan syarat, harus dibeli minimal 5 orang dan maksimal quota 10 orang dalam waktu 1 minggu. Artinya, jika kuota tidak mencukupi (dibawah 5) selama 1 minggu, maka Group Buy akan gagal dan uang customer akan di refund kembali.
Nah karena ada proses menunggu sampai kuota terpenuhi, sehingga ada usecase yang menyebabkan customer untuk melakukan cancel order melalui admin borabora. Oleh sebab itu, agar memudahkan admin untuk melakukan refund, maka dibuatlah fitur ini sehingga admin bisa melakukan refund secara full (untuk semua barang dalam 1 order) atau partial (hanya sebagian).
Tantangan dari proses refund ini adalah, untuk bisa mencakup beberapa rules yang sangat flexible. Kita harus bisa memastikan jumlah yang di refund, itu sama dengan yang di bayar oleh customer. Berikut beberapa hal yang harus di perhatikan saat melakukan refund :
- Perhatikan voucher yang digunakan. Borabora mempunyai beberapa rules voucher, seperti Gratis Ongkir, Total Price, Voucher Toko, dan lain sebagainya. Setiap voucher mempunyai rules yang berbeda beda. Sehingga ini bakal bergantung juga dengan proses refund-nya.
- Perhatikan kapan melakukan partial refund, dan kapan melakukan full refund. Pada kasus ini, kita harus bisa memastikan request yang diberikan itu akan menyebabkan proses refund secara partial, atau secara keseluruhan. Misalnya, dari 3 barang yang di order, ternyata ada 1 yg gagal fullfilled dan 2 lagi berhasil. Nah dari 2 tadi, customer memutuskan untuk melakukan refund ke 1 barang. Saat admin melakukan request, maka kita harus bisa memastikan bahwa kita melakukan partial refund, yaitu nge-refund 1 barang saja, tidak full refund. Karena barang 1 lagi fullfilled dan customer tidak meminta untuk di batalkan.
- Mekanisme kalau berhasil / gagal melakukan refund. Karena process refund nya adalah melakukan request ke pihak payment (dalam kasus ini adalah DANA), maka kita harus bisa melakukan mekanisme jika refund-nya berhasil / gagal. Disini kami menggunakan database transaction untuk nge-support hal itu sehingga data yang dihasilkan selalu valid dan konsisten.
4. Setup Monitoring
Salah satu hal menarik yang saya kerjakan adalah menambahkan monitoring. Monitoring yang digunakan adalah NewRelic. Salah satu alasan menggunakan newrelic adalah karena ia mempunyai free tier dan setup nya juga lebih mudah. Newrelic memiliki fitur Application Monitoring (APM) yang cukup lengkap.
Pada fitur ini, kita bisa melihat berapa transaksi yang dilakukan, throughput dan error rate nya. Disini kita juga bisa melihat distributed tracing, sehingga memudahkan kita untuk melihat hal hal yang harus di perbaiki seperti adanya bottleneck, dan lain sebagainya
Selain itu, saya juga membuat custom dashboard yang di dapat dari custom metric yang dikirim oleh backend apps-nya.
Kita bisa mengirim custom event ke newrelic dengan cara :
1// AddMetric implements MonitoringReport.
2func (newrelicMonitoring) RecordEvent(ctx context.Context, serviceName string, usecase string, payload infrafiber.Response) {
3 txn := newrelic.FromContext(ctx)
4
5 eventName := "EventSuccess"
6 eventPayload := map[string]interface{}{
7 "usecase": usecase,
8 "service": serviceName,
9 "httpCode": payload.StatusCode,
10 }
11
12 if payload.ErrorCode != "" {
13 eventName = "EventError"
14 eventPayload["error"] = payload.Error
15 eventPayload["errorCode"] = payload.ErrorCode
16 }
17
18 txn.Application().RecordCustomEvent(eventName, eventPayload)
19}Sehingga kita bisa melihat data data tersebut di dalam dashboard yang kita bikin. Query yang digunakan mirip dengan SQL, namun dengan fitur yang lebih terbatas (disebut dengan NRQL). Salah satu contoh query nya adalah :
1SELECT count(*) AS 'Total Request'
2FROM EventSuccess, EventError
3WHERE appName = 'prod_app'
4 AND usecase = 'products'
5SINCE 1 hour ago EXTRAPOLATEBerikut adalah contoh dari custom dashboard Newrelic.
Dan terakhir yang menarik dari newrelic adalah, kita bisa melakukan monitoring juga terhadap database (dalam kasus ini PostgreSQL). Dengan fitur ini, kita bisa melihat query apa saja yg lambat sehingga memungkinkan untuk melakukan tunning query.
Masih banyak hal hal lain yang saya kerjakan disini, namun point point diatas adalah yang paling menarik yang bisa saya share ke kalian. Mungkin nantinya bakal bertambah seiring berjalannya waktu. Jika ada yang ingin ditanyakan, feel free to DM yaa
- Email : [email protected]
- Linkedin : reyhanjovie