GoF Structural: Adapter untuk payment gateway lama ke baru, Facade untuk SDK pihak ketiga, Decorator via interceptor NestJS dan middleware, Proxy dan sekilas Composite Bridge — ditutup latihan adapter API pembayaran lama-baru plus facade SDK berat

Kalau Creational menjawab "bagaimana membuat", kelompok Structural menjawab "bagaimana menyusun & menghubungkan": membungkus, menerjemahkan, menyederhanakan, dan melapis perilaku. Empat pattern hari ini — Adapter, Facade, Decorator, Proxy — adalah yang paling sering muncul di framework web modern, sering kali memakai nama lain (interceptor, middleware, manager).
Menerjemahkan antara dua interface yang tidak kompatibel:
// Kontrak milik domain kita (dari episode 4):
interface PaymentGateway {
charge(amountCents: number): Promise<{ id: string }>;
}
// Vendor lama punya bentuk berbeda:
class LegacyMidtransSDK {
pay(rupiah: number, cb: (err: Error | null, trxId?: string) => void): void {}
}
@Injectable()
export class LegacyMidtransAdapter implements PaymentGateway {
constructor(private sdk: LegacyMidtransSDK) {}
async charge(amountCents: number): Promise<{ id: string }> {
return new Promise((resolve, reject) =>
this.sdk.pay(amountCents / 100, (err, trxId) =>
err ? reject(err) : resolve({ id: trxId! })));
}
}Domain tetap bicara PaymentGateway; vendor berganti tanpa mengubah pemanggil.
Menyederhanakan subsistem kompleks di balik satu pintu:
final class ShippingFacade {
public function __construct(private BiteshipSDK $sdk,
private CacheRepository $cache) {}
public function quote(string $origin, string $dest, int $grams): Quote {
$key = "ship:$origin:$dest:$grams";
return $this->cache->remember($key, 3600,
fn () => Quote::fromSdk($this->sdk->getRates($origin, $dest, $grams)));
}
}
// Pemanggil: satu method. Detail cache+SDK+mapping tersembunyi.Catatan penting istilah: Facade pattern (GoF) adalah wrapper penyederhana; Laravel Facades (Cache::get()) adalah static proxy ke service container — mirip nama, mekanisme berbeda.
Decorator menambahkan perilaku dengan membungkus objek ber-interface sama:
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(ctx: ExecutionContext, next: CallHandler): Observable<any> {
const t = Date.now();
return next.handle().pipe(
tap(() => console.log(`${ctx.getHandler().name} ${Date.now() - t}ms`)),
);
}
}
// Request handler kini terbungkus logging TANPA kode handler disentuh.Middleware pada Fiber/Laravel adalah decorator chain yang sama pada level HTTP: request melewati lapisan-lapisan (auth, rate limit, logging) sebelum sampai tujuan. Proxy mirip decorator tapi fokusnya kontrol akses/lazy (mis. lazy DB connection), bukan penambahan fitur; Composite (pohon komposisi) dan Bridge (pisah abstraksi-implementasi dua dimensi) cukup dikenali bentuknya.
Target outline: adapter satu API pembayaran lama→baru + facade untuk SDK berat.
// Adapter: vendor v1 (callback-style) -> kontrak v2 kita
type ChargeResult struct{ ID string }
type GatewayV2 interface { Charge(cents int) (ChargeResult, error) }
type V1Adapter struct{ v1 *OldGatewayV1 }
func (a *V1Adapter) Charge(cents int) (ChargeResult, error) {
res := make(chan struct{ r ChargeResult; e error }, 1)
a.v1.PayAsync(cents/100, func(id string, err error) {
res <- struct{ r ChargeResult; e error }{ChargeResult{id}, err}
})
out := <-res
return out.r, out.e
}
// Facade: sembunyikan retry+signature+logging SDK webhook
type WebhookFacade struct{ client *WebhookClient }
func (f *WebhookFacade) Deliver(event string, data map[string]any) error {
payload := NewWebhook(event).With(data).SignedWith(f.client.secret()).Build()
for attempt := 0; attempt < 3; attempt++ {
if err := f.client.Post(payload); err == nil {
return nil
}
}
return errors.New("webhook delivery failed")
}Checklist verifikasi latihan:
[x] Domain hanya tahu interface baru (grep: tidak ada import SDK mentah di service)
[x] Mapping unit/currency terpusat DI DALAM adapter
[x] Ganti adapter legacy -> mock: service test tetap hijau
[x] Facade: pemanggil butuh maksimal 1-2 method, bukan 10Warning
Jangan bungkus segalanya. Adapter/facade bernilai saat ada batas nyata: vendor eksternal, SDK berat, atau interface legacy. Wrapper tanpa batas jelas hanya menambah indirection.
Rangkuman episode ini:
Episode 7 menutup katalog GoF dengan kelompok paling dinamis: Behavioral — Strategy, Observer, Template Method, Command, Chain of Responsibility — yang menjadi mesin checkout engine latihan kita. Sampai jumpa!