Materi Pembelajaran
Domain Driven Design
Membangun software yang mencerminkan bisnis β bukan asumsi programmer.
ποΈ
Developer membuat sistem yang secara teknis benar, tapi secara bisnis salah. DDD menyelesaikan ini.
ANALOGI β ARSITEK DAN KLIEN
Klien bilang "saya mau rumah yang nyaman". Kalau arsitek langsung bangun tanpa banyak tanya, hasilnya mungkin bagus menurut arsitek tapi salah menurut klien. DDD mengajarkan programmer untuk ngobrol dalam, sering, dan dalam bahasa yang sama dengan bisnis.
π« Masalah Klasik
Fitur tidak sesuai kebutuhan. Nama variabel berbeda dengan istilah bisnis. Komunikasi putus di tengah jalan.
β
Solusi DDD
Bangun software berdasarkan domain β masalah bisnis nyata. Teknologi menyesuaikan, bukan sebaliknya.
π INTI DDD
DDD bukan tentang teknologi. DDD tentang memahami masalah bisnis lalu menerjemahkannya ke dalam kode. DDD dibagi dua sisi: Strategic Design (domain, bounded context, context map) dan Tactical Design (entity, aggregate, event, repository).
2003
Diperkenalkan oleh Eric Evans
Strategic
Domain, Context, Language
Tactical
Entity, Aggregate, Event
Domain = bidang masalah bisnis. Sub-domain = pecahannya yang lebih spesifik.
ANALOGI β RESTORAN
Dapur (memasak), kasir (pembayaran), pelayan (melayani tamu), gudang (stok). Masing-masing adalah sub-domain dengan aturan sendiri, tapi bekerja untuk satu tujuan.
Sub-domain sebuah Restoran
π§βπ€βπ§
Pelayan
Supporting
TIPE 01
Core Domain
Inti bisnis. Keunggulan kompetitif. Harus dibangun sendiri dengan sangat baik.
TIPE 02
Supporting Domain
Mendukung core, bukan yang utama. Bisa custom tapi tak perlu sempurna.
TIPE 03
Generic Domain
Masalah umum, pakai solusi jadi. Contoh: login, email, kalender.
KUNCI
Domain Model
Peta aturan bisnis yang bisa dipahami semua pihak. Bukan kode β tapi representasi konseptual.
Satu kata yang sama, digunakan oleh semua orang β bisnis, manajer, programmer β dengan arti yang sama persis.
π« TANPA UBIQUITOUS LANGUAGE
- Bisnis: "Pelanggan"
- Database: tabel "user"
- Code: variabel "person"
- API: field "client"
- Semua sama, nama beda β bingung!
β
DENGAN UBIQUITOUS LANGUAGE
- Bisnis: "Pelanggan"
- Database: tabel "pelanggan"
- Code: class
Pelanggan - API: field "pelanggan"
- Satu kata, satu makna!
Cara membangun Ubiquitous Language
Ngobrol dengan domain expert
β
Catat istilah penting
β
Sepakati definisi
β
Pakai di kode & dokumen
Jika istilah berbeda antar departemen, mungkin maknanya memang berbeda β dan itu sinyal adanya dua Bounded Context.
Dalam sistem besar, kata yang sama bisa berarti hal berbeda di area berbeda. Bounded Context adalah "pagar" yang memastikan satu kata = satu makna di dalamnya.
ANALOGI β KATA "PESANAN" DI RESTORAN
Di restoran yang sama, kata "pesanan" punya arti berbeda tergantung siapa yang bicara:
π³ Dapur: pesanan = daftar menu yang harus dimasak + instruksi (tanpa sambal, extra pedas)
π§βπ€βπ§ Pelayan: pesanan = nomor meja, nama tamu, status antaran (sudah / belum)
π³ Kasir: pesanan = total harga, metode bayar, kembalian, diskon
π¦ Stok: pesanan = bahan yang terpakai (telur -2, nasi -1 porsi)
Kata sama, data dan makna beda! Bounded Context memberi pagar: di "Dapur", pesanan hanya tentang masak. Di "Kasir", pesanan hanya tentang uang.
Restoran β Bounded Contexts:
π³ Dapur
Menu, Resep, Antrian Masak
π³ Kasir
Harga, Bayar, Kembalian
π¦ Stok
Bahan, Supplier, Restock
π§βπ€βπ§ Pelayan
Meja, Tamu, Antaran
ποΈ Reservasi
Jadwal, Kapasitas
β Ulasan
Rating, Komentar
Satu kata, beda makna per Context:
| Kata |
π³ Dapur |
π³ Kasir |
π§βπ€βπ§ Pelayan |
π¦ Stok |
| "Pesanan" |
Daftar menu + instruksi masak |
Total harga + metode bayar |
Nomor meja + status antar |
Bahan yang terpakai |
| "Item" |
Hidangan yang harus dimasak |
Baris di struk (nama + harga) |
Piring yang harus diantar |
Bahan baku (telur, beras) |
| "Pelanggan" |
Preferensi alergi / rasa |
Nama di invoice / member |
Tamu di meja nomor X |
β (tidak relevan) |
Setiap kolom = satu Bounded Context. Data, aturan, dan makna masing-masing independen.
π HUBUNGAN DOMAIN β BOUNDED CONTEXT
Satu sub-domain (area masalah) biasanya dipetakan ke satu Bounded Context (solusi teknis). Sub-domain "Dapur" diimplementasi di Bounded Context "Dapur" dengan Entity, event, dan aturan bisnisnya sendiri β terisolasi dari konteks lain.
π― Manfaat
Tim Dapur bisa berkembang tanpa mengganggu tim Kasir. Kode lebih bersih. Perubahan di satu konteks tidak merusak yang lain.
πΊοΈ Context Map
Peta hubungan antar Bounded Context. Tipe relasi: Shared Kernel (berbagi model), Customer-Supplier (upstream-downstream), Anti-Corruption Layer (filter data masuk).
Komponen taktis untuk membangun domain model.
01
EntityEntity
Objek dengan identitas unik yang persisten. Walau atributnya berubah, tetap objek yang sama. Contoh: Pesanan #001 berubah status tapi tetap pesanan itu.
02
Value ObjectValue Object
Tanpa identitas. Didefinisikan oleh nilainya saja dan bersifat immutable. Contoh: "Rp 50.000" β dua VO dengan nilai sama dianggap identik.
03
AggregateAggregate & Aggregate Root
Cluster Entity + Value Object sebagai satu consistency boundary. Aggregate Root = satu-satunya entry point. Semua perubahan harus lewat Root.
04
RepositoryRepository
Abstraksi untuk persistence β simpan dan ambil Aggregate. Kode domain tidak perlu tahu detail DB (SQL, NoSQL, file).
05
Domain EventDomain Event
Fakta yang sudah terjadi di domain. Bersifat immutable, past-tense. Contoh: PesananDibuat, PembayaranDiterima.
06
Domain ServiceDomain Service
Operasi bisnis yang melibatkan beberapa Aggregate dan tidak bisa diletakkan di satu Entity. Contoh: TransferUang antara 2 rekening.
Contoh: Sistem Pemesanan Pizza
Entity: Pesanan #001
VO: Harga Rp 80.000
Root: Pesanan (berisi Items)
Repo: PesananRepository
Event: PesananMasukDapur
Service: HitungDiskon
Kode diorganisasi ke dalam lapisan. Logic bisnis terisolasi di pusat (Domain). Dependency mengarah ke dalam.
4 β Interface (API / Controllers)TERLUAR
3 β Infrastructure (DB, Messaging, External)TEKNIS
2 β Application (Use Cases, Orchestration)KOORDINASI
1 β Domain (Entities, Business Rules)INTI β€οΈ
LAYER 01
DomainDomain Layer
Jantung sistem. Entity, VO, Aggregate, Event, Domain Service. Tidak bergantung pada layer lain.
LAYER 02
ApplicationApplication Layer
Mengatur use case. Menerima command/query, memanggil domain, return result. Tidak berisi logic bisnis β hanya orkestrasi.
LAYER 03
InfrastructureInfrastructure Layer
Implementasi teknis: koneksi DB, message broker, email. Mengimplementasi interface dari Domain/Application.
LAYER 04
InterfaceInterface Layer
Titik masuk dari dunia luar: REST API, gRPC, WebSocket. Terjemahkan HTTP request menjadi command/query.
β‘ DEPENDENCY RULE
Dependency selalu mengarah ke dalam. Domain tidak kenal Infrastructure maupun Interface (Dependency Inversion Principle). Ini memungkinkan penggantian DB tanpa mengubah logic bisnis.
Simpan semua event yang terjadi, bukan hanya state terakhir. State direkonstruksi dengan replay.
ANALOGI β BUKU TABUNGAN
Saldo rekening = hasil semua transaksi. Simpan semua "transaksi" (event), saldo (state) bisa dihitung kapan saja dari awal.
ποΈ CRUD TRADISIONAL
- Simpan hanya state terakhir
- Data: { saldo: 500.000 }
- Riwayat hilang
- Sulit di-audit
π EVENT SOURCING
- Simpan semua event
- Setor 1.000.000
- Tarik 300.000
- Transfer 200.000
- Replay β saldo 500.000 β
Command
β
Aggregate
β
Event
β
Apply()
β
Event Store
C# β Aggregate Root + Event Sourcing (CTAS)
public class CaseAggregate : AggregateRoot<CaseAggregateState>
{
public CaseCreatedEvent StartNewCase(ICreateCaseRequest cmd)
{
var @event = new CaseCreatedEvent();
@event.CaseNumber = caseNumber;
@event.TaxpayerIdentifier = cmd.TaxpayerIdentifier;
Apply(@event); // mutate state + store event
return @event;
}
}
β
Keuntungan
β’ Audit trail lengkap
β’ Time-travel debugging
β’ Event replay untuk rebuild state
β’ Cocok untuk keuangan & perpajakan
β οΈ Tantangan
β’ Lebih kompleks dari CRUD
β’ Event store membesar β butuh Snapshot
β’ Event versioning (upcasting)
β’ Read model eventually consistent
Command Query Responsibility Segregation β pisahkan model tulis dari model baca.
βοΈ COMMAND (WRITE)
- Mengubah state
- Validasi bisnis ketat
- Lewat Aggregate & Domain
CreateCaseCommandUpdateTaxpayerCommand
π QUERY (READ)
- Hanya membaca
- Dioptimalkan untuk performa baca
- Bisa pakai Read Model / Projection
GetCaseByIdQuerySearchTaxpayerQuery
Client
β
Command / Query
β
Handler
β
Domain / Read Model
β
Response
π KAPAN PAKAI CQRS?
Cocok ketika: beban baca β« tulis, model baca dan tulis sangat berbeda, butuh scale yang berbeda, atau dikombinasikan dengan Event Sourcing. Untuk CRUD sederhana, CQRS berlebihan.
Mengkoordinasikan proses bisnis multi-langkah lintas service. Jika satu langkah gagal, saga menjalankan compensating action.
ANALOGI β PESAN TIKET PESAWAT
(1) Reserve kursi, (2) potong saldo, (3) kirim tiket email, (4) catat di maskapai. Kalau langkah ke-3 gagal, Saga rollback: uang dikembalikan, kursi dibuka lagi.
π Create
β
π Upload
β
π€ Assign
β
π Update
β
β
Done
C# β MassTransit StateMachine (CTAS)
public class CaseTypeCreationStateMachine :
MassTransitStateMachine<CaseTypeCreationState>
{
public State Creating { get; set; }
public State DocumentUploading { get; set; }
public State Completed { get; set; }
public State Faulted { get; set; }
During(Creating, When(CreationCompleted)
.TransitionTo(DocumentUploading));
}
π Orchestration
Satu koordinator pusat (saga orchestrator) mengatur semua langkah dan mengirim compensating command jika gagal. CTAS pakai ini via MassTransit StateMachine.
π Choreography
Tanpa koordinator pusat. Setiap service dengar event lalu bertindak sendiri. Lebih loose-coupled, tapi lebih sulit di-trace dan di-debug.
Pattern teknis yang umum di proyek DDD skala besar. Semua ditemukan di codebase CTAS.
PATTERN 01
Factory
Aggregate Factory
Membuat instance Aggregate baru dengan identitas unik. Memisahkan logika pembuatan dari Aggregate itu sendiri β sesuai Single Responsibility Principle.
CTAS β CaseAggregateFactory
public class CaseAggregateFactory : ICaseAggregateFactory
{
public CaseAggregate CreateAggregate()
=> new CaseAggregate { AggregateIdentifier = Guid.NewGuid() };
}
PATTERN 02
Snapshot
Snapshot Strategy
Optimasi Event Sourcing. Simpan "foto" state di titik tertentu agar tidak perlu replay ribuan event dari awal. CTAS menggunakan SnapshotStrategy<T> di modul Accounting.
PATTERN 03
Projection
Read Model / Projection
Consumer yang mendengarkan Domain Event lalu membangun model baca yang dioptimasi untuk query. Ini bagian "Q" dari CQRS. Contoh di CTAS: TaxpayerForTaxTypeReadModelEventConsumer.
PATTERN 04
Event Store
Event Store
Penyimpanan khusus event. CTAS punya TransactionEventStore di Accounting dengan operasi: save, replay, snapshot check, dan boxing (archival ke offline storage).
PATTERN 05
Outbox
Outbox Pattern
Event disimpan di tabel outbox bersamaan dengan perubahan data (1 transaksi DB atomik). Background worker mengirim ke message broker. Menjamin konsistensi data & pesan.
PATTERN 06
Lock
Distributed Locking
Mencegah race condition di sistem terdistribusi. CTAS pakai Redis Redlock via ILockHelper.Lock("resource"). Hanya satu proses boleh akses resource secara bersamaan.
PATTERN 07
Validation
Command Validation
Validasi command sebelum masuk domain logic. CTAS pakai FluentValidation β class AbstractValidator<TCommand> di Domain/Commands/Validator/.
PATTERN 08
Gateway
API Gateway
Single entry-point untuk semua request dari client. CTAS menggunakan Ocelot sebagai reverse-proxy yang mengarahkan ke 20+ microservice berdasarkan route config.
π MIDDLEWARE PIPELINE
CTAS menggunakan middleware chain: CorrelationLog (trace distributed request), HMAC (autentikasi antar service), SecurityPayload (enkripsi), dan MassTransit Middleware (message filter & retry).
Core Tax Administration System β implementasi DDD skala besar di .NET 6 untuk sistem perpajakan.
4 Layer
Clean Architecture
Context Map:
π
Registration
Wajib Pajak
Pattern yang Digunakan:
Setiap bounded context diorganisasi:
1-Domain/ β
2-Application/ β
3-Infrastructure/ β
4-Interface/
Folder Structure
Registration/
βββ 1-Domain/ β Commands, Model, Queries, Sagas, Services
βββ 2-Application/ β Consumers, Events, Repositories, Services
βββ 3-Infrastructure/ β DB, External Service calls
βββ 4-Interface/ β REST API (WebApi)
Pattern AggregateRoot<TState>. Semua mutasi lewat Apply(event). State di-maintain oleh generic TState class.
Aggregates di CTAS: CaseAggregate, CaseTypeAggregate, TransactionAggregate, BillingAggregate, PaymentOrderAggregate, NtpnPaymentAggregate.
CaseTypeCreationStateMachine Β· DocumentUploadStateMachine Β· DocumentDeletionStateMachine Β· DeregistrationStateMachine Β· ExOfficioVatRegistrationStateMachine Β· SubmitApprovalBalanceTransferStateMachine Β· PmseDataUpdateApproveStateMachine
Komunikasi antar context via RabbitMQ (MassTransit). Shared.Contracts = 50+ sub-folder kontrak (Accounting, Cases, Payment, Registration, dll) β ini adalah Ubiquitous Language antar service.
Outbox Pattern: event β outbox table (atomik) β worker β RabbitMQ β consumer.
TransactionEventStore di Accounting: save, replay, snapshot check, boxing (archive ke offline storage).
TransactionSnapshotStrategy : SnapshotStrategy<T> β auto-snapshot setiap N event.
AccountingSnapshot : Snapshot β serialized state pada titik tertentu.
Consumer khusus yang build read model dari event stream (CQRS query side):
TaxpayerForTaxTypeReadModelEventConsumer Β· BankDetailReadModelEventConsumer Β· ContactReadModelEventConsumer Β· FamilyMemberReadModelEventConsumer Β· AppointmentReadModelEventConsumer Β· LandAndBuildingReadModelConsumer
Redlock (Redis): ILockHelper.Lock("resource") β mencegah race condition lintas instance.
FluentValidation: AbstractValidator<TCommand> di Domain/Commands/Validator/.
Ocelot API Gateway β single entry point routing ke 20+ microservice.
Middleware Pipeline: CorrelationLog β HMAC β SecurityPayload β handler.
Uji pemahaman tentang konsep DDD. Pilih jawaban yang paling tepat!