Belajar .NET - Advanced Architecture & Patterns
Series/Belajar .NET/Episode 16
Episode 16 of 23

Belajar .NET - Advanced Architecture & Patterns

Episode ini menaikkan level dari kode ke arsitektur: clean architecture, layered architecture, dan modular monolith, mediator dan event-driven design, CQRS dan domain-driven design, serta service composition dan bounded contexts untuk aplikasi enterprise.

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

Pendahuluan

Kode yang benar tidak otomatis menjadi sistem yang terawat. Episode 16 membahas arsitektur: cara menyusun project agar mudah diubah, diuji, dan diskalakan saat tim dan fitur bertambah. Kalian akan mengenal clean architecture, layered architecture, modular monolith, CQRS, dan domain-driven design.

Pola-pola ini bukan aturan kaku, melainkan perangkat berpikir. Kuncinya adalah memisahkan apa yang sering berubah (framework, UI, database) dari inti domain yang stabil — dan menjaga arah dependensi agar tidak melingkar.

Clean Architecture dan Layered Architecture

Lapisan dan Arah Dependensi

Layered architecture membagi aplikasi menjadi Presentation, Application, Domain, dan Infrastructure. Clean architecture menegaskan aturan dependensi: lapisan dalam (Domain) tidak boleh bergantung pada lapisan luar. Arah dependensi menuju ke dalam.

Struktur project yang umum:

Struktur folder berlapis
dotnet new sln -n Order
dotnet new classlib -n Order.Domain -o src/Order.Domain
dotnet new classlib -n Order.Application -o src/Order.Application
dotnet new webapi -n Order.Api -o src/Order.Api
dotnet sln add src/Order.Domain src/Order.Application src/Order.Api

Order.Domain memuat entitas dan aturan bisnis murni. Order.Application memuat use case dan interface. Order.Api menghubungkan keduanya dengan dunia luar. Referensi dibuat satu arah: Application ke Domain, Api ke Application.

Manfaat dan Biaya

Keuntungannya jelas: domain bisa diuji tanpa database atau web server, dan mengganti UI atau database tidak menyentuh logika bisnis. Biayanya adalah kompleksitas awal yang lebih tinggi — jangan terapkan clean architecture penuh pada aplikasi kecil.

Modular Monolith

Satu Aplikasi, Banyak Modul

Modular monolith menggabungkan kesederhanaan monolit dengan pemisahan microservice: satu aplikasi yang dideploy, tetapi dipecah menjadi modul yang mandiri dengan boundary tegas.

Modul dengan folder terpisah
internal class OrderModule
{
    public static void Register(IServiceCollection services)
    {
        services.AddScoped<IOrderService, OrderService>();
    }
}

Setiap modul mendaftarkan layanannya sendiri. Modul berkomunikasi lewat interface internal, bukan memanggil implementasi modul lain secara langsung. Ini menjaga otonomi tanpa biaya operasional microservice — strategi yang baik untuk memulai dan memecah nanti.

Mediator Pattern dan Event-Driven Design

MediatR untuk Komunikasi Terpusat

Mediator memisahkan pemanggil dari handler — mengirim perintah (command) ke handler yang sesuai. Library MediatR adalah implementasi paling populer:

Install MediatR
dotnet add package MediatR

Lalu definisikan command dan handler:

Command dan handler
public record BuatPesananCommand(string Produk, int Qty) : IRequest<int>;
 
public class BuatPesananHandler : IRequestHandler<BuatPesananCommand, int>
{
    public Task<int> Handle(BuatPesananCommand request, CancellationToken ct)
    {
        Console.WriteLine($"{request.Produk} x {request.Qty}");
        return Task.FromResult(1);
    }
}

IMediator.Send(new BuatPesananCommand(...)) meneruskan perintah ke handler yang terdaftar. Kode pengirim tidak tahu handler mana yang menangani — dependensi menjadi longgar dan mudah diganti atau diuji.

Event-Driven Design

Selain command, MediatR mendukung notification untuk event. Saat satu kejadian terjadi, banyak handler dijalankan — misalnya saat pesanan dibuat, kirim email dan update stok. Pengiriman dilakukan dengan IMediator.Publish(event). Ini memisahkan efek samping dari inti logika.

CQRS dan Domain-Driven Design

Pisahkan Baca dan Tulis

CQRS (Command Query Responsibility Segregation) memisahkan model baca dan tulis. Perintah (command) mengubah state; query membaca tanpa efek samping. Dengan command/query handler yang terpisah, masing-masing bisa diskalakan dan dioptimalkan sendiri.

Domain-Driven Design (DDD) berfokus pada pemodelan domain yang kompleks: entitas dengan identitas, value objects yang immutable, aggregate yang menjaga konsistensi, dan repository yang membungkus persistence. DDD paling berguna saat aturan bisnisnya rumit — bukan untuk CRUD sederhana.

Service Composition dan Bounded Contexts

Bounded Context sebagai Boundary

Bounded context membagi domain menjadi subdomain yang terpisah — misalnya Order dan Billing — masing-masing dengan model dan istilahnya sendiri. Komposisi layanan terjadi lewat antarmuka antar context, bukan berbagi class internal.

Komposisi antar context
public interface IBillingClient
{
    Task TagihAsync(int pesananId, decimal total);
}
 
public class BillingClient : IBillingClient
{
    private readonly HttpClient _http;
    public BillingClient(HttpClient http) => _http = http;
}

IBillingClient adalah kontrak antar context yang diekspos oleh modul Billing dan dikonsumsi Order. Saat nanti dipisah menjadi microservice, kontrak ini menjadi API — dengan mengganti implementasi BillingClient dari lokal ke HTTP tanpa mengubah pemakai.

Info

Mulailah sederhana: layered architecture yang rapi lebih baik daripada clean architecture yang setengah jadi. Tambah pola seperti CQRS dan DDD saat kompleksitas domain benar-benar menuntutnya.

Ringkasan Praktik Arsitektur

  • Jaga arah dependensi menuju ke dalam; domain bebas dari framework.
  • Mulai dengan modular monolith sebelum beralih ke microservice.
  • Mediator (MediatR) melonggarkan kopling antar handler.
  • CQRS memisahkan model baca dan tulis.
  • Bounded context mendefinisikan boundary model domain.
  • Kontrak antar modul diekspresikan lewat interface.

Penutup

Inti yang harus dibawa pulang:

  • Clean dan layered architecture menjaga arah dependensi yang stabil.
  • Modular monolith memisahkan modul tanpa biaya microservice.
  • Mediator memisahkan pemanggil dari handler dengan command.
  • Event-driven design memisahkan efek samping dari logika inti.
  • CQRS memisahkan operasi baca dan tulis.
  • Bounded context menetapkan batas model domain yang jelas.

Di episode 17 selanjutnya kita akan membahas messaging dan integration — background dan hosted services, message brokers RabbitMQ, Kafka, dan Azure Service Bus, integrasi gRPC dan REST, serta distributed transactions dan eventual consistency.