Skip to content

Bagian 10 — Arsitektur & Studi Kasus

Sembilan bagian sebelumnya mengajarkan potongan Mastra: agent, tool, memory, workflow, RAG, dan seterusnya. Masing-masing dijelaskan sendiri-sendiri, dengan contoh kecil yang berdiri sendiri.

Itu jurang yang paling sering membuat orang tersendat. Kamu tahu cara membuat tool. Kamu tahu cara memasang memory. Tapi saat diminta membangun bot WhatsApp untuk 500 pelanggan, pertanyaan pertamanya bukan "bagaimana cara membuat tool" — melainkan:

  • Di mana kode webhook-nya ditaruh?
  • Satu agent untuk semua orang, atau satu per pelanggan?
  • Nomor telepon jadi thread atau resource?
  • Kalau dua orang mengirim pesan bersamaan, apa yang terjadi?
  • Kalau server restart saat ada yang menunggu balasan?

Tidak satu pun dijawab dengan mengetahui API. Semuanya keputusan arsitektur — dan itulah isi bagian ini.

Cara bagian ini bekerja

Lima belas studi kasus, disusun dari yang paling sederhana ke yang paling kompleks. Tiap kasus memuat:

BagianIsinya
MasalahnyaKebutuhan nyata, bukan latihan buatan
Keputusan arsitektur2–4 keputusan menentukan, masing-masing dengan pro dan kontra dan alasan pilihan
Struktur direktoriPohon berkas lengkap, siap disalin
Diagram sequenceAlur permintaan dari masuk sampai keluar
Kode implementasiBerkas per berkas, bukan potongan menggantung
Best practiceCara menata kodenya agar tetap terbaca setelah enam bulan
Kapan pola ini salahBatas pemakaiannya — sama pentingnya

Setiap kasus dirancang untuk bisa kamu tiru langsung. Bukan pseudo-code, bukan "dan seterusnya".

Lima keputusan yang selalu muncul

Sebelum masuk kasus, ini kerangka berpikirnya. Hampir semua desain sistem agent adalah kombinasi jawaban atas lima pertanyaan berikut.

1. Agent atau workflow?

Pertanyaan paling menentukan, dan sudah kamu kenal dari 0.1.

AgentWorkflow
LangkahnyaDiputuskan modelKamu tentukan
HasilnyaBervariasiDeterministik
Cocok untukPercakapan, riset, klasifikasiProses bisnis, transaksi
Diuji denganScorerAssertion biasa

Aturan praktis: kalau salah langkah berarti kerugian uang atau pelanggaran aturan, itu workflow. Kalau salah langkah cuma berarti jawaban kurang bagus, itu boleh agent.

Kebanyakan sistem nyata memakai keduanya: agent di lapisan percakapan, workflow di lapisan eksekusi.

2. Di mana state hidup?

Empat tempat, empat umur yang berbeda:

text
┌──────────────────────────────────────────────────────────┐
│ RequestContext   satu permintaan       userId, tenantId  │
│ Memory (thread)  satu percakapan       riwayat pesan     │
│ Memory (resource) lintas percakapan    preferensi user   │
│ Storage/DB       selamanya             pesanan, booking  │
└──────────────────────────────────────────────────────────┘

Kesalahan paling mahal di sini: menaruh data bisnis di memory. Memory untuk konteks percakapan, bukan untuk sumber kebenaran. Status pesanan tinggal di database; memory hanya boleh mengingat bahwa pengguna pernah menanyakannya.

3. Siapa yang memulai?

PemicuMekanisme MastraKasus
Pengguna mengirim pesanServer + custom route10.3, 10.4
WaktuSchedules (cron)10.6
Peristiwa eksternalSignal providers10.7
Sistem lainMCP / A2A10.13

Sistem yang hanya bereaksi pada pengguna jauh lebih sederhana daripada yang juga bereaksi pada waktu dan peristiwa. Naikkan kerumitan hanya saat memang dibutuhkan.

4. Sinkron atau asinkron?

Batas praktisnya sederhana: kalau pekerjaan bisa lebih lama dari timeout HTTP, ia tidak boleh sinkron.

text
< 30 detik   →  request-response biasa
> 30 detik   →  background task / durable agent
menunggu manusia → workflow suspend
menunggu peristiwa → signal

5. Satu pengguna atau banyak?

Ini yang paling sering diabaikan sampai terlambat, dan paling berbahaya.

Begitu ada pengguna kedua, kamu butuh isolasi: resource yang benar, identitas dari sesi terautentikasi, dan storage yang bisa dibagi antar proses. Menambahkannya belakangan berarti memigrasikan data yang sudah tercampur.

Dibahas tuntas di 10.10.

Tiga topologi dasar

Hampir semua kasus di bagian ini adalah variasi dari tiga bentuk berikut.

Topologi A — Agent tunggal

Paling sederhana. Cocok sampai agent punya ~7 tool atau instruksi masih muat satu layar. Kasus 10.1–10.5.

Topologi B — Agent + Workflow

Agent menangani percakapan dan memutuskan kapan proses dijalankan; workflow memastikan prosesnya benar setiap kali. Kasus 10.8, 10.9.

Topologi C — Supervisor + Subagent

Dipakai saat satu agent kewalahan memegang terlalu banyak keahlian sekaligus. Kasus 10.12.

Struktur direktori yang bertahan

Sebelum kasus pertama, ini pola penataan berkas yang dipakai konsisten di seluruh bagian ini — dan alasannya.

text
src/
├── mastra/
│   ├── agents/           satu berkas per agent
│   ├── tools/            satu berkas per tool
│   ├── workflows/        satu berkas per workflow
│   ├── scorers/          penilai mutu
│   └── index.ts          titik daftar SEMUA komponen
├── domain/               ← logika bisnis, TANPA Mastra
│   ├── booking.ts
│   └── pricing.ts
├── infra/                ← akses dunia luar
│   ├── db.ts
│   └── whatsapp.ts
└── server/               ← route HTTP, webhook, middleware
    └── routes.ts

Tiga aturan yang membuat struktur ini bertahan:

1. domain/ tidak boleh mengimpor apa pun dari Mastra.

Ini aturan terpenting. Logika bisnis — cara menghitung harga, aturan siapa boleh membatalkan booking — harus bisa diuji tanpa memanggil model sama sekali.

UntungRugi
Bisa diuji dengan unit test biasa, cepat dan gratisSatu lapisan tambahan untuk dilewati
Aturan bisnis tidak ikut berubah saat model digantiPerlu disiplin; mudah tergoda memanggil agent langsung
Bisa dipakai ulang di luar konteks agent

2. Tool tipis, domain tebal.

Tool seharusnya cuma penerjemah: validasi input, panggil domain/, bentuk hasilnya. Kalau execute sebuah tool sudah lebih dari 20 baris, logikanya kemungkinan salah tempat.

ts
// ✅ Tool tipis
execute: async ({ bookingId }) => {
  const hasil = await batalkanBooking(bookingId)   // logika ada di domain/
  return hasil.ok
    ? { status: 'berhasil', refund: hasil.refund }
    : { status: hasil.alasan, refund: null }
}

3. index.ts adalah satu-satunya tempat pendaftaran.

Ingat dari 0.2: komponen yang tidak terdaftar tidak dikenali runtime. Menjaga semua pendaftaran di satu berkas membuat "apa saja yang ada di sistem ini" bisa dijawab dengan membuka satu berkas.

Di mana type tinggal?

Pertanyaan yang wajar melihat struktur di atas: kenapa tidak ada direktori types/? Apakah type sebaiknya dipisah dari logika bisnis?

Jawabannya butuh satu pembedaan yang tidak selalu terlihat: di proyek Mastra ada dua jenis "type", dan keduanya punya tempat berbeda.

Dua jenis kontrak

Zod schemaTypeScript type
Berlaku saatRuntimeCompile-time saja
MemeriksaData yang benar-benar datangKode yang kamu tulis
Dipakai diBatas sistem — tool, step, structured outputDi dalam kodemu sendiri
Dikirim ke model?Ya — jadi bagian promptTidak
Hilang saat build?TidakYa

Baris terakhir yang paling menentukan. TypeScript type menguap saat kompilasi — ia tidak ada saat program berjalan. Zod schema tetap ada dan benar-benar memeriksa.

Konsekuensinya sederhana:

Di batas sistem, pakai Zod. Di dalam kodemu, pakai TypeScript type.

Batas sistem itu tempat data masuk dari luar kendalimu: argumen dari model, badan permintaan HTTP, respons API pihak ketiga, baris database.

Kenapa itu mendesak dibahas tuntas di 2.2 — lengkap dengan demonstrasi apa yang rusak kalau batasnya bocor.

Lalu type-nya ditaruh di mana?

Default: berdampingan dengan yang memilikinya. Lihat domain/karyawan.ts di 10.2 — ia mengekspor HasilCutidan ambilSisaCuti() dari berkas yang sama.

Untung colocationRugi
Type dan logikanya berubah bersamaan, cukup satu berkasBerkas sedikit lebih panjang
Tidak ada berkas yang harus selalu diedit berpasanganType tidak terlihat sebagai satu daftar
Impor jelas asalnya

Direktori types/ terpisah punya masalah yang khas: ia jadi berkas yang harus diedit setiap kali apa pun berubah, dan lama-lama menjadi tempat sampah berisi type yang tidak jelas lagi siapa pemiliknya.

Kapan memisahkan type memang benar

Tiga situasi, dan hanya tiga:

SituasiSolusi
Type dipakai 3 modul atau lebih yang tidak saling impordomain/types.ts
Ada impor melingkar yang harus diputusPindahkan type bersama ke berkas ketiga
Type mendeskripsikan kontrak lintas layananBerkas sendiri, bahkan paket sendiri

Di luar itu, colocation menang.

Jangan menulis type dua kali

Kesalahan yang sering muncul justru saat orang mencoba merapikan: mendefinisikan Zod schema dan TypeScript type untuk hal yang sama.

ts
// Dua sumber kebenaran yang bisa tidak sinkron
const bookingSchema = z.object({ nama: z.string(), jumlahOrang: z.number() })

export type Booking = {
  nama: string
  jumlahOrang: number      // lupa diubah saat schema berubah
}

Turunkan salah satu dari yang lain:

ts
// Satu sumber kebenaran
export const bookingSchema = z.object({
  nama: z.string(),
  jumlahOrang: z.number().int().min(1).max(20),
})

export type Booking = z.infer<typeof bookingSchema>

Arah turunannya pun ada aturannya:

ArahKapan
Zod lalu z.inferBentuk itu melewati batas sistem — tool, step, API
TypeScript type sajaBentuk itu hanya hidup di dalam kodemu

Jangan memaksakan Zod untuk tipe internal yang tidak pernah menyeberangi batas. Itu menambah biaya runtime tanpa manfaat.

Menerapkannya pada struktur direktori

text
src/
├── domain/
│   ├── booking.ts        ← type + fungsi bisnis, berdampingan
│   │                        (TypeScript type, tanpa Zod)
│   └── types.ts          ← HANYA type yang dibagi 3+ modul
├── mastra/
│   ├── tools/
│   │   └── proses-booking.ts    ← Zod di sini
│   │                               (batas: model ke kodemu)
│   └── workflows/
│       └── booking-workflow.ts  ← Zod per step
└── server/
    └── routes.ts         ← Zod untuk badan permintaan HTTP
                             (batas: internet ke kodemu)

Perhatikan polanya: Zod muncul di mastra/ dan server/ — tempat data masuk dari luar. domain/ cukup TypeScript type, karena data yang sampai ke sana sudah melewati validasi.

Itu juga alasan sebenarnya kenapa domain/ tidak boleh mengimpor Mastra: ia hidup di dalam batas, bukan di batasnya.

Cara cepat memutuskan

Tanya satu hal: "apakah data ini bisa datang dalam bentuk yang tidak saya duga?"

  • Ya, bisa → batas sistem → Zod, lalu turunkan type-nya dengan z.infer
  • Tidak → di dalam kodemu → TypeScript type saja, taruh berdampingan dengan fungsinya

Peta 15 studi kasus

Diurutkan dari yang paling sederhana. Kolom terakhir menunjukkan materi mana yang diimplementasikan — setiap bab dari Bagian 0 sampai IX muncul minimal sekali.

#KasusTopologiMateri yang dipakai
10.1FAQ Bot InternalABagian 0, 1.1, 1.2, 1.4
10.2Asisten Data EksternalA2.1, 2.2, 2.3
10.3Chatbot WhatsApp CSA3.1, 1.3, 9.2
10.4Bot Telegram PersonalA3.2, 3.3
10.5Helpdesk DokumenA5.1, 5.2, 5.3
10.6Laporan Saham TerjadwalA7.2 schedules
10.7Alert Harga KondisionalA7.2 signals, 1.4
10.8Antrian BookingB4.1, 4.2, 4.3
10.9Approval PengeluaranB4.4, 4.5, 2.3
10.10SaaS Multi-tenantB9.1, 9.3, 1.2 dinamis
10.11Satu Agent, Ribuan PenggunaB7.1, 9.1, 9.4
10.12Riset Panjang & SubagentC7.3, 7.2 goals, 3.4
10.13Integrasi lewat MCPC6.1, 6.2, 6.3
10.14Coding Agent & SandboxC7.4, 6.3 ACP
10.15Platform Siap ProduksiC8.1, 8.2, 8.3, 8.4, 9.4

Verifikasi cakupan terbalik

Dibaca dari arah sebaliknya — tiap bab materi dan kasus yang mengimplementasikannya:

MateriDiimplementasikan di
0.1–0.3 Orientasi10.1
1.1 Agent · 1.2 Instructions10.1, 10.10
1.3 generate/stream10.3
1.4 Structured output10.1, 10.7
2.1 Tool · 2.2 Schema10.2
2.3 Error & approval10.2, 10.9
3.1 Message history10.3
3.2 Working memory · 3.3 Semantic recall10.4
3.4 Observational memory10.12
4.1–4.3 Workflow & state10.8
4.4 Agent di workflow · 4.5 Suspend/resume10.9
5.1–5.3 RAG10.5
6.1 MCP client · 6.2 MCP server10.13
6.3 A2A & ACP10.13, 10.14
7.1 Durable agents10.11
7.2 Goals · Schedules · Signals10.12, 10.6, 10.7
7.3 Subagents & skills10.12
7.4 Sandbox & browser10.14
8.1–8.4 Observability & eval10.15
9.1 Storage10.10, 10.11
9.2 Server & routes10.3
9.3 Auth10.10
9.4 Deployment10.11, 10.15

Tidak ada baris kosong. Setiap yang diajarkan punya tempat dipakai.

Cara memakai bagian ini

Kalau kamu belajar berurutan: baca semuanya dari 10.1. Tiap kasus menambahkan satu-dua konsep baru di atas kasus sebelumnya.

Kalau kamu punya kebutuhan spesifik: langsung ke kasus yang paling mirip lewat tabel di atas, lalu baca kasus yang nomornya lebih kecil kalau ada istilah yang belum jelas.

Kalau kamu ingin membangun sesuatu: ambil kasus terdekat, salin struktur direktorinya, ganti domainnya. Bagian "Kapan pola ini salah" akan memberitahu kalau kasus itu ternyata tidak cocok untukmu.

Kasus ini saling menumpuk

Kasus 10.11 (skala besar) mengasumsikan kamu sudah paham 10.3 (webhook) dan 10.10 (multi-tenant). Kalau langsung meloncat ke sana, prasyaratnya disebutkan di awal tiap kasus.

Lanjut ke mana

Mulai dari yang paling sederhana — 10.1 FAQ Bot Internal.

Materi belajar mandiri. Bukan dokumentasi resmi Mastra.