Clean Code Architecture C#: คู่มือฉบับสมบูรณ์และคำถามสัมภาษณ์ 2026

เรียนรู้ Clean Code Architecture ใน C# พร้อมหลักการ SOLID, Repository Pattern, Dependency Injection และคำถามสัมภาษณ์ยอดนิยมสำหรับนักพัฒนา .NET

Clean Code Architecture C# diagram showing layers and dependencies

Clean Code Architecture ใน C# ผสมผสานหลักการ Clean Code ของ Robert C. Martin เข้ากับ Clean Architecture Pattern เพื่อสร้างแอปพลิเคชัน .NET ที่บำรุงรักษาง่าย ทดสอบได้ และขยายขนาดได้ การสัมภาษณ์ทางเทคนิคมุ่งเน้นแนวคิดเหล่านี้มากขึ้นเรื่อยๆ เพราะแสดงให้เห็นว่าผู้สมัครคิดอย่างไรเกี่ยวกับการออกแบบซอฟต์แวร์นอกเหนือจากการทำให้โค้ดทำงานได้

เคล็ดลับการสัมภาษณ์

เมื่อถูกถามเกี่ยวกับ Clean Architecture ผู้สัมภาษณ์คาดหวังให้ผู้สมัครอธิบายกฎ dependency: source code dependencies ชี้เข้าข้างใน ไปยัง policies ระดับสูงกว่า เลเยอร์ Domain ไม่รู้อะไรเกี่ยวกับ Infrastructure ไม่ใช่ในทางกลับกัน

หลักการ SOLID เป็นรากฐานของ Clean Code ใน C#

หลักการ SOLID เป็นกระดูกสันหลังของ Clean Code ใน C# เอกสารทางการของ Microsoft เกี่ยวกับ .NET fundamentals แนะนำ patterns เหล่านี้สำหรับแอปพลิเคชันระดับองค์กร แต่ละหลักการจัดการปัญหาการบำรุงรักษาที่เฉพาะเจาะจง

Single Responsibility Principle (SRP): คลาสมีเหตุผลเดียวในการเปลี่ยนแปลง OrderService ด้านล่างจัดการเฉพาะการประมวลผลคำสั่งซื้อ มอบหมายการ persist และการแจ้งเตือนให้กับ components แยกต่างหาก

OrderService.cscsharp
public class OrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly INotificationService _notificationService;

    public OrderService(
        IOrderRepository orderRepository,
        INotificationService notificationService)
    {
        _orderRepository = orderRepository;
        _notificationService = notificationService;
    }

    public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
    {
        // Validates and creates the domain entity
        var order = Order.Create(request.CustomerId, request.Items);
        
        // Persists through the repository abstraction
        await _orderRepository.AddAsync(order);
        
        // Notifies through a separate service
        await _notificationService.SendOrderConfirmationAsync(order);
        
        return order;
    }
}

Open/Closed Principle (OCP): คลาสยังคงเปิดสำหรับการขยาย แต่ปิดสำหรับการแก้ไข วิธีการชำระเงินใหม่ต้องการคลาสใหม่ ไม่ใช่การเปลี่ยนแปลงคลาสที่มีอยู่

IPaymentProcessor.cscsharp
public interface IPaymentProcessor
{
    string PaymentMethod { get; }
    Task<PaymentResult> ProcessAsync(Payment payment);
}

// StripePaymentProcessor.cs
public class StripePaymentProcessor : IPaymentProcessor
{
    public string PaymentMethod => "Stripe";
    
    public async Task<PaymentResult> ProcessAsync(Payment payment)
    {
        // Stripe-specific implementation
        var charge = await _stripeClient.CreateChargeAsync(payment.Amount);
        return new PaymentResult(charge.Id, charge.Status == "succeeded");
    }
}

// PaymentService.cs
public class PaymentService
{
    private readonly IEnumerable<IPaymentProcessor> _processors;

    public PaymentService(IEnumerable<IPaymentProcessor> processors)
    {
        _processors = processors;
    }

    public async Task<PaymentResult> ProcessPaymentAsync(
        Payment payment, string method)
    {
        var processor = _processors
            .FirstOrDefault(p => p.PaymentMethod == method)
            ?? throw new NotSupportedException($"Payment method {method} not supported");
        
        return await processor.ProcessAsync(payment);
    }
}

การเพิ่มการรองรับ PayPal หมายถึงการเพิ่มคลาส PayPalPaymentProcessor PaymentService ยังคงไม่เปลี่ยนแปลง

สี่เลเยอร์ของ Clean Architecture ใน .NET

Clean Architecture จัดระเบียบโค้ดเป็นเลเยอร์ซ้อนกัน Repository Clean Architecture โดย Jason Taylor ให้เทมเพลต .NET ที่ใช้กันอย่างแพร่หลาย แต่ละเลเยอร์มีความรับผิดชอบที่ชัดเจนและ dependencies ไหลเข้าข้างใน

เลเยอร์ความรับผิดชอบDependencies
DomainEntity, value object, domain eventไม่มี
ApplicationUse case, DTO, interfaceDomain
InfrastructureDatabase, API ภายนอก, file systemApplication, Domain
PresentationController, view, API endpointApplication
Domain/Entities/Customer.cscsharp
public class Customer
{
    public Guid Id { get; private set; }
    public string Email { get; private set; }
    public CustomerStatus Status { get; private set; }

    private Customer() { } // EF Core constructor

    public static Customer Create(string email)
    {
        if (string.IsNullOrWhiteSpace(email))
            throw new DomainException("Email cannot be empty");
        
        return new Customer
        {
            Id = Guid.NewGuid(),
            Email = email.ToLowerInvariant(),
            Status = CustomerStatus.Active
        };
    }

    public void Deactivate()
    {
        if (Status == CustomerStatus.Inactive)
            throw new DomainException("Customer already inactive");
        
        Status = CustomerStatus.Inactive;
    }
}

Entity ของ Domain ห่อหุ้มกฎทางธุรกิจ ตรวจสอบความถูกต้องของ invariants ของตัวเองและแสดง behavior ผ่าน methods ไม่ใช่ setters

ทำไมต้องใช้ Private Setter?

Private setter ป้องกันโค้ดภายนอกจากการทำให้ entity อยู่ในสถานะที่ไม่ถูกต้อง Method Deactivate() บังคับใช้กฎที่ว่าลูกค้าที่ไม่ active แล้วไม่สามารถถูก deactivate ได้อีก Pattern นี้ปรากฏบ่อยในการนำไปใช้งาน Domain-Driven Design และ Clean Architecture

Dependency Injection Patterns สำหรับ Clean Architecture

Dependency Injection (DI) เปิดใช้งานการกลับทิศของ dependency ที่ Clean Architecture ต้องการ .NET 10 มี DI container ในตัวที่รองรับ constructor injection, scoped lifetimes และ keyed services ที่เปิดตัวใน .NET 8

Program.cs (Minimal API)csharp
var builder = WebApplication.CreateBuilder(args);

// Domain services - typically transient
builder.Services.AddTransient<IOrderValidator, OrderValidator>();

// Application services - scoped per request
builder.Services.AddScoped<IOrderService, OrderService>();

// Infrastructure - scoped to share DbContext
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();

// External clients - singleton with HttpClient pooling
builder.Services.AddHttpClient<IPaymentGateway, StripeGateway>(client =>
{
    client.BaseAddress = new Uri("https://api.stripe.com/v1/");
});

// Keyed services for multiple implementations (.NET 8+)
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");

var app = builder.Build();

เลเยอร์ Application กำหนด interfaces เลเยอร์ Infrastructure implement พวกมัน เลเยอร์ Presentation (หรือ composition root) เชื่อมต่อทุกอย่างเข้าด้วยกัน

Repository Pattern กับ Entity Framework Core 9

Repository Pattern abstract การเข้าถึงข้อมูลไว้เบื้องหลัง interfaces ที่เน้น domain EF Core 9 ที่มาพร้อมกับ .NET 10 ให้ ORM พื้นฐาน

Application/Interfaces/IOrderRepository.cscsharp
public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default);
    Task<IReadOnlyList<Order>> GetByCustomerAsync(Guid customerId, CancellationToken ct = default);
    Task AddAsync(Order order, CancellationToken ct = default);
    void Update(Order order);
}

// Infrastructure/Repositories/OrderRepository.cs
public class OrderRepository : IOrderRepository
{
    private readonly ApplicationDbContext _context;

    public OrderRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
    {
        return await _context.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == id, ct);
    }

    public async Task<IReadOnlyList<Order>> GetByCustomerAsync(
        Guid customerId, CancellationToken ct = default)
    {
        return await _context.Orders
            .Where(o => o.CustomerId == customerId)
            .OrderByDescending(o => o.CreatedAt)
            .ToListAsync(ct);
    }

    public async Task AddAsync(Order order, CancellationToken ct = default)
    {
        await _context.Orders.AddAsync(order, ct);
    }

    public void Update(Order order)
    {
        _context.Orders.Update(order);
    }
}

Repository ส่งคืน domain entities ไม่ใช่ DTOs การ mapping ไปยัง DTOs เกิดขึ้นในเลเยอร์ Application เพื่อรักษาความบริสุทธิ์ของ Domain

พร้อมที่จะพิชิตการสัมภาษณ์ .NET แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Unit Testing Components ของ Clean Architecture

Clean Architecture ทำให้การทดสอบง่ายขึ้นเพราะ dependencies ถูก inject ผ่าน interfaces xUnit และ NSubstitute ให้ testing framework และ mocking library สำหรับ .NET

OrderServiceTests.cscsharp
public class OrderServiceTests
{
    private readonly IOrderRepository _orderRepository;
    private readonly INotificationService _notificationService;
    private readonly OrderService _sut;

    public OrderServiceTests()
    {
        _orderRepository = Substitute.For<IOrderRepository>();
        _notificationService = Substitute.For<INotificationService>();
        _sut = new OrderService(_orderRepository, _notificationService);
    }

    [Fact]
    public async Task CreateOrderAsync_ValidRequest_PersistsAndNotifies()
    {
        // Arrange
        var request = new CreateOrderRequest(
            CustomerId: Guid.NewGuid(),
            Items: new[] { new OrderItemDto("SKU-001", 2, 29.99m) });

        // Act
        var order = await _sut.CreateOrderAsync(request);

        // Assert
        await _orderRepository.Received(1).AddAsync(Arg.Any<Order>());
        await _notificationService.Received(1)
            .SendOrderConfirmationAsync(Arg.Is<Order>(o => o.Id == order.Id));
    }

    [Fact]
    public async Task CreateOrderAsync_EmptyItems_ThrowsDomainException()
    {
        // Arrange
        var request = new CreateOrderRequest(
            CustomerId: Guid.NewGuid(),
            Items: Array.Empty<OrderItemDto>());

        // Act & Assert
        await Assert.ThrowsAsync<DomainException>(
            () => _sut.CreateOrderAsync(request));
    }
}

Tests ยืนยัน behavior ไม่ใช่ implementation _sut (system under test) โต้ตอบกับ mocks Assertions ตรวจสอบว่า methods ที่ถูกต้องถูกเรียกด้วย arguments ที่ถูกต้อง

คำถามสัมภาษณ์เกี่ยวกับ Clean Architecture C#

การสัมภาษณ์ทางเทคนิคสำรวจความเข้าใจ Clean Architecture ในหลายระดับ คำถามเหล่านี้ปรากฏในการสัมภาษณ์ .NET ระดับ Senior

กับดักการสัมภาษณ์ที่พบบ่อย

ผู้สมัครมักสับสนระหว่าง Clean Architecture กับสถาปัตยกรรม N-tier ความแตกต่างหลักคือ: ใน Clean Architecture dependencies ชี้เข้าข้างในไปยัง Domain ใน N-tier แต่ละเลเยอร์ขึ้นอยู่กับเลเยอร์ด้านล่าง ทำให้ Domain ขึ้นอยู่กับ Infrastructure

ถาม: Clean Architecture แตกต่างจากสถาปัตยกรรมแบบ layered ดั้งเดิมอย่างไร?

สถาปัตยกรรมแบบ layered ดั้งเดิมมีแต่ละเลเยอร์ขึ้นอยู่กับเลเยอร์ด้านล่าง: Presentation ขึ้นอยู่กับ Business Logic ซึ่งขึ้นอยู่กับ Data Access Clean Architecture กลับสิ่งนี้: Domain ไม่มี dependencies, Application ขึ้นอยู่กับ Domain และ Infrastructure ขึ้นอยู่กับทั้งสอง การกลับทิศนี้หมายความว่าเทคโนโลยีฐานข้อมูลสามารถเปลี่ยนได้โดยไม่ต้องแตะกฎทางธุรกิจ

ถาม: เมื่อใดที่ไม่ควรใช้ Clean Architecture?

Clean Architecture เพิ่ม indirection สำหรับแอปพลิเคชันที่เน้น CRUD โดยไม่มีกฎทางธุรกิจที่ซับซ้อน overhead เกินกว่าประโยชน์ API ง่ายๆ ที่ proxy ตารางฐานข้อมูลโดยตรงไม่ต้องการสี่เลเยอร์ คุณค่าเกิดขึ้นเมื่อความซับซ้อนของ business logic สมควรกับการแยก

ถาม: จัดการ cross-cutting concerns เช่น logging และ caching อย่างไร?

สอง patterns ทำงานได้ดี: decorator pattern และ middleware Caching decorator ห่อ repository interface implement interface เดียวกันขณะเพิ่ม cache logic Logging โดยทั่วไปใช้ middleware หรือ DI interceptor ที่ห่อ service calls โดยไม่ทำให้ business logic สกปรก

CachingOrderRepository.cs (Decorator pattern)csharp
public class CachingOrderRepository : IOrderRepository
{
    private readonly IOrderRepository _inner;
    private readonly IDistributedCache _cache;

    public CachingOrderRepository(
        IOrderRepository inner,
        IDistributedCache cache)
    {
        _inner = inner;
        _cache = cache;
    }

    public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
    {
        var cacheKey = $"order:{id}";
        var cached = await _cache.GetStringAsync(cacheKey, ct);
        
        if (cached is not null)
            return JsonSerializer.Deserialize<Order>(cached);
        
        var order = await _inner.GetByIdAsync(id, ct);
        
        if (order is not null)
        {
            await _cache.SetStringAsync(
                cacheKey,
                JsonSerializer.Serialize(order),
                new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
                },
                ct);
        }
        
        return order;
    }

    // Other methods delegate to _inner
}

ถาม: โครงสร้าง validation ใน Clean Architecture เป็นอย่างไร?

Validation เกิดขึ้นที่สองระดับ Domain validation (invariants) อยู่ใน entities: Order ไม่สามารถมีศูนย์ items Application validation (input validation) อยู่ใน command handlers หรือ validators: request ต้องมี customer ID ที่ถูกต้อง FluentValidation ผสานได้ดีสำหรับ input validation ในขณะที่ domain validation ยังคงอยู่ใน entity constructors และ methods

การจัดระเบียบโค้ดและ Naming Conventions

โครงสร้างโปรเจกต์สื่อสารสถาปัตยกรรม Template Clean Architecture มาตรฐานจัดระเบียบโปรเจกต์ตามเลเยอร์

text
src/
├── MyApp.Domain/
│   ├── Entities/
│   ├── ValueObjects/
│   ├── Events/
│   └── Exceptions/
├── MyApp.Application/
│   ├── Common/
│   │   ├── Interfaces/
│   │   └── Behaviors/
│   ├── Orders/
│   │   ├── Commands/
│   │   ├── Queries/
│   │   └── EventHandlers/
│   └── Customers/
├── MyApp.Infrastructure/
│   ├── Persistence/
│   ├── Services/
│   └── Configuration/
└── MyApp.WebApi/
    ├── Controllers/
    ├── Filters/
    └── Middleware/

Vertical slices (Orders, Customers) จัดกลุ่ม use cases ที่เกี่ยวข้อง การจัดระเบียบนี้ขยายขนาดได้ดีกว่า horizontal slices (Commands, Queries) เมื่อแอปพลิเคชันเติบโต

ข้อพิจารณาด้านประสิทธิภาพใน Clean Architecture

Abstraction มีค่าใช้จ่าย ทุก interface call เพิ่ม indirection แนวปฏิบัติเหล่านี้ลด overhead ขณะรักษาความสามารถในการทดสอบ

ใช้ records สำหรับ DTOs: Records สร้าง implementations ของ Equals และ GetHashCode ที่มีประสิทธิภาพ พวกมันเป็น immutable โดย default ป้องกันการ mutation โดยไม่ตั้งใจ

Application/Orders/Queries/OrderDto.cscsharp
public record OrderDto(
    Guid Id,
    Guid CustomerId,
    IReadOnlyList<OrderItemDto> Items,
    decimal Total,
    DateTime CreatedAt);

public record OrderItemDto(
    string Sku,
    int Quantity,
    decimal UnitPrice);

หลีกเลี่ยง over-abstraction: ไม่ใช่ทุกคลาสที่ต้องการ interface Abstract external dependencies (database, HTTP clients, file system) Domain services ภายในที่มี implementation เดียวไม่ค่อยต้องการ interfaces

Profile ก่อน optimize: Overhead ของ Clean Architecture layers โดยทั่วไปไม่มีนัยสำคัญเมื่อเทียบกับ I/O operations Database query ที่ใช้เวลา 50ms นั้นเกิน microseconds ที่ใช้ใน method dispatch มาก

การประยุกต์หลักการ Clean Code กับ C# Methods

Clean Code มุ่งเน้นที่ readability ในระดับ method และ class แนวปฏิบัติเหล่านี้ใช้ได้โดยไม่คำนึงถึง architectural pattern

Methods ทำสิ่งเดียว: Method ชื่อ ProcessOrderAndSendEmail ละเมิด SRP แยกเป็น ProcessOrder และ SendOrderConfirmation

ชื่อที่มีความหมาย: CalculateOrderTotal สื่อสารเจตนา DoCalculation ไม่สื่อ ชื่อตัวแปรเป็นไปตามกฎเดียวกัน: customerOrders ดีกว่า list

Methods เล็ก: ถ้า method เกิน 20 บรรทัด มันน่าจะทำมากเกินไป แยก helper methods ที่มีชื่ออธิบาย

csharp
// Before: Long method doing multiple things
public decimal CalculateInvoiceTotal(Invoice invoice)
{
    decimal subtotal = 0;
    foreach (var item in invoice.Items)
    {
        subtotal += item.Quantity * item.UnitPrice;
    }
    
    decimal discount = 0;
    if (invoice.Customer.IsPreferred)
    {
        discount = subtotal * 0.1m;
    }
    
    decimal tax = (subtotal - discount) * 0.2m;
    
    return subtotal - discount + tax;
}

// After: Small methods with single responsibilities
public decimal CalculateInvoiceTotal(Invoice invoice)
{
    var subtotal = CalculateSubtotal(invoice.Items);
    var discount = CalculateDiscount(subtotal, invoice.Customer);
    var tax = CalculateTax(subtotal - discount);
    
    return subtotal - discount + tax;
}

private decimal CalculateSubtotal(IEnumerable<InvoiceItem> items)
    => items.Sum(item => item.Quantity * item.UnitPrice);

private decimal CalculateDiscount(decimal subtotal, Customer customer)
    => customer.IsPreferred ? subtotal * 0.1m : 0;

private decimal CalculateTax(decimal taxableAmount)
    => taxableAmount * 0.2m;

เวอร์ชันที่ refactor แล้วอ่านเหมือนสรุปของ business logic แต่ละ helper method สามารถทดสอบแยกได้

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ประเด็นสำคัญของ Clean Code Architecture ใน C#

  • กฎ dependency: code dependencies ชี้เข้าข้างใน จาก Infrastructure ไปยัง Domain ไม่เคยออกข้างนอก
  • หลักการ SOLID แนะนำการออกแบบคลาส: single responsibility, open for extension, Liskov substitution, interface segregation, dependency inversion
  • สี่เลเยอร์แยก concerns: Domain (entities), Application (use cases), Infrastructure (ระบบภายนอก), Presentation (API/UI)
  • Dependency injection เชื่อมต่อเลเยอร์ที่ composition root โดยทั่วไปใน Program.cs
  • Repository pattern abstract การเข้าถึงข้อมูลเบื้องหลัง interfaces ที่เน้น domain ที่ส่งคืน entities ไม่ใช่ DTOs
  • Unit tests ยืนยัน behavior โดย mocking interfaces ตรวจสอบว่า methods ที่ถูกต้องได้รับ arguments ที่ถูกต้อง
  • Clean Code ในระดับ method: methods เล็ก ชื่อที่มีความหมาย single responsibilities
  • ค่าใช้จ่ายด้านประสิทธิภาพของ abstraction โดยทั่วไปไม่มีนัยสำคัญเมื่อเทียบกับ I/O operations; profile ก่อน optimize
  • คำถามสัมภาษณ์สำรวจความเข้าใจกฎ dependency trade-offs และ practical patterns เช่น decorators สำหรับ cross-cutting concerns
ชาเลนจ์ประจำวัน

คุณหาบั๊กใน .NET เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 24 สิงหาคม 2569

แท็ก

#clean-architecture
#csharp
#solid
#dotnet
#best-practices

แชร์

บทความที่เกี่ยวข้อง

Entity Framework Core Performance Optimization

Entity Framework Core: การเพิ่มประสิทธิภาพและแนวทางปฏิบัติที่ดีที่สุดในปี 2026

คู่มือฉบับสมบูรณ์สำหรับการเพิ่มประสิทธิภาพ Entity Framework Core 10 บน .NET 10 เรียนรู้ AsNoTracking, compiled queries, batch updates, split queries และตัวดำเนินการ LeftJoin

.NET 10 new features Native AOT C# 14

.NET 10 ในปี 2026: ฟีเจอร์ใหม่ Native AOT และ C# 14 สำหรับเตรียมสัมภาษณ์งาน

.NET 10 เปิดตัวในฐานะรีลีส long-term support พร้อมการปรับปรุง Native AOT, extension member C# 14, field keyword และ file-based apps คู่มือครบถ้วนครอบคลุมฟีเจอร์ใหม่ การปรับปรุงประสิทธิภาพ และความรู้สำหรับสัมภาษณ์งานสำหรับนักพัฒนา .NET ในปี 2026

คู่มือ Clean Architecture บน .NET และ C#

Clean Architecture บน .NET: คู่มือเชิงปฏิบัติ

เชี่ยวชาญ Clean Architecture บน .NET ด้วย C# เรียนรู้หลักการ SOLID การแยกชั้น และรูปแบบการนำไปใช้สำหรับแอปพลิเคชันที่บำรุงรักษาง่าย