ข้าม​ไป​ยัง​เนื้อหา

เมื่อไร "อย่า" ใช้ Clean Architecture

บท​ที่​แล้ว​ปิด​ท้าย​ด้วย​ประโยค​ที่​ฟัง​ดู​แปลก​สำหรับ​คอร์ส​ที่​ใช้​เวลา​เจ็ด​บท​เต็ม​สอน​วิธี​สร้าง Clean ArchitectureClean Architectureแนว​สถาปัตยกรรม​ของ Robert C. Martin ที่​จัด code เป็น​วงแหวน​ซ้อน​กัน (Entities → Use Cases → Interface Adapters → Frameworks) ภาย​ใต้ Dependency Rule เพื่อ​กัน business rules ออก​จาก UI ฐาน​ข้อมูล และ frameworkArchitecture มา​ตลอด — “ต้นทุน​ของ​โครงสร้าง4 project ports และ ceremony ทั้งหมด​นี้​คุ้ม​ก็​ต่อ​เมื่อ domain ซับซ้อน​พอ” ฟัง​ดูเหมือน​กำลัง​จะ​ถอน​คำ​พูด​ตัวเอง​ใช่​ไหม ไม่ใช่​เลย บท​ปิด​คอร์ส​นี้​ไม่​ได้​มาบอกว่า​เจ็ด​บท​ที่​ผ่าน​มา​ผิด แต่​มา​เติม​สิ่ง​ที่​ไดอะแกรม​วง​แหวนสวยๆ ไม่​เคย​บอกไว้ตรงๆ สัก​ที นั่น​คือ สถาปัตยกรรม​ทุก​แบบ​มี​ต้นทุน และ​งาน​ของ​วิศวกร​ที่​ดี​ไม่ใช่​แค่​รู้​วิธี​สร้าง​มัน แต่​รู้​ด้วย​ว่า​เมื่อไร​ควร “อย่า” สร้าง​มัน

📦 code ตัวอย่าง

บท​นี้​ไม่มี code ใหม่​ใน repo kaen-food-ordering — สิ่ง​ที่​ควร​เปิด​ดู​แทน​คือ2 template โอเพน​ซอร์ส​ที่​พูด​ถึง​ด้าน​ล่าง (Ardalis/CleanArchitecture และ jasontaylordev/CleanArchitecture) ซึ่ง​ทำ​ทุก​อย่าง​ที่​คอร์ส​นี้​สอน​ไว้​ให้​พร้อม​ใช้งาน​จริง​แล้ว

ลอง​นึก​ภาพ feature ที่​เรียบ​ง่าย​ที่สุด​เท่า​ที่​จะ​นึกออก​ได้: หน้า​ตั้ง​ค่า​ร้าน​ที่​ให้​เจ้าของ​ร้าน​แก้ไข “ชื่อ​ร้าน” หนึ่ง​ช่อง ไม่มี​ส่วนลด ไม่มี​การ​คำนวณ ไม่มี invariant ใดๆ ให้​รักษา​เลย​สัก​ข้อ — ถ้า​จะ​ทำ feature นี้​ตาม​โครง4 project ที่​วาง​ไว้​ตั้งแต่​บท​ที่ 1 หน้าตา​ของ​งาน​จะ​เป็น​แบบ​นี้:

  • FoodOrdering.Domain — เพิ่ม entity RestaurantProfile พร้อม method Rename(...) ที่​ไม่มี guard clause อะไร​ให้​เขียนจริงๆ
  • FoodOrdering.Application — เพิ่ม port IRestaurantProfileRepository, command RenameRestaurantCommand, และ handler ที่​ประสาน​สอง​อย่าง​นี้​เข้า​ด้วย​กัน
  • FoodOrdering.Infrastructure — implement EfRestaurantProfileRepository พร้อม EF Core mapping
  • FoodOrdering.Web — endpoint ใหม่, request DTO, response DTO
  • Program.cs — เพิ่ม​บรรทัด DI registration อีก​หนึ่ง​บรรทัด

รวม​แล้ว​แตะ​ราว7 file ข้าม4 project เพื่อ​เปลี่ยน string หนึ่ง​ช่อง เทียบ​กับ Order.Place(...) ใน​บท​ที่ 2 ที่​มี invariant จริง​สี่​ข้อ​ให้​รักษา Dependency RuleDependency Ruleกฎ​เดียว​ของ Clean Architecture: source-code dependency ต้อง​ชี้​เข้า​ด้าน​ใน​เสมอ วงใน​ห้าม​รู้จัก​วงนอก — ใน code .NET แปล​เป็น​ทิศทาง​ของ ProjectReference ระหว่าง projectArchitecture ที่​บังคับ​ให้​วงใน​ไม่รู้จัก​วงนอก​นั้น​คุ้ม​ค่า​มาก​ตอน​มี​กฎ​ธุรกิจ​ให้​ปกป้อง​จาก​การ​รั่วไหล​ของ framework แต่​ตอน​ไม่มี​กฎ​อะไร​ให้​ปกป้อง​เลย — ผลลัพธ์​ของ ceremony ทั้งหมด​นั้น​ก็​แค่​ทำให้​งาน​ที่​ควร​เสร็จ​ใน file เดียว​กลาย​เป็น​งาน​ที่​ต้อง​กระโดด​ข้าม7 file

หลัก​ที่​อธิบาย​สัญชาตญาณ​ข้าง​บน​ได้​ตรง​ที่สุด​ชื่อ YAGNIYAGNI (You Aren't Gonna Need It)อย่า​สร้าง​สิ่ง​ที่​ยัง​ไม่​ต้อง​ใช้ — เตือน​ไม่​ให้​ใส่ abstraction/ชั้น​เกิน​จำเป็น​เพียง​เพราะ 'เผื่อ​ไว้'Architecture ย่อ​จาก “You Aren’t Gonna Need It” — อย่า​สร้าง​สิ่ง​ที่​ยัง​ไม่​ต้อง​ใช้ หลัก​นี้​มัก​ถูก​พูด​ถึง​ใน​ระดับ code (อย่า​เขียน parameter ที่​ยัง​ไม่มี​ใคร​เรียก​ใช้) แต่​มัน​ใช้ได้​กับ​ระดับ​สถาปัตยกรรม​ด้วย: การ​แยก​เป็น4 project ประกาศ port แล้ว​รอ adapter มา implement คือ “การ​ลงทุน​ล่วงหน้า” อย่าง​หนึ่ง มัน​จ่าย​คืน​ก็​ต่อ​เมื่อ​มี​เหตุผล​จริง​ให้​กัน​วงใน​จาก​วงนอก เช่น กฎ​ธุรกิจ​ที่​ซับซ้อน​และ​เปลี่ยน​บ่อย หรือ​ความ​จำเป็น​ต้อง​สลับ adapter ระหว่าง test กับ​ของ​จริง — ถ้า​ไม่มี​สอง​อย่าง​นี้​เลย การ​ลงทุน​นั้น​ก็​เป็น​แค่​ต้นทุน​เปล่าๆ ที่​ไม่​เคย​ได้​ผล​ตอบแทน​คืน​มา

❌ อย่า​ลาก​ทั้ง​สี่​ชั้น​มา​ใช้​กับ feature ที่​ไม่มี​กฎ​ธุรกิจ​เลย
// สมมติแค่ "แก้ไขชื่อร้าน" ในหน้าตั้งค่า — ไม่มี invariant ให้รักษาเลยสักข้อ
// 1) FoodOrdering.Domain
public sealed record RestaurantProfileId(Guid Value); // record wrapper แบบเดียวกับ OrderId (บทที่ 2)
public sealed class RestaurantProfile
{
public RestaurantProfileId Id { get; }
public string Name { get; private set; }
public RestaurantProfile(RestaurantProfileId id, string name)
{
Id = id;
Name = name;
}
public void Rename(string name) => Name = name; // ไม่มี guard clause ให้เขียนจริง ๆ
}
// 2) FoodOrdering.Application — port + command + handler เพื่อ "เปลี่ยนชื่อ" 1 field
public interface IRestaurantProfileRepository
{
Task<RestaurantProfile?> FindAsync(RestaurantProfileId id, CancellationToken ct);
Task SaveAsync(RestaurantProfile profile, CancellationToken ct);
}
public sealed record RenameRestaurantCommand(Guid Id, string Name) : IRequest;
public sealed class RenameRestaurantHandler : IRequestHandler<RenameRestaurantCommand>
{
private readonly IRestaurantProfileRepository _repo;
public RenameRestaurantHandler(IRestaurantProfileRepository repo) => _repo = repo;
public async Task Handle(RenameRestaurantCommand cmd, CancellationToken ct)
{
var profile = await _repo.FindAsync(new RestaurantProfileId(cmd.Id), ct)
?? throw new KeyNotFoundException();
profile.Rename(cmd.Name);
await _repo.SaveAsync(profile, ct);
}
}
// 3) FoodOrdering.Infrastructure — EfRestaurantProfileRepository implement port ด้านบน (ละไว้)
// 4) FoodOrdering.Web — endpoint แปลง HTTP PATCH เป็น RenameRestaurantCommand (ละไว้)
// 7 file 4 project เพื่อเปลี่ยน string หนึ่งช่อง — ไม่มีบรรทัดไหนเลยที่รักษากฎธุรกิจอะไรจริง ๆ

สังเกต​ว่า code ข้าง​บน “ถูก” ทุก​บรรทัด​ใน​แง่​ที่ compile ผ่าน​และ​ตาม​สไตล์​ที่​คอร์ส​นี้​สอน​มา​ตลอด​เป๊ะ — ปัญหา​ไม่​ได้​อยู่​ที่​วิธี​เขียน แต่​อยู่​ที่การ​เลือก​ใช้​เครื่องมือ​ผิด​กับ​ปัญหา นี่​คือ​สิ่ง​ที่ DevIQ เรียก​ว่า Golden Hammer — พอ​ถนัด​สถาปัตยกรรม​แบบ​หนึ่ง​จน​คล่อง ก็​มี​แนวโน้ม​จะ​หยิบ​มัน​มา​ใช้​กับ​ทุก​ปัญหา​ที่​เจอ โดย​ไม่ทัน​หยุด​ถาม​ว่า domain ตรง​หน้า​ต้องการ​มัน​จริง​ไหม RestaurantProfile ไม่ใช่ Order — มัน​ไม่มี​อะไร​ให้ Dependency Rule ปกป้อง

ถ้า Clean Architecture จัด code ตาม “ชั้น​เทคนิค” (Domain, Application, Infrastructure, Web) ทาง​เลือก​ที่​ตรง​ข้าม​กัน​คือ​จัด code ตาม “feature” — แต่ละ feature เป็น1 slice ที่​ตัด​ผ่าน​ทุก​ชั้น​ใน​ตัวเอง ตั้งแต่​รับ request จนถึง​บันทึก​ข้อมูล อยู่​ใน folder เดียวกัน แนวคิด​นี้​เรียก​ว่า Vertical Slice ArchitectureVertical Slice Architectureแนว​จัด code ตาม 'feature' (slice แนว​ตั้ง​ที่​ตัด​ผ่าน​ทุก​ชั้น) แทน​การ​แบ่ง​ตาม​ชั้น​เทคนิค — ทาง​เลือก​ที่​เบา​กว่า​เมื่อ Clean Architecture เริ่ม over-engineerArchitecture ข้อดี​ของ​มัน​คือ feature แต่ละ​อัน​ตัดสิน​ใจ “ความ​หนา” ของ​ตัวเอง​ได้​อิสระ — slice ที่​ซับซ้อน​อย่าง PlaceOrder จะมี domain model, port และ handler แยก​กัน​เหมือน​เดิม​ก็ได้​ถ้า​จำเป็น​จริง ใน​ขณะ​ที่ slice ง่ายๆ อย่าง RenameRestaurant เขียน​จบ​ใน file เดียว​ได้​เลย​โดย​ไม่​ต้อง​ผ่าน4 project:

# Layered (Clean Architecture) — จัดตามชั้นเทคนิค ต้องเปิดหลาย project ต่อ1 feature
FoodOrdering.Domain/
Orders/Order.cs
FoodOrdering.Application/
Orders/PlaceOrderCommand.cs
Orders/PlaceOrderHandler.cs
Orders/IOrderRepository.cs
FoodOrdering.Infrastructure/
Orders/EfOrderRepository.cs
FoodOrdering.Web/
Orders/PlaceOrderEndpoint.cs
# Vertical Slice — จัดตาม feature ทุกอย่างของ feature เดียวอยู่ที่เดียว
Features/
PlaceOrder/
PlaceOrderRequest.cs
PlaceOrderEndpoint.cs
PlaceOrderHandler.cs
Order.cs # domain logic เท่าที่ feature นี้ต้องใช้ ยังรักษา invariant ได้เหมือนเดิม
RenameRestaurant/
RenameRestaurantEndpoint.cs # feature นี้ไม่มีกฎอะไรซับซ้อน จบใน file เดียวได้จริง

จะ​เลือก​แบบ​ไหน​ตอบ​ด้วย​คำถาม​เดียว​กับ​ที่​ใช้​ตัดสิน​ตลอด​คอร์ส​นี้ นั่น​คือ domain ตรง​หน้า​มี​กฎ​ธุรกิจ​ที่​ซับซ้อน​และ​เปลี่ยน​บ่อย​พอ​จะ​คุ้ม​กับ ceremony ไหม:

flowchart TD
  Q1{"domain ซับซ้อน?<br/>กฎธุรกิจเปลี่ยนบ่อย?"}
  Q1 -->|ใช่| CA["Clean Architecture<br/>(วงแหวน + Dependency Rule)"]
  Q1 -->|ไม่ใช่| Q2{"CRUD ล้วน ๆ?<br/>แทบไม่มีกฎธุรกิจ"}
  Q2 -->|ใช่| VS["Vertical Slice<br/>(จัดตาม feature)"]
  Q2 -->|ไม่แน่ใจ| CA
  classDef ca fill:#16a34a,stroke:#065f46,color:#f8fafc;
  classDef vs fill:#f59e0b,stroke:#92400e,color:#1c1917;
  class CA ca;
  class VS vs;

คำ​บรรยาย​ภาพ: คำถาม​แรก​กรอง domain ที่​คุ้ม​กับ ceremony ออก​ไป​ทาง Clean Architecture (เขียว) คำถาม​ที่​สอง​กรอง​ส่วน​ที่​เหลือ — ถ้า​ยัง​ไม่​แน่ใจ​ว่า​กฎ​จะ​ซับซ้อน​ขึ้น​ไหม​ใน​อนาคต ให้​เอียง​ไป​ทาง Clean Architecture ไว้​ก่อน เพราะ​ย้าย​จาก Vertical Slice ไปหา Clean Architecture ทีหลัง​มัก​เจ็บ​กว่า​ตรง​กัน​ข้าม และ​ใน​ระบบ​จริง​สอง​แบบ​นี้​อยู่​ร่วม​กัน​ได้​ใน​โซลูชัน​เดียวกัน — ใช้ Clean Architecture กับ Order แต่​ใช้ Vertical Slice กับ RestaurantProfile ก็ได้ ไม่มี​กฎ​ว่า​ทั้ง​ระบบ​ต้อง​เลือก​แบบ​เดียว​ทั้งหมด

ทีนี้​พอ​ระบบ​เริ่ม​มี​ทั้ง slice ที่​หนา​แบบ Clean Architecture และ slice ที่​บาง​แบบ Vertical Slice ปน​กัน​หลาย10 feature คำถาม​ถัด​ไป​ที่​ตาม​มา​เอง​คือ “แล้ว​จะ​รวม​ทุก feature เหล่า​นี้​ไว้​ใน deploy เมนต์​เดียวกัน​โดย​ไม่​ให้​มัน​พัน​กัน​จน​แยก​ไม่​ออก​ได้​ยังไง” นั่น​คือ​ขอบเขต​ของ​แนวคิด Modular Monolith ที่​จะ​มี​คอร์ส​แยก​มา​พูด​ถึง​เต็มๆ ใน​อนาคต — วาง feature เป็น module อิสระ​ต่อ​กัน​ภายใน deploy เมนต์​เดียว ก่อน​จะ​ตัดสิน​ใจ​แยก​เป็น microservice จริง​ก็​ยัง​ไม่​สาย

ทุก​อย่าง​ที่​คอร์ส​นี้​สอน​มา​ตลอด​เจ็ด​บท — 4 project, Dependency Rule, DIP, CQRS/MediatR, composition root, test ที่​ไม่​ต้อง mock — ไม่ใช่​ของ​ใหม่​ที่​คิด​ขึ้น​เอง​สำหรับ​คอร์ส​นี้ มัน​คือ pattern ที่​ชุมชน .NET ใช้งาน​จริง​มา​นาน และ​มี template โอเพน​ซอร์ส​คุณภาพ​สูง​สอง​ตัว​ที่​รวบรวม​มัน​ไว้​ให้​ครบ​แล้ว พร้อม dotnet new ได้​ทันที:

  • Ardalis/CleanArchitecture โดย Steve “Ardalis” Smith — template ที่​เน้น​ความ​เรียบ​ง่าย​ตรง​ตาม​วงแหวน​ของ Uncle Bob พร้อม specificationSpecificationpattern ห่อ 'กฎ​การ​คัดเลือก/เงื่อนไข' เป็น object นำ​มา​ประกอบ​และ​นำ​กลับ​มา​ใช้​ซ้ำ​ได้ เช่น EligiblePromotionSpec, CancellableOrderSpecTactical Design pattern และ Result pattern แทน​การ​โยน exception เพื่อ​ควบคุม flow
  • jasontaylordev/CleanArchitecture โดย Jason Taylor — template ที่​ครบ​เครื่อง​กว่า มา​พร้อม EF Core, FluentValidation, ตัวอย่าง Angular/React frontend และ pipeline behavior ของ MediatR สำหรับ cross-cutting concern อย่าง logging กับ validation

ทั้ง2 repo เผยแพร่​เป็น dotnet new template ที่​ติดตั้งได้ตรงๆ ด้วย dotnet new install (ดู​คำ​สั่ง​และ version ล่าสุด​ที่​แน่นอน​ได้​จาก README ของ​แต่ละ repo เพราะ​มัน​อัปเดต​บ่อย) มอง​เป็น ทาง​ลัด​สำหรับ​งาน​จริง ไม่ใช่​ทาง​ลัด​สำหรับ​การ​เรียนรู้ — ถ้า​เพิ่ง​อ่าน​คอร์ส​นี้จบใหม่ๆ แนะนำ​ให้​โคลน​ทั้ง2 repo มา​ไล่​อ่าน​โครงสร้าง​เทียบ​กับ​ที่​เพิ่ง​สร้าง​เอง​มา​ตลอด​เจ็ด​บท จะ​เห็น​เลย​ว่า​เหตุผล​เบื้องหลัง​ทุก folder คือ​เหตุผล​เดียว​กับ​ที่​อธิบาย​ไป​ใน​คอร์ส​นี้ ต่าง​กัน​แค่​รายละเอียด​ปลีกย่อย​ของ​แต่ละ​ทีม

คอร์ส​นี้​จบ​ด้วย​การ​สร้าง​โครง​และ​พิสูจน์​ว่า​มัน test ได้ แต่​ยัง​เปิด​ประตู​ค้าง​ไว้​สอง​บาน บาน​แรก​คือ tactical patterns เชิง​ลึก — คอร์ส Patterns, Principles & Practices of DDD ที่​แปล​ไว้​แล้ว​ครบ 26 บท​ได้​ปู​ทฤษฎี​ของ bounded contextBounded Contextขอบเขต​ที่ ubiquitous language และ model ชุด​หนึ่ง​มี​ความหมาย​คง​เส้น​คง​วา เช่น Ordering, Payment, Dispatch ใน domain ฟู้ด​เดลิ​เวอรีStrategic Design, aggregate, domain service และ event sourcing ไว้​แล้ว แต่​คอร์ส​นี้​หยิบ​มา​ใช้​แค่​ส่วน​ที่​จำเป็น​สำหรับ Order เดียว​ใน Bounded Context เดียว คอร์ส​ถัด​ไป​ใน​ซีรีส์ (ยัง​ไม่​เผยแพร่) จะ​พา​ลง​ลึก​กว่า​นั้น — ใช้ tactical pattern เต็ม​ชุด​กับ domain ที่​มี​หลาย Bounded Context คุย​กันจริงๆ

บาน​ที่​สอง​คือ การ test แบบ​เต็ม​รูปแบบ — บท​ที่ 7 แตะ​แค่​ผิว​ของ unit test กับ integration test เป็น​ที​เซอร์ ส่วน​เรื่อง test fixture, contract test ระหว่าง bounded context, mutation testing และ​กลยุทธ์ test MediatR pipeline ทั้ง​เส้น เป็น​เนื้อหา​ที่​ลึก​กว่า​ขอบเขต​ของ​คอร์ส​ที่​เน้น​โครงสร้าง​นี้ คอร์ส​แยก​ที่​ว่าด้วย​การ test โดย​เฉพาะ (ยัง​ไม่​เผยแพร่​เช่น​กัน) จะ​กลับ​มา​ปิด​ช่องว่าง​นั้น

ระหว่าง​รอ​สอง​คอร์ส​นั้น พื้นฐาน​ที่​มี​อยู่​แล้ว — The Clean Architecture, Hexagonal Architecture, และ​คอร์ส DDD ทั้ง​สอง​เล่ม — เพียงพอ​ให้​ลงมือ​สร้าง project จริง​ได้​แล้ว​วัน​นี้

ย้อน​กลับ​ไป​บท​ที่ 1 เรา​เริ่ม​จาก​คำถาม​เดียว: จะ​แปลง​วงแหวน​ของ Uncle Bob ให้​เป็น​โซลูชัน .NET ที่ dotnet build ผ่าน​ได้​ยังไง แล้ว​ไล่​ตอบ​ที​ละ​ชั้น — Domain ที่​รักษา invariant ของ Order เอง (บท​ที่ 2), Application ที่​ประสาน use case ผ่าน CQRS/MediatR และ​รับมือ​กับ​โปรโมชัน​ที่​เปลี่ยน​บ่อย​ด้วย Strategy (บท​ที่ 3), Infrastructure ที่ implement port ด้วย EF Core โดย​ไม่​ให้ domain รู้จัก​มัน​เลย (บท​ที่ 4), Web ที่​เป็น​แค่ adapter บางๆ ตาม pattern REPR (บท​ที่ 5), composition root จุด​เดียว​ที่​ประกอบ​ทุก​อย่าง​เข้า​ด้วย​กัน (บท​ที่ 6) และ test จริง​ที่​พิสูจน์​ว่าการ​กลับ​ทิศ dependency ทั้งหมด​นั้น​คุ้ม​ค่า (บท​ที่ 7) วัน​นี้​เรา​ปิด​ท้าย​ด้วย​บทเรียน​ที่​สำคัญ​ไม่​แพ้​กัน — รู้​ว่า​เมื่อไร​ควร​หยุด​ใช้​สิ่ง​ที่​เพิ่ง​เรียน​มา​ทั้งหมด

ถ้า​นับ​จาก​คอร์ส Event Storming ที่​ค้น​พบ domain ฟู้ด​เดลิ​เวอรี​บน​กำแพง Post-it ผ่าน​คอร์ส DDD ที่​ปู​ทฤษฎี มา​ถึง​คอร์ส​นี้​ที่​แปลง​มัน​เป็น code จริง — เส้นทาง​ที่​เดิน​มา​คือ จาก​กระดาษ​สู่ code ที่​รัน​ได้ ทดสอบ​ได้ และ​รู้​ขอบเขต​ของ​ตัวเอง นั่น​คือ​สิ่ง​ที่​วิศวกร​ที่​ดีสร้างขึ้นจริงๆ ไม่ใช่​แค่ code ที่ compile ผ่าน แต่​คือ​การ​ตัดสิน​ใจ​ที่​มี​เหตุผล​รองรับ​ทุก​จุด รวม​ถึง​การ​ตัดสิน​ใจ​ว่า​จะ “ไม่” ใช้​เครื่องมือ​ที่​เพิ่ง​ชำนาญ​ด้วย ขอบคุณ​ที่​เดินทาง​มา​ครบ​ทั้ง 8 บท — ไป​ลงมือ​สร้าง​ของ​จริง​กัน​ต่อ​ได้​เลย


🔗 อ้างอิง​เพิ่มเติม​ใน DevIQ

เจาะ​ลึก​แนวคิด​ใน​บท​นี้​ต่อ​ได้ที่​คลัง​อ้างอิง DevIQ:

  • YAGNI — หลัก​เต็ม​ว่า​ทำไม​ไม่​ควร​สร้าง​สิ่ง​ที่​ยัง​ไม่​ต้อง​ใช้ พร้อม​สัญญาณ​เตือน​ของ​การ​ละเมิด
  • Golden Hammer — antipattern ของ​การ​ใช้​เครื่องมือ​ที่​ถนัด​กับ​ทุก​ปัญหา​โดย​ไม่​ถาม​ว่า​เหมาะ​จริง​ไหม
  • Vertical Slices — แนวคิด​เต็ม​ของ​การ​จัด feature ตัด​ผ่าน​ทุก​ชั้น พร้อม​ความ​สัมพันธ์​กับ Separation of Concerns

เช็กความเข้าใจ — บทที่ 8

ข้อ 1 / 3

Clean Architecture มีแนวโน้ม 'over-engineer' มากที่สุดกับ domain แบบไหน?