# Clean Code Architecture C#: คู่มือฉบับสมบูรณ์และคำถามสัมภาษณ์ 2026 > เรียนรู้ Clean Code Architecture ใน C# พร้อมหลักการ SOLID, Repository Pattern, Dependency Injection และคำถามสัมภาษณ์ยอดนิยมสำหรับนักพัฒนา .NET - Published: 2026-08-24 - Updated: 2026-08-24 - Author: Anthony Fillion-Maillet - Tags: clean-architecture, csharp, solid, dotnet, best-practices - Reading time: 12 min --- 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](https://learn.microsoft.com/en-us/dotnet/fundamentals/) แนะนำ patterns เหล่านี้สำหรับแอปพลิเคชันระดับองค์กร แต่ละหลักการจัดการปัญหาการบำรุงรักษาที่เฉพาะเจาะจง **Single Responsibility Principle (SRP)**: คลาสมีเหตุผลเดียวในการเปลี่ยนแปลง `OrderService` ด้านล่างจัดการเฉพาะการประมวลผลคำสั่งซื้อ มอบหมายการ persist และการแจ้งเตือนให้กับ components แยกต่างหาก ```csharp // OrderService.cs public class OrderService { private readonly IOrderRepository _orderRepository; private readonly INotificationService _notificationService; public OrderService( IOrderRepository orderRepository, INotificationService notificationService) { _orderRepository = orderRepository; _notificationService = notificationService; } public async Task 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)**: คลาสยังคงเปิดสำหรับการขยาย แต่ปิดสำหรับการแก้ไข วิธีการชำระเงินใหม่ต้องการคลาสใหม่ ไม่ใช่การเปลี่ยนแปลงคลาสที่มีอยู่ ```csharp // IPaymentProcessor.cs public interface IPaymentProcessor { string PaymentMethod { get; } Task ProcessAsync(Payment payment); } // StripePaymentProcessor.cs public class StripePaymentProcessor : IPaymentProcessor { public string PaymentMethod => "Stripe"; public async Task 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 _processors; public PaymentService(IEnumerable processors) { _processors = processors; } public async Task 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](https://github.com/jasontaylordev/CleanArchitecture) ให้เทมเพลต .NET ที่ใช้กันอย่างแพร่หลาย แต่ละเลเยอร์มีความรับผิดชอบที่ชัดเจนและ dependencies ไหลเข้าข้างใน | เลเยอร์ | ความรับผิดชอบ | Dependencies | |--------|--------------|-------------| | Domain | Entity, value object, domain event | ไม่มี | | Application | Use case, DTO, interface | Domain | | Infrastructure | Database, API ภายนอก, file system | Application, Domain | | Presentation | Controller, view, API endpoint | Application | ```csharp // Domain/Entities/Customer.cs 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 ```csharp // Program.cs (Minimal API) var builder = WebApplication.CreateBuilder(args); // Domain services - typically transient builder.Services.AddTransient(); // Application services - scoped per request builder.Services.AddScoped(); // Infrastructure - scoped to share DbContext builder.Services.AddScoped(); builder.Services.AddScoped(); // External clients - singleton with HttpClient pooling builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.stripe.com/v1/"); }); // Keyed services for multiple implementations (.NET 8+) builder.Services.AddKeyedScoped("email"); builder.Services.AddKeyedScoped("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 พื้นฐาน ```csharp // Application/Interfaces/IOrderRepository.cs public interface IOrderRepository { Task GetByIdAsync(Guid id, CancellationToken ct = default); Task> 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 GetByIdAsync(Guid id, CancellationToken ct = default) { return await _context.Orders .Include(o => o.Items) .FirstOrDefaultAsync(o => o.Id == id, ct); } public async Task> 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 ## Unit Testing Components ของ Clean Architecture Clean Architecture ทำให้การทดสอบง่ายขึ้นเพราะ dependencies ถูก inject ผ่าน interfaces [xUnit](https://xunit.net/) และ [NSubstitute](https://nsubstitute.github.io/) ให้ testing framework และ mocking library สำหรับ .NET ```csharp // OrderServiceTests.cs public class OrderServiceTests { private readonly IOrderRepository _orderRepository; private readonly INotificationService _notificationService; private readonly OrderService _sut; public OrderServiceTests() { _orderRepository = Substitute.For(); _notificationService = Substitute.For(); _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()); await _notificationService.Received(1) .SendOrderConfirmationAsync(Arg.Is(o => o.Id == order.Id)); } [Fact] public async Task CreateOrderAsync_EmptyItems_ThrowsDomainException() { // Arrange var request = new CreateOrderRequest( CustomerId: Guid.NewGuid(), Items: Array.Empty()); // Act & Assert await Assert.ThrowsAsync( () => _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 สกปรก ```csharp // CachingOrderRepository.cs (Decorator pattern) 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 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(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 มาตรฐานจัดระเบียบโปรเจกต์ตามเลเยอร์ ``` 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 โดยไม่ตั้งใจ ```csharp // Application/Orders/Queries/OrderDto.cs public record OrderDto( Guid Id, Guid CustomerId, IReadOnlyList 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 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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/dotnet/clean-code-architecture-csharp-guide-interview-questions-2026