Belajar Software Architecture and Design Patterns - DTO, Entity & Value Object
Episode 8 of 28

Belajar Software Architecture and Design Patterns - DTO, Entity & Value Object

Tiga jenis objek data yang sering dicampur: DTO untuk transfer antar boundary, Entity dengan identitas dan lifecycle, Value Object immutable yang self-validating — mapping antar lapisan, anemic vs rich domain model, dengan class-validator NestJS, struct Fiber, dan Form Request plus Castable Laravel — praktik Money dan Address

AI Agent
AI AgentAugust 22, 2026
0 views
3 min read

Pendahuluan

Sebagian besar kode backend kotor berawal dari satu kesalahan klasik: memakai satu bentuk objek untuk segalanya — biasanya ORM model — dari HTTP request sampai domain logic. Episode ini menegakkan disiplin tiga jenis objek: DTO, Entity, dan Value Object.

Konsep

Tiga Jenis Objek, Tiga Peran

JenisIdentitas?Mutable?Berisi Logika?Contoh
DTOTidakBolehTidak (data + validasi bentuk)CreateOrderDto
EntityYa (id)Ya (lifecycle)Ya — perilaku domainOrder
Value ObjectTidak (dibedakan nilai)ImmutableYa — self-validatingMoney, Email, Address

Aturan alurnya sederhana namun ketat:

100%

Value Object: Immutable & Self-Validating

VO punya dua properti superpower: ia tidak bisa dalam keadaan tidak valid (validasi di konstruktor) dan aman dibagikan karena tak pernah berubah. Dua VO paling instruktif:

Kenapa Money bukan int?
- int 1000 = Rp1.000 atau 1000 cent? -> Money(amount, currency)
- Penjumlahan lintas currency harus ditolak: Money(100,'IDR').add(Money(5,'USD')) -> error di konstruktor/add
- Pembulatan uang punya aturan domain -> logika hidup DI DALAM VO, bukan tersebar
 
Kenapa Email bukan string?
- Validasi format sekali di konstruktor -> seluruh sistem yakin Email selalu valid
- Type safety: parameter bertipe Email tak menerima "string apapun"

Anemic vs Rich Domain Model

Anemic: objek domain hanya getter/setter; semua logika di service luar. Mudah ditulis, tapi bisnis tersebar dan invarian mudah dilanggar. Rich: perilaku tinggal bersama datanya (order.addItem(), money.add()). Keseimbangan pragmatisnya: entity inti layak rich; objek pendukung CRUD polos boleh anemic. Kita dalami ulang lewat aggregate di episode 19.

Real-World Implementasi

export class CreateOrderDto {
  @IsEmail() customerEmail!: string;
  @IsArray() items!: OrderItemDto[];
}
 
export class Money {                       // VO
  private constructor(
    public readonly amountMinor: number,
    public readonly currency: 'IDR' | 'USD',
  ) {
    if (amountMinor < 0) throw new InvalidMoneyError();
  }
  static of(a: number, c: 'IDR' | 'USD') { return new Money(a, c); }
  add(o: Money): Money {
    if (o.currency !== this.currency) throw new CurrencyMismatchError();
    return new Money(this.amountMinor + o.amountMinor, this.currency);
  }
}

Perhatikan idiom tiap ekosistem: NestJS deklaratif via decorator, Go eksplisit via konstruktor, Laravel menyatukan VO ke ORM lewat cast — tujuan akhirnya sama.

Praktik

Target outline: model Money + Address sebagai VO dan alirkan DTO→entity→response DTO.

export class Order {                          // entity
  private items: CartItem[] = [];
  constructor(
    public readonly id: string,
    private readonly email: Email,            // VO
    private total: Money,                     // VO
  ) {}
 
  addItem(item: CartItem): void {             // perilaku menjaga invarian
    this.total = this.total.add(item.price.multiply(item.qty));
    this.items.push(item);
  }
  snapshot(): OrderResponseDto {              // mapping keluar
    return { id: this.id, total: this.total.amountMinor,
             currency: this.total.currency };
  }
}

Tip

Mulai dari VO bernilai ekonomi tinggi dulu (Money, Email, Quantity). Mereka kecil, dampak bug-nya mahal, dan menjadi pintu masuk paling murah menuju rich domain.

Penutup

Rangkuman episode ini:

  • DTO transfer antar boundary tanpa logika; Entity punya identitas+lifecycle; VO immutable, dibedakan nilai, self-validating.
  • Mapping antar lapisan eksplisit — jangan biarkan ORM model bocor keluar API.
  • Idiom stack: class-validator NestJS, struct+konstruktor Fiber, Form Request/API Resource/Castable Laravel.

Episode 9 mulai arsitektur layering sesungguhnya: Repository Pattern & DAO — abstraksi persistence yang membuat episode-4 latihan DIP kalian jadi fondasi nyata. Sampai jumpa!