การแยกระบบ (Decomposition)
คุณตัดสินใจแล้วว่าจะสร้างระบบเป็นชุดของ service แทนที่จะเป็น monolith ก้อนเดียว คำสัญญาฟังดูน่าดึงดูด คือได้หน่วยเล็ก ๆ ที่ deploy แยกอิสระ และแต่ละทีมส่งงานตามจังหวะตัวเองได้ แต่ก่อนจะได้ผลนั้น คุณต้องเจอคำถามที่ดูง่ายแต่หลอกตา คือเส้นแบ่งควรอยู่ตรงไหน สถาปัตยกรรมแบบ microservice เหนืออื่นใดคือชุดของเส้นแบ่ง วาดดี ระบบจะชวนให้คุณเข้าไปแก้ วาดแย่ คุณจะได้ distributed monolith ที่ทุกการเปลี่ยนแปลงยังกระเพื่อมไปทั่วทุก service เหมือนเดิม ต่างกันแค่ตอนนี้กระเพื่อมผ่านเครือข่ายแทน
ปัญหาคือคำว่า “ก็แยกออกเป็น service สิ” ไม่ใช่คำแนะนำ แต่เป็นการพูดเป้าหมายซ้ำเฉย ๆ การแยกตามชั้นทางเทคนิค เช่น service หนึ่งสำหรับ web tier หนึ่งสำหรับ business logic หนึ่งสำหรับ data access รังแต่จะสร้าง coupling แน่น ๆ แบบเดิมขึ้นมาใหม่ เพราะ feature เดียวก็ยังตัดผ่านทั้งสามชั้นอยู่ดี ส่วนการแยกแบบสุ่มก็ได้ service ที่ต้องเรียกหากันตลอดเวลากว่าจะทำงานอะไรสำเร็จสักอย่าง ที่แย่กว่านั้นคือไม่มี compiler ตัวไหนบอกคุณได้ว่าเส้นแบ่งผิด กว่าจะรู้ตัวก็หลายเดือนถัดมา ตอนที่ feature “เล็ก ๆ” อันหนึ่งไปแตะสี่ service และสามทีม
ดังนั้นคำถามที่ module นี้ตอบคือ ด้วยหลักการอะไรที่คุณจะใช้ตัดสินว่าอะไรควรอยู่ใน service หนึ่งและอะไรควรอยู่ในอีก service หนึ่ง เพื่อให้แต่ละ service มีความเกาะเกี่ยวภายในสูง (cohesive) เชื่อมต่อกับส่วนที่เหลืออย่างหลวม ๆ (loosely coupled) และสามารถพัฒนาต่อยอดได้ด้วยตัวเอง?
วิธีแก้
หัวข้อที่มีชื่อว่า “วิธีแก้”ไม่มี algorithm เดียวที่ใช้ได้ แต่มีชุด pattern เล็ก ๆ ที่สอดคล้องกันและทำงานร่วมกันได้ สองตัวในนั้นเป็น กลยุทธ์ สำหรับค้นหาเส้นแบ่ง ส่วนที่เหลือเป็น pattern เสริม ที่ช่วยให้ service ที่ได้นั้นมีสุขภาพดี
สองกลยุทธ์หลักของการ decompose คือ:
- Decompose by Business Capability — นิยามแต่ละ service รอบ ๆ สิ่งที่ธุรกิจ ทำ (Order Management, Billing, Shipping) เส้นแบ่งจะอิงตาม business capability ที่มั่นคงขององค์กรมากกว่าเทคโนโลยีปัจจุบัน
- Decompose by Subdomain — ใช้ Domain-Driven Design เพื่อค้นหา bounded context และ subdomain แล้วทำให้แต่ละตัวเป็น service นี่เป็นเส้นทางที่เข้มงวดและขับเคลื่อนด้วยโมเดล (model-driven) มากกว่า ไปสู่เส้นแบ่งประเภทเดียวกันโดยคร่าว ๆ
และมีอีกสอง pattern เสริมที่ช่วยให้ service เหล่านั้นยังเป็นอิสระหลังสร้างเสร็จแล้ว:
- Self-Contained Service — service หนึ่งตอบ request โดยไม่ต้องเรียก service อื่นแบบ synchronous จึงยังคงพร้อมใช้งานได้แม้เพื่อนบ้านจะช้าหรือล่ม
- Service per Team — ความเป็นเจ้าของของแต่ละ service ถูกจัดให้สอดคล้องกับทีมเดียว เพื่อให้สถาปัตยกรรมและองค์กรเสริมแรงซึ่งกันและกันแทนที่จะขัดแย้งกัน
flowchart TB
Start[Monolith to decompose] --> Q{Choose a strategy}
Q -->|what the business does| BC[Decompose by<br/>Business Capability]
Q -->|domain model| SD[Decompose by<br/>Subdomain / DDD]
BC --> Services[Candidate services]
SD --> Services
Services --> SCS[Self-Contained Service<br/>keep each one available]
Services --> SPT[Service per Team<br/>give each one an owner]
SCS --> Good[Cohesive, loosely coupled,<br/>independently deployable services]
SPT --> Good โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”เราเริ่มจากสองกลยุทธ์ก่อน เพราะทุกอย่างที่เหลือขึ้นอยู่กับการวางเส้นแบ่งให้ถูกต้องพอประมาณ Decompose by Business Capability เป็นจุดเริ่มต้นที่เข้าถึงได้ง่ายที่สุดและเป็นค่าเริ่มต้นที่ดี Decompose by Subdomain นำวินัยของ Domain-Driven Design เข้ามา และมักให้เส้นแบ่งที่คมชัดกว่าเมื่อ domain มีความซับซ้อน ทั้งสองอย่างนี้เสริมกัน ไม่ใช่คู่แข่งกัน capability ให้การแบ่งรอบแรกอย่างรวดเร็ว ส่วน subdomain เข้ามาเกลาให้คมขึ้น
จากนั้นเราหันไปดูสอง pattern ที่ปกป้อง service หลังจากเส้นแบ่งถูกวาดแล้ว Self-Contained Service เกี่ยวกับความเป็นอิสระตอน runtime คือ service ไม่ควรล้มเพียงเพราะปลายทางที่คุยด้วยไม่พร้อมใช้งาน Service per Team เกี่ยวกับความเป็นอิสระเชิงองค์กร คือ service ควรมีทีมเดียวที่เป็นเจ้าของแบบ end to end ตาม Conway’s law แต่ละบทเรียนมีรูปแบบเดียวกัน คือ สถานการณ์ที่นำไปสู่ pattern, แรงต่าง ๆ ที่ตึงเครียดกัน, สิ่งที่ pattern ทำ, แผนภาพ, และการมองอย่างตรงไปตรงมาว่า pattern นั้นมีต้นทุนอะไร
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”- Decompose by Business Capability — กลยุทธ์เริ่มต้นที่พบบ่อยที่สุด
- Decompose by Subdomain — เส้นทางแบบ Domain-Driven Design สู่เส้นแบ่ง