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

The Clean Architecture: วงแหวน​ที่​ปกป้อง business rules จาก​กรอบ​งาน​และ​รายละเอียด

เกี่ยว​กับ​บท​แปล​นี้

บทความ​นี้​เป็น บท​แปล​ภาษา​ไทย​อย่าง​ไม่​เป็น​ทางการ ของ “The Clean Architecture” โดย Robert C. Martin (หรือ​ที่​รู้จัก​กัน​ใน​ชื่อ Uncle Bob) เผยแพร่​ครั้ง​แรก​บน The Clean Code Blog เมื่อ 13 สิงหาคม 2012

แปล​และ​เรียบเรียง​เพื่อ​การ​ศึกษา — ไดอะแกรม​วงแหวน​ซ้อน​และ​ไดอะแกรม​การ​ข้าม​ผ่าน​ขอบเขต​ถูกวาด​ใหม่​เป็น Mermaid ภาษา​ไทย เพื่อ​ให้​อ่าน​และ​ค้นหา​ได้​สะดวก​ใน​เว็บ ศัพท์​เทคนิค​คง​ไว้​เป็น​ภาษา​อังกฤษ​ตาม​ต้นฉบับ ลิขสิทธิ์​ของ​เนื้อหา​ต้นฉบับ​เป็น​ของ​ผู้​เขียน

ตลอด​สิบ​กว่า​ปี​ที่​ผ่าน​มา วงการ​ซอฟต์แวร์​เสนอ​แนวทางการ​จัด​สถาปัตยกรรม​ออก​มา​หลาย​ชื่อ ทั้ง Hexagonal Architecture, Onion Architecture, Screaming Architecture, DCI และ BCE แต่ละ​ชื่อ​มา​จาก​คนละ​คน คนละ​บริบท คนละ​คำ​ศัพท์

แต่ Uncle Bob ชี้​ให้​เห็น​ว่า​เมื่อ​มอง​ลึก​ลง​ไป​ใน​โครงสร้าง​ของ​แต่ละ​แนวทาง พวก​มัน​ล้วน​พยายาม​แก้​ปัญหา​เดียวกัน และ​มา​บรรจบ​กัน​ที่​ข้อ​สรุป​เดียวกัน​อย่าง​น่า​ทึ่ง นั่น​คือ​การ​แบ่ง​ซอฟต์แวร์​ออก​เป็น​ชั้น ๆ (layer) เพื่อ​แยก business rules ออก​จาก​รายละเอียด​ที่​ไม่​เกี่ยวข้อง​กับ​ธุรกิจ

  • Hexagonal Architecture (หรือ Ports and Adapters) ของ Alistair Cockburn วาง​แกน application ไว้​ตรง​กลาง แล้ว​ให้​มัน​คุย​กับ​โลก​ภายนอก​ผ่าน port และ adapter เท่านั้น
  • DCI (Data, Context, Interaction) ของ James Coplien และ Trygve Reenskaug เน้น​แยก​บทบาท (role) ที่ object แสดงออก​ใน​แต่ละ use case ออก​จาก​ตัว​ข้อมูล​ถาวร​ของ object นั้น
  • BCE (Boundary, Control, Entity) ของ Ivar Jacobson ซึ่ง​เป็น​ส่วน​หนึ่ง​ของ Objectory/ICONIX แบ่งออบ​เจ็กต์ออก​เป็น​สาม​บทบาท คือ boundary object ที่​คุย​กับ​ภายนอก, control object ที่​คุม​ตรรกะ​ของ use case และ entity object ที่​แทน​ความ​รู้​ของ domain
  • Onion Architecture ของ Jeffrey Palermo ก็​วาด​วงกลม​ซ้อน​กัน​เช่น​เดียวกัน โดย​ให้ domain model อยู่​ที่​แกน​กลาง ล้อม​ด้วย​ชั้น application service แล้ว​จึง​เป็น​ชั้น​โครงสร้าง​พื้นฐาน​ที่​อยู่​นอก​สุด

แม้​จะ​ต่าง​กัน​ใน​รายละเอียด​ของ​การ​แบ่ง​ชั้น​และ​คำ​ศัพท์​ที่​ใช้ แต่​ทั้งหมด​นี้​มี​เป้าหมาย​ร่วม​เดียวกัน​คือ แยก​เรื่อง​ของ​ธุรกิจ (business) ออก​จาก​เรื่อง​ของ​กลไก (mechanism) Uncle Bob จึง​นำ​ทั้งหมด​มา​สังเคราะห์​รวม​เป็น​ภาพ​เดียว​ที่​เขา​เรียก​ว่า The Clean Architecture

ระบบ​ที่​ปฏิบัติ​ตาม​แนวทาง​นี้​จะ​มี​คุณสมบัติ​สำคัญ​ห้า​ข้อ:

  1. ไม่​ผูก​ติด​กับ framework (Framework-independent) — สถาปัตยกรรม​ไม่​พึ่งพา​การ​มี​อยู่​ของ library หรือ framework ใด ๆ คุณ​ใช้ framework เป็น “เครื่องมือ” ได้ ไม่ใช่​ต้อง​ยัด​ระบบ​ทั้งหมด​ลง​ไป​อยู่​ใน constraint ที่ framework กำหนด
  2. ทดสอบ​ได้ (Testable) — business rules ทดสอบ​ได้​โดย​ไม่​ต้อง​มี UI, ฐาน​ข้อมูล, web server หรือ​องค์​ประกอบ​ภายนอก​อื่น​ใด​เลย
  3. ไม่​ผูก​ติด​กับ UI — UI เปลี่ยน​ได้​ง่าย​โดย​ไม่​กระทบ​ส่วน​ที่​เหลือ​ของ​ระบบ เช่น สลับ​จาก​เว็บ​มา​เป็น console UI ได้​โดย​ไม่​ต้อง​แตะ business rules เลย
  4. ไม่​ผูก​ติด​กับ​ฐาน​ข้อมูล — สลับ Oracle หรือ SQL Server ไป​เป็น MongoDB, BigTable, CouchDB หรือ​อะไร​ก็ได้​ตามใจ เพราะ business rules ไม่​ได้​ผูก​ติด​กับ​ฐาน​ข้อมูล​ใด​ฐาน​ข้อมูล​หนึ่ง
  5. ไม่​ผูก​ติด​กับ​สิ่ง​ใด ๆ ใน​โลก​ภายนอก — จริง ๆ แล้ว business rules ไม่รู้​อะไร​เลย​เกี่ยว​กับ​โลก​ภายนอก

ภาพ​ต้นฉบับ​ของ Uncle Bob วาด​เป็น​วงกลม​สี่​วง​ซ้อน​กัน โดย​ยิ่ง​อยู่​วงใน​มาก​เท่าไร ก็​ยิ่ง​เป็น​นโยบาย (policy) ที่​เป็น​นามธรรม​และ​กว้าง​ระดับ​องค์กร​มาก​ขึ้น​เท่านั้น ยิ่ง​อยู่​วงนอก​มาก​เท่าไร ก็​ยิ่ง​เป็น​กลไก​ที่​เป็น​รูปธรรม​และ​ใกล้​ตัว​เครื่อง (mechanism) มาก​ขึ้น​เท่านั้น

เมื่อ​วาด​ใหม่​เป็น​ผัง​ลำดับ​ชั้น​แบบ Mermaid (แทน​วงกลม​จริง) จะ​ได้​ดังนี้:

flowchart TB
  subgraph FD["Frameworks and Drivers"]
    subgraph IA["Interface Adapters"]
      subgraph UCASE["Use Cases"]
        subgraph ENT["Entities"]
          E1[Enterprise business rules]
        end
        U1[Application business rules]
      end
      C1[Controllers]
      C2[Presenters]
      C3[Gateways]
    end
    W1[Web and UI frameworks]
    W2[Database]
    W3[Devices and external interfaces]
  end
  W1 --> C1
  C1 --> U1
  U1 --> E1

ภาพ 1 — วงแหวน​ซ้อน​กัน​สี่​ชั้น​ของ Clean Architecture จาก​ใน​สุด​ไป​นอก​สุด: Entities, Use Cases, Interface Adapters, Frameworks and Drivers ลูกศร​แสดง​ทิศทาง​ของ source-code dependency ที่​ชี้​เข้า​ด้าน​ใน​เสมอ ตั้งแต่ framework ไป​ยัง controller ไป​ยัง use case ไป​จนถึง entity · อ่าน​ต้นฉบับ ↗

วงใน​สุด​คือ Entities ซึ่ง​ห่อ​หุ้ม enterprise-wide business rules เอา​ไว้ นั่น​คือ​กฎเกณฑ์​และ​ตรรกะ​ทาง​ธุรกิจ​ที่​สำคัญ​ที่สุด​ของ​กิจการ ไม่​ว่า​จะ​รัน​ด้วย application ตัว​ไหน​ก็ตาม

Entity อาจ​เป็น object ที่​มี​ทั้ง​ข้อมูล​และ method หรือ​จะ​เป็น​ชุด​ของ​โครงสร้าง​ข้อมูล​กับ function แยก​กัน​ก็ได้ ไม่​สำคัญ — สิ่ง​ที่​สำคัญ​คือ Entity ต้อง​เป็น​สิ่ง​ที่ เปลี่ยนแปลง​น้อย​ที่สุด เมื่อ​โลก​ภายนอก​เปลี่ยน

ถ้า​เรา​เปลี่ยน​วิธี​นำทางใน​หน้า​จอ หรือ​เปลี่ยน​นโยบาย​ด้าน​ความ​ปลอดภัย นั่น​ไม่​ควร​มี​ผล​กับ Entity เลย​แม้แต่​น้อย เพราะ Entity อยู่​ใน​ระดับ​ที่​เป็น​นามธรรม​และ​กว้าง​ที่สุด​ใน​ระบบ

ถัด​ออก​มา​คือ​ชั้น Use Cases ซึ่ง​บรรจุ application-specific business rules เอา​ไว้ ชั้น​นี้​ประสาน (orchestrate) การ​ไหล​ของ​ข้อมูล​เข้า​และ​ออก​จาก Entities และ​สั่ง​ให้ Entities ใช้​กฎ​ธุรกิจ​ระดับ​องค์กร​ของ​มัน​เพื่อ​บรรลุ​เป้าหมาย​ของ use case นั้น ๆ โดย​เฉพาะ

เรา​ไม่​คาด​หวัง​ว่าการ​เปลี่ยนแปลง​ใน​ชั้น​นี้​จะ​กระทบ Entities และ​ก็​ไม่​คาด​หวัง​ว่า​ชั้น​นี้​จะ​ได้​รับ​ผลกระทบ​จาก​การ​เปลี่ยนแปลง​ภายนอก เช่น ฐาน​ข้อมูล, UI หรือ framework ที่​ใช้

ใน​ทาง​กลับ​กัน ถ้า​พฤติกรรม​ของ application เปลี่ยน — เช่น เปลี่ยน​ขั้นตอน​ของ use case — ชั้น​นี้​ก็​คือ​ชั้น​ที่​ควร​ได้​รับ​ผลกระทบ​นั้น

ชั้น​ถัด​มา​คือ Interface Adapters ซึ่ง​เป็น​ชุด​ของ​ตัว​แปลง (adapter) ที่​แปลง​ข้อมูล​จาก​รูปแบบ​ที่​สะดวก​ต่อ use cases และ Entities ไป​เป็น​รูปแบบ​ที่​สะดวก​ต่อ​สิ่ง​ภายนอก เช่น ฐาน​ข้อมูล​หรือ​เว็บ และ​แปลง​กลับ​ใน​ทิศทาง​ตรง​ข้าม

ชั้น​นี้​คือ​ที่​อยู่​ของ​สถาปัตยกรรม MVC ทั้ง​ชุด​สำหรับ GUI — Controllers, Presenters และ Views — Model ใน​ที่​นี้​ก็​เป็น​เพียง​โครงสร้าง​ข้อมูล​ที่ Controller ส่ง​เข้าไป​ยัง use case แล้ว​ส่ง​กลับ​ออก​มา​จาก use case ไป​ยัง Presenter หรือ View เท่านั้น​เอง

ใน​ชั้น​เดียวกัน​นี้​ยัง​มี Gateways ซึ่ง​ทำ​หน้าที่​แปลง​ข้อมูล​ไป​มา​กับ​ฐาน​ข้อมูล

กฎ​สำคัญ​คือ SQL ทั้งหมด​ควร​จำกัด​อยู่​ใน​ชั้น​นี้​เท่านั้น — โดย​เฉพาะ​ใน​ส่วน​ของ Interface Adapters ที่​เกี่ยวข้อง​กับ​ฐาน​ข้อมูล — ไม่มี​รหัส​ใน​วงใน​กว่า​นี้ (Use Cases หรือ Entities) ที่​ควร​รู้จัก​รายละเอียด​ของ​ฐาน​ข้อมูล​เลย​แม้แต่​น้อย

วงนอก​สุด​คือ Frameworks and Drivers ซึ่ง​ประกอบ​ด้วย framework และ​เครื่องมือ​ทั้งหลาย เช่น ฐาน​ข้อมูล, เว็บ framework ฯลฯ โดย​ทั่วไป​แล้ว​รหัส​ใน​วง​นี้​ควร​มี​น้อย ส่วน​ใหญ่​เป็น​เพียง glue code ที่​คอย​สื่อสาร​เข้าไป​ยัง​วง​ถัด​เข้าไป

Uncle Bob ย้ำ​เสมอ​ว่า “the Web is a detail. The database is a detail.” — เว็บ​ก็​คือ​รายละเอียด ฐาน​ข้อมูล​ก็​คือ​รายละเอียด — เพราะ​สิ่ง​เหล่า​นี้​ล้วน​เป็น​รายละเอียด​ที่​อยู่​วงนอก​สุด การ​เปลี่ยนแปลง​ใน​วง​นี้​จึง​ควร​สร้าง​ความ​เสียหาย​น้อย​ที่สุด​ต่อ​ระบบ

กฎ​การ​พึ่งพา (The Dependency Rule)

กฎ​ที่​ทำให้​ทุก​อย่าง​ใน​ภาพ​นี้​ทำงาน​ได้​คือ​กฎ​เดียว เรียบ​ง่าย แต่​เข้มงวด:

source-code dependency ต้อง​ชี้​เข้า​ด้าน​ใน​เท่านั้น

ไม่มี​สิ่ง​ใด​ใน​วงใน​ที่​จะ​รู้จัก​อะไร​เกี่ยว​กับ​สิ่ง​ที่​อยู่​ใน​วงนอก​ได้​เลย รวม​ถึง​ชื่อ function ชื่อ class ตัวแปร หรือ entity ใด ๆ ที่​ถูก​ประกาศ​ไว้​ใน​วงนอก

และ​ข้อมูล​รูปแบบ​ใด​ก็ตาม​ที่​ถูก​สร้าง​ขึ้น​ใน​วงนอก (เช่น รูปแบบ​ที่ framework กำหนด) ก็​ไม่​ควร​ถูก​ส่ง​เข้าไป​ปน​เปื้อน​วงใน เพราะ​จะ​ทำให้​วงใน​ต้อง​รู้จัก​รายละเอียด​ของ​วงนอก​ทาง​อ้อม ซึ่ง​ขัด​กับ​หลักการ​ทั้งหมด

ผลลัพธ์​คือ​ยิ่ง​อยู่​ใกล้​ศูนย์กลาง​มาก​เท่าไร code ก็​ยิ่ง​เป็น​นโยบาย​ระดับ​สูง เป็น​นามธรรม และ​เปลี่ยนแปลง​น้อย​เท่านั้น ส่วน​ยิ่ง​อยู่​วงนอก​มาก​เท่าไร code ก็​ยิ่ง​เป็น​กลไก​ที่​เป็น​รูปธรรม ผูก​กับ​เทคโนโลยี และ​คาด​ว่า​จะ​เปลี่ยน​บ่อย​กว่า

หลักการ​นี้​คือ​รูปแบบ​เฉพาะ​ของ Dependency Inversion Principle (DIP) ที่​ขยาย​จาก​ระดับ class ไป​สู่​ระดับ​สถาปัตยกรรม​ทั้ง​ระบบ — module ระดับ​สูง (policy) ไม่​ควร​พึ่งพา module ระดับ​ต่ำ (detail) ทั้ง​สอง​ฝ่าย​ควร​พึ่งพา abstraction ร่วม​กัน​แทน

Uncle Bob ย้ำ​ว่า​สี่​วงใน​ภาพ​นี้​เป็น​เพียง แผนผัง​เชิง​ตัวอย่าง (schematic) ไม่ใช่​กฎ​ตายตัว​ว่า​ต้อง​มี​สี่​ชั้น​เป๊ะ ๆ เสมอ​ไป บาง​ระบบ​อาจ​ต้องการ​วง​เพิ่มเติม​มากกว่า​นี้ ไม่มี​กฎ​ว่า​คุณ​ต้อง​มี​แค่​สี่​วง​เท่านั้น

แต่ กฎ​การ​พึ่งพา​ยัง​คง​ต้อง​ใช้​เสมอ ไม่​ว่า​จะ​มี​กี่​วง​ก็ตาม source code ต้อง​ชี้​เข้า​ด้าน​ใน​เสมอ ยิ่ง​เข้า​ใกล้​ศูนย์กลาง ระดับ​ของ​นโยบาย​และ​ความ​เป็น​นามธรรม​ก็​ยิ่ง​สูง​ขึ้น และ​วงใน​สุด​คือ​ที่​ที่​บรรจุ​นโยบาย​ทั่วไป​ที่สุด​ของ​ระบบ​เอา​ไว้

จุด​ที่​ดู​ขัดแย้ง​ใน​ตัวเอง​ที่สุด​ของ​ภาพ​นี้​คือ ใน​การ​ทำงาน​จริง control flow มัก​วิ่ง​ข้าม​ขอบเขต​จาก​วงใน​ไป​วงนอก เช่น Use Case ต้อง​ส่ง​ผลลัพธ์​ออก​ไป​ให้ Presenter แสดง​ผล แต่​กฎ​การ​พึ่งพา​กลับ​ห้าม​ไม่​ให้ source code ของ Use Case (วงใน) รู้จัก​หรือ​อ้างอิง​ถึง Presenter (วงนอก) โดยตรง

ทางออก​คือ​ใช้ polymorphism ผ่าน Dependency Inversion Principle เพื่อ “พลิก” ทิศทาง​ของ dependency ให้​สวน​ทาง​กับ​ทิศทาง​ของ control flow ตัวอย่าง​คลาสสิก​คือ​คู่​ระหว่าง Use Case Interactor กับ Presenter:

flowchart LR
  Controller -->|control flow calls| Interactor
  Interactor -->|control flow calls| OutputPort[Use Case Output Port]
  Presenter -.->|implements - dependency points inward| OutputPort
  OutputPort -.->|runtime dispatch| Presenter
  Presenter -->|control flow calls| ViewModel

ภาพ 2 — การ​ข้าม​ผ่าน​ขอบเขต​ระหว่าง Use Case กับ Interface Adapters Interactor เรียก interface ที่​ชื่อ Use Case Output Port ซึ่ง​ถูก​ประกาศ​ไว้​ใน​วง​ของ Use Case เอง (เส้น​ทึบ = control flow) ส่วน Presenter ซึ่ง​อยู่​วงนอก​เป็น​ผู้ implement interface นั้น (เส้น​ประ = source-code dependency) ผล​คือ control flow วิ่ง​จาก Interactor ออก​ไป​หา Presenter ได้​ตาม​ปกติ แต่ source-code dependency ของ Presenter กลับ​ชี้ เข้า​ด้าน​ใน หา interface ที่​อยู่​ใน​วง Use Case ไม่ใช่ Interactor ที่​ชี้​ออก​ไป​หา Presenter · อ่าน​ต้นฉบับ ↗

พูด​ให้​ชัด​ขึ้น: Interactor เรียก​ใช้ OutputPort ซึ่ง​เป็น interface ที่​มัน​เป็น​เจ้าของ (ประกาศ​อยู่​ใน​วง Use Cases เดียวกัน) ส่วน Presenter ที่​อยู่​ใน​วง Interface Adapters เป็น​ผู้ implement interface นั้น

เวลา​รัน​จริง Interactor เรียก method บน OutputPort แล้ว​มัน​ก็​ไป​เรียก​ใช้ implementation จริง​ที่​อยู่​ใน Presenter — control flow ข้าม​จาก​ใน​ไป​นอก​ได้​ตาม​ที่​ต้องการ

แต่ dependency ทาง compile ของ Presenter (ต้อง import/รู้จัก interface OutputPort) กลับ​ชี้ เข้า​ด้าน​ใน ไม่ใช่ Interactor ที่​ต้อง​รู้จัก Presenter แบบ​เดิม

เทคนิค​นี้​คือ​กลไก​เดียว​ที่​ถูก​ใช้​ซ้ำ ๆ ทุก​ครั้ง​ที่​ต้อง ข้าม​ขอบเขต (crossing boundaries) ใน​ภาพ​นี้ ไม่​ว่า​จะ​เป็น​ขอบเขต​ระหว่าง Controller กับ Use Case Interactor (ผ่าน Use Case Input Port) หรือ​ระหว่าง Use Case กับ Gateway ที่​คุย​กับ​ฐาน​ข้อมูล

ข้อมูล​ที่​ข้าม​ผ่าน​ขอบเขต​ต้อง​มี​รูปแบบ​อย่างไร

หัวข้อ​ที่​มีชื่อ​ว่า “ข้อมูล​ที่​ข้าม​ผ่าน​ขอบเขต​ต้อง​มี​รูปแบบ​อย่างไร”

เมื่อ​ข้อมูล​ต้อง​ข้าม​ขอบเขต​จาก​วง​หนึ่ง​ไป​อีก​วง​หนึ่ง มัน​ต้อง​อยู่​ใน​รูปแบบ​ที่ เรียบ​ง่าย​และ​แยก​ตัว​เป็น​อิสระ (isolated) ที่สุด อาจ​เป็น struct ธรรมดา, Data Transfer Object (DTO), parameter ของ function หรือ​แม้แต่​แพ็ก​เข้า hash map ก็ได้

สิ่ง​ที่​สำคัญ​ที่สุด​คือ ห้าม​ส่ง Entity object หรือ​แถว​ข้อมูล​จาก​ฐาน​ข้อมูล (database row) ข้าม​ขอบเขต​เข้าไป​ใน​ทิศทาง​เข้า​ด้าน​ใน​เด็ดขาด เพราะ​นั่น​จะ​ทำให้​วงใน​ต้อง​รู้จัก​รูปแบบ​ข้อมูล​ของ​วงนอก​ทาง​อ้อม ซึ่ง​ขัด​กับ​กฎ​การ​พึ่งพา​โดยตรง

รูปแบบ​ของ​ข้อมูล​ที่​ข้าม​ขอบเขต​ควร​ถูก​ออกแบบ​ให้​สะดวก​ที่สุด​สำหรับ วงใน​ที่​เป็น​ผู้รับ เสมอ ไม่ใช่​สะดวก​สำหรับ​วงนอก​ที่​เป็น​ผู้​ส่ง — นี่​คือ​รายละเอียด​เล็ก ๆ ที่​มัก​ถูก​มอง​ข้าม แต่​เป็น​ตัว​ตัดสิน​ว่า code วงใน​จะ​ยัง​คง “ไม่รู้จัก” วงนอก​ได้​จริง​หรือ​ไม่

ลอง​นึก​ภาพ​เว็บ application ทั่วไป​ที่​คุย​กับ​ฐาน​ข้อมูล: เว็บ server รวบรวม​ข้อมูล input แล้ว​ส่ง​ให้ Controller ซึ่ง​บรรจุ​ข้อมูล​นั้น​ลง​ใน Request Model ที่​เป็น​โครงสร้าง​ข้อมูล​ธรรมดา แล้ว​ส่ง​ต่อ​ให้ Use Case Interactor

ตัว Interactor ตีความ​ข้อมูล​นั้น​และ​ใช้​มัน​ควบคุม​การ​ทำงาน​ของ Entities มัน​อาจ​ต้อง​เรียก Gateway (ผ่าน output port ของ​ฝั่ง​ข้อมูล) เพื่อ​ดึง​ข้อมูล​เพิ่มเติม​จาก​ฐาน​ข้อมูล จัด​ข้อมูล​ให้​อยู่​ใน​รูปแบบ​ที่ Entities ใช้งาน​สะดวก แล้ว​สั่ง​ให้ Entities ทำงาน​ตาม​กฎ​ธุรกิจ​ของ​มัน

จาก​นั้น Interactor รวบรวม​ผลลัพธ์​จาก Entities มาสร้าง​เป็น Response Model แล้ว​ส่ง​ผ่าน output port ไป​ยัง Presenter ซึ่ง​มีหน้าที่​แปลง Response Model ให้​กลาย​เป็น View Model ที่​พร้อม​แสดง​ผล ก่อน​จะ​ส่ง​ต่อ​ไป​ยัง View เพื่อ render จริง

สังเกต​ว่า dependency ทั้งหมด​ใน​เรื่อง​นี้​ชี้​เข้าหา Use Cases เสมอ ไม่​ว่า​จะ​มา​จาก​ฝั่ง Controller/Presenter หรือ​ฝั่ง Gateway ก็ตาม — นี่​คือ​ภาพ​รวม​ที่​ยืนยัน​ว่า​กฎ​การ​พึ่งพา​ถูก​ยึดถือ​อย่าง​สม่ำเสมอ​ตลอด​ทั้ง​ระบบ

เป้าหมาย​สุดท้าย​ของ Clean Architecture ไม่ใช่​ความ​สวยงาม​ของ​ไดอะแกรม แต่​คือ​การ​ทำให้​ระบบ ทดสอบ​ได้​โดย​เนื้อแท้ (intrinsically testable)

เมื่อ business rules ถูก​แยก​ออก​จาก​รายละเอียด​ภายนอก​อย่าง​เคร่งครัด​ตาม​กฎ​การ​พึ่งพา เรา​ก็​ทดสอบ business rules ได้​โดย​ไม่​ต้อง​พึ่ง UI, ฐาน​ข้อมูล, web server หรือ​ส่วนประกอบ​ภายนอก​อื่น​ใด​เลย

และ​เมื่อ​วัน1 framework หรือ​ฐาน​ข้อมูล​ที่​ใช้​อยู่​ล้าสมัย​หรือ​ต้อง​เปลี่ยน ก็​สามารถ​แทนที่​ได้​โดย​กระทบ​ส่วน​อื่น​ของ​ระบบ​น้อย​ที่สุด เพราะ business logic ไม่​เคย​ผูก​ติด​กับ​รายละเอียด​การ implement เหล่า​นั้น​ตั้งแต่​แรก

แนวคิด​นี้​เชื่อม​โยง​โดยตรง​กับ​งาน​ออกแบบ​สถาปัตยกรรม​ใน Domain-Driven Design เช่น​กัน — วงแหวน Entities/Use Cases ใน​ภาพ​ของ Uncle Bob สอดคล้อง​กับ​แนวคิด​ของ domain model กับ application architecture ที่​ประสาน use case ต่าง ๆ ใน​ทาง DDD

ส่วน Gateway ที่​คุย​กับ​ฐาน​ข้อมูล​ใน​ชั้น Interface Adapters ก็​คือ​รูปแบบ​เดียว​กับ Repository pattern ที่​ทำให้ domain ไม่​ต้อง​รู้จัก​เทคโนโลยี​จัด​เก็บ​ข้อมูล​จริง

และ​ถ้า​เทียบ​ภาพ​วงแหวน​ซ้อน​กัน​ของ Clean Architecture กับ​ภาพ hexagon ของ Hexagonal Architecture จะ​เห็น​ว่า​ทั้ง​สอง​พูด​เรื่อง​เดียวกัน​ด้วย​รูปทรง​ต่าง​กัน คือ​แกน​กลาง​ที่​ไม่รู้จัก​โลก​ภายนอก ล้อม​รอบ​ด้วย​ชั้น​ที่​คอย​แปลง​ข้อมูล​เข้า-ออก

Clean Architecture ไม่ใช่​สถาปัตยกรรม​ใหม่​ที่ Uncle Bob คิดค้น​ขึ้น​มา​เอง แต่​เป็นการ​มอง​เห็น​รูปแบบ​ร่วม​ที่​ซ่อน​อยู่​เบื้องหลัง Hexagonal Architecture, Onion Architecture, DCI และ BCE แล้วกลั่น​ออก​มา​เป็น​กฎ​เดียว​ที่​เรียบ​ง่าย​ที่สุด​เท่า​ที่​จะ​ทำได้ นั่น​คือ กฎ​การ​พึ่งพา — source-code dependency ต้อง​ชี้​เข้า​ด้าน​ใน​เสมอ

เมื่อ​ยึด​กฎ​นี้​ไว้ ไม่​ว่า​คุณ​จะ​มี​กี่​วง จะ​เรียก​ชื่อ​ชั้น​แต่ละ​ชั้น​ว่า​อะไร หรือ​จะ​ใช้​เทคนิค​ใด​ใน​การ​ข้าม​ขอบเขต (โดย​เฉพาะ Dependency Inversion Principle) สิ่ง​ที่​ได้​กลับ​มา​เสมอ​คือ​ระบบ​ที่ business rules อยู่​ตรง​กลาง ปลอดภัย​จาก​ความ​ผันผวน​ของ UI, ฐาน​ข้อมูล และ framework ที่​อยู่​รอบ​ตัว​มัน


📄 อ่าน​ต้นฉบับ​ภาษา​อังกฤษ: The Clean Architecture — Robert C. Martin (Uncle Bob), The Clean Code Blog (สิงหาคม 2012)