Bila Business Rule Berubah, Architecture Pun Kena Berubah
Published:
Updated:
Recently aku tengah develop satu commission system.
At first glance, nampak macam straightforward.
Ada member.
Member boleh refer member baru.
Bila berjaya refer, dia dapat 20% commission.
Lepas tu ada satu lagi mechanism yang aku panggil stage.
Setiap member akan bermula dari Stage 1. Dalam setiap stage ada dua level:
- L1 boleh isi sehingga 5 member
- L2 boleh isi sehingga 25 member
- total 30 nodes
- bila dah cukup 30 nodes, stage tu complete
- kemudian member akan proceed ke stage seterusnya
Ada 5 stage keseluruhannya.
Simple kan?
Actually, not really.
Sebab bila kita mula implement benda macam ni, yang complicated bukan formula commission.
Yang complicated ialah apa sebenarnya yang kita maksudkan dengan "placement".
Original plan
Dalam design asal, ada dua module yang agak berbeza.
Pertama, referral.
Bila A refer B:
A
│
└── B
A dapat 20% direct referral commission.
Simple.
Kedua ialah stage progression.
Bila B register dan masuk ke system, B akan masuk Stage 1.
Stage 1 ada structure:
L1
┌──────┼──────┐
│ │ │
... ... ...
│
└── sehingga 5 nodes
L2
└── setiap L1 boleh ada 5
sehingga 25 nodes
Bila total L1 + L2 dah cukup 30 nodes, Stage 1 complete.
Kemudian proceed ke Stage 2.
Dan begitu seterusnya.
Implementation pertama
Dalam implementation awal, bila ada registration baru, system akan cari placement berdasarkan siapa yang register / bayar dahulu.
Secara teknikal, lebih kurang macam:
New member
↓
Cari stage yang masih ada slot
↓
Cari position kosong
↓
Place member
So siapa yang sampai dulu, dia dapat slot dulu.
At that point, benda ni nampak okay.
Sebab requirement asal memang macam tu.
Tapi kemudian business rule berubah.
Rule berubah
Requirement baru:
Bila member refer member baru, placement member baru perlu berada dalam sponsor's network.
Bukan lagi random.
Bukan lagi siapa yang kebetulan register dahulu.
Ini lebih masuk akal dari sudut fairness dan relationship dalam network.
Jadi flow baru jadi:
Member A
│
├── Sponsor B
│
└── B perlu diletakkan dalam
network A
Referral commission masih sama.
20%.
Tak berubah.
Yang berubah ialah placement logic.
Dan nampak macam satu perubahan kecil.
Sebenarnya tidak.
Bila test sendiri, baru nampak masalah
Aku mula test system ni secara manual.
Register satu member.
Register lagi satu.
Refer.
Check placement.
Register lagi.
Check stage.
Complete stage.
Repeat.
Aku buat macam ni sebab untuk system yang ada banyak state transition, aku memang suka test manually dulu.
Aku nak tengok sendiri:
registration
→ referral
→ commission
→ placement
→ stage progression
→ next stage
satu per satu.
And then aku jumpa satu scenario yang pada awalnya tak obvious.
Scenario pertama: member masih dalam Stage 1
Contohnya:
Member A
└── Stage 1 ACTIVE
A refer B.
B boleh masuk ke Stage 1 network.
No problem.
Logic boleh cari active Stage 1 yang berkaitan dengan sponsor.
Boleh place.
Everything works.
Scenario kedua: member dah complete Stage 1
Sekarang scenario berubah.
A dah penuhkan Stage 1.
Member A
Stage 1
└── 30 nodes
↓
COMPLETED
A kemudian proceed ke Stage 2.
Member A
Stage 1 → COMPLETED
Stage 2 → ACTIVE
Kemudian A refer member baru, B.
Referral commission?
No problem.
A masih sponsor B.
20% masih boleh calculate.
Tapi sekarang:
B nak diletakkan di mana?
Kalau code kita hanya fikir:
Sponsor
↓
Active Stage
↓
Placement
kita akan cari Stage 1 milik A.
Tapi Stage 1 A dah complete.
Jadi tak jumpa.
Kalau implementation kita terlalu bergantung kepada:
stage.status = active
kita akan sampai kepada satu keadaan pelik:
Member A masih boleh refer orang, tapi orang yang dia refer tak tahu nak diletakkan di mana.
And this is where I realized:
Aku bukan sedang fix satu bug.
Aku sedang berdepan dengan satu business rule yang belum diterjemahkan dengan lengkap ke dalam architecture.
"Member ada dalam stage" tak sama dengan "stage member masih active"
Ini antara perkara paling penting yang aku dapat daripada issue ni.
Sebelum ni, concept aku agak bercampur:
Member
└── Stage
└── Active / Completed
Tapi bila system semakin complicated, kita kena bezakan:
Member's stage history
dengan:
Current active stage
Contohnya:
A
│
├── Stage 1
│ └── COMPLETED
│
├── Stage 2
│ └── ACTIVE
│
└── Stage 3
└── NOT STARTED
Stage 1 masih penting.
Ia bukan "hilang" bila complete.
Ia cuma dah tak menerima placement baru untuk cycle tersebut.
Ini nampak macam benda kecil.
Tapi benda kecil macam inilah yang boleh buat kita jumpa code scattered dekat 5-6 tempat.
Referral dan placement sebenarnya bukan benda yang sama
Ini satu lagi architectural decision yang jadi lebih obvious selepas issue ni.
Bila A refer B:
A → B
itu ialah sponsorship relationship.
Ia menentukan:
- siapa sponsor siapa
- siapa dapat 20% commission
- referral history
Tetapi:
B → placement
ialah benda lain.
Ia menentukan:
- B masuk stage mana
- B berada pada level mana
- siapa parent placement B
- position B
- bila stage complete
Jadi sebenarnya ada dua relationship:
SPONSORSHIP
A
│
└── B
PLACEMENT
A's network
│
├── X
│ ├── Y
│ └── Z
│
└── ...
Sponsor B tak semestinya bermaksud B duduk tepat di bawah A.
Ini penting.
Sebab business requirement baru sebenarnya bukan:
"Put the new member under the sponsor."
Tetapi lebih kepada:
"Place the new member somewhere within the sponsor's eligible network according to the stage placement rules."
That is a much more precise rule.
Business decision vs technical decision
Bila aku sampai dekat sini, aku rasa benda ni perlu didokumentasikan dengan proper.
Bukan sekadar:
// find active stage
dalam code.
Sebab active itu sendiri ialah technical representation kepada satu business decision.
Kita kena jawab:
Apa maksud active?
Adakah:
Member sedang bekerja dalam stage tersebut?
Atau:
Stage tersebut masih menerima placement?
Dua benda ni mungkin nampak sama pada awalnya.
Tapi bila member dah progress ke Stage 2, jawapannya boleh jadi berbeza.
Jadi aku document the rules
Aku mula pecahkan benda ni kepada beberapa bahagian.
Business rule
Contohnya:
- setiap member bermula pada Stage 1
- setiap stage mempunyai 30 nodes
- L1 maksimum 5 nodes
- L2 maksimum 25 nodes
- bila 30 nodes penuh, stage complete
- member boleh proceed ke stage seterusnya
- direct sponsor menerima 20% commission
- referral baru perlu ditempatkan berdasarkan sponsor's network
- placement tidak semestinya sama dengan direct sponsor relationship
Technical rule
Kemudian kita translate business rule tu kepada technical behaviour.
Contohnya:
Registration
↓
Create member
↓
Create sponsorship relationship
↓
Calculate referral commission
↓
Determine placement
↓
Find eligible stage/cycle
↓
Find available node
↓
Create placement
↓
Check completion
↓
Advance stage if required
Bila benda ni ditulis macam ni, architecture jadi lebih mudah difahami.
Ini juga ubah cara aku fikir tentang service
Sebelum ni, bila requirement masih simple, tak ada masalah kalau logic tersebar dekat controller, model, action dan job.
Tapi bila flow dah jadi:
registration
→ referral
→ commission
→ network
→ stage
→ placement
→ progression
aku tak boleh lagi fikir:
"Kat mana nak letak code ni?"
Aku kena fikir:
"Siapa sebenarnya yang bertanggungjawab membuat keputusan ini?"
Contohnya:
Referral
↓
Who is the sponsor?
Commission
↓
How much should sponsor receive?
Placement
↓
Where should new member be placed?
Stage progression
↓
Has current cycle completed?
Next stage
↓
What happens after completion?
Bila setiap decision ada owner yang jelas, system jadi lebih predictable.
Jangan terus patch code
Ini part yang aku rasa paling penting.
Bila jumpa bug macam ni, instinct pertama developer biasanya:
if stage is inactive...
do something else
Tambah satu if.
Lepas tu test.
Jumpa bug lain.
Tambah lagi satu if.
Test lagi.
Kemudian:
if A && B && !C && D...
Lepas beberapa minggu, kita sendiri takut nak sentuh code tu.
Aku cuba elakkan benda ni.
Sebab kalau business rule memang berubah, aku tak nak sekadar patch implementation lama.
Aku nak pastikan model pemikiran system tu betul.
Testing bukan sekadar "boleh register"
Untuk system macam ni, happy path memang tak cukup.
Kalau test hanya:
Register A
Register B
Register C
memang semuanya nampak cantik.
Masalah mula keluar bila state berubah.
Jadi aku test scenario macam:
Member in Stage 1
↓
Refer member
↓
Placement works
Kemudian:
Stage 1 completed
↓
Member moves to Stage 2
↓
Member refers someone
↓
Placement must STILL work
Ini yang menarik.
Sebab bug tersebut bukan muncul ketika registration biasa.
Ia muncul selepas lifecycle state berubah.
Manual testing masih ada tempatnya
Aku tahu sekarang banyak benda boleh automate.
Unit test.
Feature test.
Browser test.
Integration test.
Semua tu penting.
Tapi untuk business system yang complicated, aku masih suka buat manual testing pada critical flow.
Open browser.
Register.
Login.
Refer.
Pay.
Check commission.
Check placement.
Complete stage.
Register another member.
Check again.
Sebab bila aku buat secara manual, aku boleh nampak system behaviour sebagai seorang user.
Dan kadang-kadang benda yang kita nampak dalam browser jauh lebih obvious daripada apa yang kita nampak dalam code.
Especially untuk system yang mempunyai banyak state.
Lepas fix, aku ulang lagi
Lepas issue placement ni selesai, aku tak terus declare:
"Done."
Aku buat lagi beberapa round testing.
Bukan sebab aku suka torture diri sendiri.
Tapi sebab bila kita ubah satu bahagian yang connected dengan:
referral
commission
placement
stage
progression
kita kena assume perubahan itu mungkin affect benda lain.
Jadi aku ulang flow dari awal.
Round 1.
Round 2.
Round 3.
Manual.
Dengan state yang berbeza.
Barulah confidence mula naik.
The system akhirnya jadi lebih stable
Ironically, issue ni sebenarnya buat system jadi lebih baik.
Sebab sebelum issue ni muncul, implementation mungkin "working".
Tetapi kita belum betul-betul define apa yang berlaku apabila:
Stage 1 → completed
Stage 2 → active
Member → refer new member
Sekarang behaviour itu dah jelas.
Bukan sahaja code tahu apa nak buat.
Documentation pun tahu apa nak buat.
Developer lain nanti boleh faham.
Dan yang paling penting, business owner pun boleh validate:
"Ya, memang itu behaviour yang kita expect."
Ini yang aku belajar
Bila kita develop custom system, business rule jarang kekal 100% sama dari awal sampai akhir.
Kadang-kadang client ubah requirement.
Kadang-kadang kita jumpa scenario yang tak pernah dibincangkan.
Kadang-kadang bila system dah boleh digunakan, baru nampak behaviour sebenar yang diperlukan.
Itu normal.
Yang penting ialah bagaimana kita respond kepada perubahan itu.
Bagi aku, ada beza besar antara:
"Aku kena modify code sebab requirement berubah."
dengan:
"Aku kena faham semula business rule supaya architecture system boleh represent rule tersebut dengan betul."
Yang pertama menghasilkan patch.
Yang kedua menghasilkan system yang lebih matang.
Dan dalam case commission system ni, aku rasa issue placement tadi bukan sekadar bug.
Ia memaksa aku untuk define dengan lebih jelas:
- apa itu referral
- apa itu sponsorship
- apa itu placement
- apa itu stage
- apa maksud active
- apa maksud completed
- bagaimana stage progression berlaku
- dan apa yang patut berlaku bila member yang dah advance masih refer member baru
Bila semua benda tu dah jelas, baru code jadi lebih senang untuk diurus.
Kadang-kadang bug bukan datang sebab code kita salah.
Kadang-kadang bug datang sebab business rule kita sendiri belum cukup jelas.
Dan bila kita jumpa benda macam ni, mungkin masa tu bukan untuk tambah lagi satu if.
Mungkin masa tu kita kena berhenti sekejap, tulis balik rules, dan tengok semula architecture.
