This episode covers .NET application architecture: clean architecture and layered architecture, dependency injection with the built-in container, CQRS and the mediator pattern for separating commands and queries, and DDD fundamentals and modular solution organization.

A large codebase keeps growing. Without a clear architecture, every new feature makes the code harder to understand, test, and change. Architecture is how you manage that complexity with discipline.
In .NET, these principles have become common practice: separation of layers, dependency injection, separation of commands and queries, and domain modeling that stays close to the business.
Episode 16 teaches clean architecture and layered architecture, dependency injection, CQRS with a mediator, and the basics of Domain-Driven Design and modular solution organization.
Layered architecture divides an application into layers: Presentation, Application, Domain, and Infrastructure. The main rule: dependencies point inward — inner layers must know nothing about outer layers.
Clean architecture sharpens this by centering everything on the Domain as the most stable core. Details like the database and framework are placed at the edges so they're easy to replace:
Controllers/Api → Application → Domain
\ |
-------- InfrastructureWith this structure, the business rules in the Domain aren't tied to EF Core, ASP.NET Core, or any specific NuGet package. You can test business rules without a database at all.
Dependency injection (DI) makes a class not create its own dependencies, but receive them through the constructor. The built-in .NET container registers and manages the object lifecycle:
interface IPembayaran
{
Task BayarAsync(decimal jumlah);
}
class PaymentGateway : IPembayaran { }
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IPembayaran, PaymentGateway>();
class OrderService
{
private readonly IPembayaran _pembayaran;
public OrderService(IPembayaran pembayaran)
{
_pembayaran = pembayaran;
}
}AddScoped<IPembayaran, PaymentGateway> creates one instance per request. Because OrderService receives IPembayaran in its constructor, it doesn't need to know the concrete implementation — this is what made testing with mocks in episode 10 so easy.
CQRS separates operations that change state (commands) from operations that read state (queries). A mediator connects the two without spreading references between handlers. The MediatR package implements this pattern:
dotnet add src/Toko.App package MediatRrecord BuatProdukCommand(string Nama, decimal Harga) : IRequest<int>;
class BuatProdukHandler : IRequestHandler<BuatProdukCommand, int>
{
public async Task<int> Handle(
BuatProdukCommand request, CancellationToken ct)
{
var produk = new Produk { Nama = request.Nama, Harga = request.Harga };
db.Produk.Add(produk);
await db.SaveChangesAsync(ct);
return produk.Id;
}
}IMediator.Send(new BuatProdukCommand("Kopi", 85_000m)) forwards the command to the registered handler. The calling code only depends on the IMediator abstraction, making each use case a self-contained, easily tested unit.
Domain-Driven Design emphasizes modeling that reflects the business language: entities, value objects, and aggregates. Each large part of the business is separated into a bounded context with its own model and language.
Use one project per layer so dependencies can be enforced by the compiler:
dotnet new sln -n Ordering
dotnet new classlib -n Ordering.Domain -o src/Ordering.Domain
dotnet new classlib -n Ordering.Application -o src/Ordering.Application
dotnet new classlib -n Ordering.Infrastructure -o src/Ordering.Infrastructure
dotnet new webapi -n Ordering.Api -o src/Ordering.Api
dotnet sln Ordering.sln add src/Ordering.Domain src/Ordering.Application \
src/Ordering.Infrastructure src/Ordering.ApiThe dotnet new classlib -n Ordering.Domain command creates a library project without an entry point. Because Application can only reference Domain (not Infrastructure), architecture violations are caught at build time — not in production.
Key takeaways:
In the next episode 17 we build for scale: cloud-native and microservices — microservices and container principles, gRPC and minimal APIs, service discovery and resilience patterns with Polly and MassTransit, and observability patterns in microservices.