GoF Behavioral: Strategy untuk pricing/discount, Observer via EventEmitter2 dan Laravel Events, Template Method, Command sebagai queue job, Chain of Responsibility via Pipeline Laravel — ditutup checkout engine dengan strategy diskon plus pipeline validasi di 3 stack

Kelompok Behavioral adalah yang paling hidup di aplikasi backend nyata: ia mengatur bagaimana objek berkomunikasi, membagi tanggung jawab alur, dan membuat perilaku bisa ditukar. Lima pattern hari ini menutup katalog GoF series ini — dan semuanya akan langsung terpakai dalam latihan checkout engine.
Keluarga algoritma yang saling dipertukarkan — kita sudah menyentuhnya di OCP (episode 3):
interface DiscountStrategy {
public function apply(int $subtotalCents): int;
}
final class PercentageDiscount implements DiscountStrategy {
public function __construct(private int $percent) {}
public function apply(int $subCents): int {
return $subCents - intdiv($subCents * $this->percent, 100);
}
}
final class FixedDiscount implements DiscountStrategy {
public function __construct(private int $offCents) {}
public function apply(int $subCents): int { return max(0, $subCents - $this->offCents); }
}Satu event, banyak pendengar tanpa saling mengenal:
@Injectable()
export class OrderService {
constructor(@InjectEventEmitter() private emitter: EventEmitter2) {}
place(dto: CheckoutDto) {
const order = this.checkout.place(dto);
this.emitter.emit('order.placed', order); // penerbit tak peduli siapa mendengar
}
}
// Listener terpisah:
@OnEvent('order.placed')
async sendReceipt(e: OrderPlaced) { await this.mailer.receipt(e.order); }dispatch(new SendInvoiceJob($order)) di Laravel).$result = app(Pipeline::class)
->send($checkoutData)
->through([
ValidateStock::class,
ApplyPromo::class,
CheckFraudScore::class,
])
->then(fn ($data) => $this->orders->create($data));Peta pemakaian pattern behavioral di ketiga stack:
| Pattern | NestJS | Fiber | Laravel |
|---|---|---|---|
| Strategy | service interface pricing | strategy interface auth method | policy/strategy via interface |
| Observer | EventEmitter2 | channel pub/sub / callback registry | Events/Listeners |
| Command | CQRS command (ep.20) | command struct + dispatcher | Queue jobs |
| Chain | guard/pipe berlapis | middleware chain | Pipeline |
Target outline: checkout engine dengan strategy diskon + pipeline validasi di 3 stack.
interface ValidationStep {
validate(cart: Cart): Promise<void>; // throw jika gagal
}
@Injectable()
export class CheckoutEngine {
private discounts = new Map<string, DiscountRule>();
register(code: string, rule: DiscountRule) { this.discounts.set(code, rule); }
async process(cart: Cart, steps: ValidationStep[]): Promise<Receipt> {
for (const step of steps) await step.validate(cart); // chain
let total = cart.subtotal();
for (const [code] of cart.promos)
total = this.discounts.get(code)!.apply(total); // strategy
return Receipt.from(total);
}
}Ujian akhir latihan: tambahkan aturan "diskon gratis ongkir jika subtotal di atas 500 ribu" — harus bisa dilakukan hanya dengan mendaftarkan strategy/rule baru tanpa menyentuh CheckoutEngine.
Rangkuman episode ini:
Katalog GoF tuntas! Episode 8 mulai FASE 3 dengan fondasi data modeling: DTO, Entity & Value Object — tiga jenis objek yang sering dicampuradukkan dan jadi sumber utama kode kotor. Sampai jumpa!