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

การแยกระบบ (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
ภาษาแห่ง pattern ของการ decompose: สองกลยุทธ์สำหรับค้นหาเส้นแบ่ง และสอง pattern เสริมสำหรับรักษาให้ service เป็นอิสระ

เราเริ่มจากสองกลยุทธ์ก่อน เพราะทุกอย่างที่เหลือขึ้นอยู่กับการวางเส้นแบ่งให้ถูกต้องพอประมาณ 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 นั้นมีต้นทุนอะไร

คำถามหลักของการ decomposition คืออะไร
ทำไมการแยกระบบตามชั้นทางเทคนิค เช่น web, logic, data จึงมักเป็นการ decompose ที่ไม่ดี
สอง pattern ใดคือ *กลยุทธ์* หลักในการหาเส้นแบ่งของ service