Common Architectural Vision
ซอฟต์แวร์ที่ออกแบบดีต้องมีสถาปัตยกรรมเดียวที่ชัดเจน
คืออะไร
หัวข้อที่มีชื่อว่า “คืออะไร”Common Architectural Vision คือแนวปฏิบัติที่ทำให้ ทุกคนในทีมมองเห็นและยึดถือสถาปัตยกรรมเดียวกัน เมื่อพัฒนาและวิวัฒน์ระบบ ไม่ใช่แค่หลักการนามธรรม แต่เป็นกิจวัตรจริง เช่น การประชุมสื่อสารวิสัยทัศน์ การเขียนเอกสารตัดสินใจ และการตรวจ code ให้สอดคล้องกับทิศทางที่ตกลงกันไว้
แนวคิดนี้มีรากมาจาก Fred Brooks ใน The Mythical Man-Month ที่เสนอคำว่า conceptual integrity (บูรณภาพเชิงแนวคิด) — ระบบที่ดีต้องมีความสอดคล้องกันของการออกแบบทั้งระบบ ราวกับออกแบบมาจากความคิดเดียว หรือกลุ่มความคิดเล็ก ๆ ที่เห็นพ้องกัน Brooks ถึงกับยกตัวอย่างมหาวิหาร Reims ที่แม้สร้างต่อเนื่องกันหลายชั่วอายุคนแต่ยังคงเอกภาพทางสถาปัตยกรรมไว้ได้ Martin Fowler ก็ให้มุมมองใกล้เคียงกันว่าสถาปัตยกรรมซอฟต์แวร์ที่แท้จริงคือ ความเข้าใจร่วมกัน (shared understanding) ที่นักพัฒนาอาวุโสในทีมมีต่อโครงสร้างของระบบ มากกว่าตัวเอกสารที่ผลิตออกมา
ซอฟต์แวร์แทบทุกตัวเริ่มต้นด้วยสถาปัตยกรรมเดียว แต่หลาย app วิวัฒน์ไปตามกาลเวลา บางครั้งผ่านการ re-architect บางส่วนที่ไม่เคยเสร็จสมบูรณ์ เมื่อเป็นเช่นนั้น ซอฟต์แวร์จะกลายเป็น “อสุรกาย Frankenstein” ที่บางส่วนสร้างบนสถาปัตยกรรมหนึ่งและบางส่วนบนอีกแบบ — ซึ่งเป็นอาการเดียวกับที่ Brian Foote และ Joseph Yoder เรียกว่า Big Ball of Mud คือระบบที่ไร้โครงสร้างชัดเจน เต็มไปด้วยการซ่อมแซมเฉพาะหน้าซ้ำแล้วซ้ำเล่าจนสถาปัตยกรรมเดิมสึกกร่อนไปเรื่อย ๆ
การเปลี่ยนสถาปัตยกรรมมักมีเหตุผลที่ดี ทางออกจึงไม่ใช่การยึดติดกับของเดิมเสมอไป แต่สำคัญที่ต้องสร้างซอฟต์แวร์ให้การเปลี่ยนสถาปัตยกรรมในอนาคตทำได้โดยไม่ต้องใช้ความพยายามมากเกินจะรับไหว และในอุดมคติควรทำให้ เสร็จ ไม่ใช่ทิ้งไว้กลางคัน
ยิ่ง app เป็น modular มากเท่าไร ก็ยิ่งปรับรับสถาปัตยกรรมใหม่ได้ง่ายขึ้น app แบบ monolith ที่ไม่มี modularity เลย ต้องเปลี่ยนสถาปัตยกรรมแบบ “ทั้งหมดหรือไม่ทำเลย” หรือแย่กว่านั้นคือเปลี่ยนขณะยังรันอยู่จนกลายเป็น Frankenstein แต่ app ที่ modular มากกว่าสามารถปรับสถาปัตยกรรมให้เหมาะกับแต่ละ module หรืออัปเกรดทีละ module ได้ สถาปัตยกรรมแบบ microservices เป็นตัวอย่างที่ component เล็ก ๆ จำนวนมากสื่อสารกันผ่าน HTTP และวิวัฒน์แยกกันได้ อย่างไรก็ตาม microservices ไม่ใช่ทางเดียว — app ใด ๆ ที่ยึด Separation of Concerns และหลักการออกแบบอื่น ๆ ก็อัปเดตและดูแลง่ายกว่าระบบที่เต็มไปด้วย spaghetti code
ทำอย่างไร
หัวข้อที่มีชื่อว่า “ทำอย่างไร”- บันทึกวิสัยทัศน์ให้เป็นลายลักษณ์อักษร — ใช้ Architecture Decision Record (ADR) แบบสั้น ๆ ตามแนวทางที่ Michael Nygard เสนอ บันทึกบริบท ทางเลือก และเหตุผลของการตัดสินใจแต่ละครั้ง เพื่อให้คนที่เข้าทีมทีหลังเข้าใจว่าทำไมระบบถึงถูกสร้างมาแบบนี้
- สื่อสารวิสัยทัศน์กับทั้งทีมอย่างสม่ำเสมอ — จัดประชุมทบทวนสถาปัตยกรรมเป็นระยะ ไม่ใช่แค่ตอนเริ่ม project Fowler แนะนำให้มีตัวอย่าง code ที่ดี (reference implementation) ใน codebase จริง เพราะนักพัฒนาส่วนใหญ่เรียนรู้จาก code ที่มีอยู่มากกว่าจากเอกสาร
- บังคับใช้ด้วยเครื่องมืออัตโนมัติ — เขียน architecture test (fitness function) ให้ CI ตรวจจับการละเมิดกฎการพึ่งพาระหว่าง layer หรือ module โดยอัตโนมัติ แทนที่จะพึ่งการตรวจ code ด้วยตาเพียงอย่างเดียว
- เมื่อจะเปลี่ยนสถาปัตยกรรม ให้ตัดสินใจร่วมกันและทำให้จบ — หลีกเลี่ยงการปล่อยให้ครึ่งระบบอยู่บนของเก่าและอีกครึ่งอยู่บนของใหม่ค้างคาไว้ตลอดกาล การย้ายแบบ Strangler Fig ช่วยให้ทยอยแทนที่ระบบเก่าได้ทีละส่วนโดยยังคงมีเส้นชัยที่ชัดเจนว่าเมื่อไรจะย้ายเสร็จ
- เพิ่มความเป็น modular ให้ระบบ — แบ่งขอบเขต module/บริการให้ชัด (เช่นตาม bounded context) เพื่อให้ต้นทุนของการเปลี่ยนสถาปัตยกรรมในอนาคตกระจุกอยู่ใน module เดียว ไม่ลามไปทั้งระบบ
- ระวัง Conway’s Law — โครงสร้างองค์กรมักสะท้อนออกมาเป็นโครงสร้างของระบบ หากทีมกระจัดกระจายกันโดยไม่มีเจ้าของสถาปัตยกรรมร่วม วิสัยทัศน์เดียวกันจะรักษาไว้ยาก จึงควรมีคนหรือกลุ่มเล็ก ๆ ที่รับผิดชอบทิศทางสถาปัตยกรรมโดยรวม ตามที่ Conway’s Law ชี้ให้เห็น
flowchart TD
A[เกิดความต้องการเปลี่ยนสถาปัตยกรรม] --> B{ทั้งทีมเห็นพ้องกับวิสัยทัศน์ใหม่หรือยัง}
B -- ยังไม่เห็นพ้อง --> C[จัดประชุมและเขียน ADR ร่วมกัน]
C --> B
B -- เห็นพ้องแล้ว --> D[บันทึกวิสัยทัศน์และเกณฑ์ลง ADR]
D --> E[ทยอยย้ายทีละ module ด้วย Strangler Fig]
E --> F{ยังมี code สถาปัตยกรรมเก่าคงค้างอยู่ไหม}
F -- มี --> E
F -- ไม่มีแล้ว --> G[ระบบทั้งหมดอยู่บนสถาปัตยกรรมเดียว]
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”สมมติทีมตัดสินใจย้ายจาก layered monolith ไปเป็น modular monolith ที่แบ่งตาม bounded context ขั้นแรกคือบันทึกการตัดสินใจไว้ใน ADR สั้น ๆ
# ADR 007: ย้ายจาก Layered Architecture เป็น Modular Monolith
สถานะ: ยอมรับแล้ว
บริบท: layer Business Logic ปัจจุบันพึ่งพากันข้าม feature แบบสะเปะสะปะทำให้แก้ไข feature หนึ่งแล้ว feature อื่นพังโดยไม่คาดคิด
การตัดสินใจ: แบ่ง codebase เป็น module ตาม bounded context แต่ละ moduleห้าม reference internal type ของ module อื่นโดยตรง ต้องคุยผ่าน publicinterface หรือ event เท่านั้น
ผลที่ตามมา: ต้องรื้อ namespace เดิมทั้งหมด และเพิ่ม architecturetest ใน CI เพื่อป้องกันการละเมิดกฎนี้ในอนาคตจากนั้นเขียน fitness function เป็น architecture test เพื่อบังคับกฎที่ตกลงกันไว้โดยอัตโนมัติ ไม่ให้ใครแอบ reference ข้าม module จนวิสัยทัศน์ค่อย ๆ เพี้ยนไปทีละน้อย
// Fitness function: บังคับว่า module Domain ต้องไม่พึ่งพา Infrastructure// โดยตรง เพื่อรักษาสถาปัตยกรรมเดียวไว้ทั้งระบบusing NetArchTest.Rules;using Xunit;
public class ArchitectureVisionTests{ [Fact] public void Domain_should_not_depend_on_infrastructure() { var result = Types.InAssembly(typeof(Domain.Order).Assembly) .That() .ResideInNamespace("MyApp.Domain") .ShouldNot() .HaveDependencyOn("MyApp.Infrastructure") .GetResult();
Assert.True( result.IsSuccessful, "พบการละเมิดวิสัยทัศน์สถาปัตยกรรม: " + string.Join(", ", result.FailingTypeNames ?? Array.Empty<string>())); }
[Fact] public void Ordering_module_should_not_reference_Billing_internals() { var result = Types.InAssembly(typeof(Ordering.OrderService).Assembly) .That() .ResideInNamespace("MyApp.Ordering") .ShouldNot() .HaveDependencyOn("MyApp.Billing.Internal") .GetResult();
Assert.True(result.IsSuccessful); }}test สองตัวนี้ทำหน้าที่เป็น “ยาม” ที่คอยตรวจสอบว่า code ใหม่ทุกชิ้นที่ merge เข้ามายังคงสอดคล้องกับวิสัยทัศน์ที่ทีมตกลงกันไว้ หากใครเผลอ import ข้ามขอบเขตที่ไม่ควร CI จะแดงทันทีแทนที่จะรอให้ code review จับได้ทีหลัง (หรือจับไม่ได้เลย)
ประโยชน์และข้อควรระวัง
หัวข้อที่มีชื่อว่า “ประโยชน์และข้อควรระวัง”ประโยชน์
- ลดความสับสนของนักพัฒนาใหม่ เพราะมีความเข้าใจร่วมกันเดียวว่าระบบทำงานอย่างไร ไม่ต้องเดาว่า module ไหนใช้แพทเทิร์นไหน
- ลดต้นทุนของ Feature ในระยะยาว เพราะไม่ต้องเขียน code รองรับสองสถาปัตยกรรมพร้อมกัน
- ทำให้การ re-architect ในอนาคตวางแผนได้ง่ายขึ้น เพราะขอบเขต module ชัดเจนอยู่แล้ว
- เปิดทางให้ automated architecture test ทำหน้าที่เฝ้าระวังแทนคนตลอดเวลา
ข้อควรระวัง
- อย่าให้วิสัยทัศน์กลายเป็นข้ออ้างของ Big Design Up Front — วิสัยทัศน์ควรมีรายละเอียดพอที่จะสื่อสารได้ ไม่ใช่ spec ที่ตายตัวจนขยับไม่ได้เมื่อบริบทเปลี่ยน
- วิสัยทัศน์ที่ไม่มีใครเป็นเจ้าของหรือไม่มีการตรวจสอบซ้ำเป็นระยะจะกลายเป็นเอกสารที่ล้าสมัยและถูกละเลย
- การเปลี่ยนสถาปัตยกรรมที่ทำครึ่ง ๆ กลาง ๆ อันตรายกว่าไม่เปลี่ยนเลย เพราะทำให้ทีมต้องเข้าใจสองระบบพร้อมกัน
- ต้องถ่วงดุลกับ YAGNI — ไม่ใช่ทุกการเปลี่ยนแปลงต้องรอ “วิสัยทัศน์ใหญ่” เสมอไป บางครั้งการปรับเล็ก ๆ ตรงหน้าตาม Boy Scout Rule ก็เพียงพอ
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”- Separation of Concerns
- Architectural Agility
- YOLO Architecture
- Big Ball of Mud
- Strangler Fig
- Conway’s Law
แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- ที่มา · deviq.com/practices/common-architectural-vision
- Software Architecture Guide — Martin Fowler
- The Mythical Man-Month — Wikipedia (Conceptual Integrity)
- Design Principles for Azure Applications — Microsoft Learn
- Big Ball of Mud — Brian Foote & Joseph Yoder
- bliki: Architecture Decision Record — Martin Fowler