Monolith vs Microservices
แทบทุกระบบที่ประสบความสำเร็จเริ่มต้นจาก monolith และมีเหตุผลที่ดีรองรับ ตอนที่ยังคิดไม่ตกว่าจะสร้างอะไร สิ่งสุดท้ายที่คุณอยากได้คือภาระของระบบแบบกระจาย คุณแค่เขียน module ต่อสายเข้าด้วยกันใน process เดียว ชี้ไปที่ database เดียว แล้ว ship ออกไป ถ้าจะเข้าใจว่าทำไมทีมหนึ่งถึงยอมแบกความซับซ้อนของ microservices คุณต้องเข้าใจสถาปัตยกรรมที่เขากำลังก้าวออกมาก่อน และเข้าใจว่าทำไม monolith ถึงรับใช้เขาได้ดีมานานขนาดนั้น
monolithic architecture บรรจุทั้งแอปพลิเคชันเป็นหน่วย deploy เดียว module ทั้งหมด — order logic, payment logic, inventory logic — อยู่ใน codebase เดียว compile หรือ bundle เป็น artifact เดียว รันใน process เดียว และใช้ฐานข้อมูลเดียวร่วมกัน
นี่ไม่ใช่ข้อบกพร่อง สำหรับทีมเล็กและผลิตภัณฑ์ที่ยังอ่อนวัย monolith มีจุดแข็งที่แท้จริง:
- พัฒนาง่าย repository เดียว build เดียว วิธีเดียวในการรันทั้งหมดบนเครื่องของคุณ
- ทดสอบง่าย end-to-end test รันกับ process เดียว ไม่มีเครือข่ายอยู่ตรงกลางให้ต้อง mock หรือคอยล้มเป็นพัก ๆ
- deploy ง่าย artifact เดียวออกไป ไม่มี version-skew ระหว่าง service ให้ต้องมานั่งคิด
- strong consistency ได้ง่าย transaction ฐานข้อมูลเดียวสามารถอัปเดต orders, inventory และ payments แบบ atomic ได้ และ join เดียวก็อ่านข้ามทั้งหมดได้
ปัญหาไม่ได้อยู่ที่ monolith ในตัวเอง แต่อยู่ที่สิ่งที่เกิดขึ้นกับ monolith ที่ ประสบความสำเร็จ เมื่อเวลาผ่านไป พอ codebase โตขึ้นและทีมโตตาม สิ่งที่เคยทำให้ทุกอย่างเรียบง่าย คือการเป็นหน่วยเดียวที่ใช้ร่วมกัน กลับกลายเป็นตัวถ่วงทุกคน:
- codebase ใหญ่จนไม่มีใครเข้าใจได้ทั้งหมด และการแก้ตรงหนึ่งก็ไปทำให้อีกตรงพัง
- ทุกการเปลี่ยนแปลง ไม่ว่าจะเล็กแค่ไหน ต้อง build และ deploy ทั้งแอปพลิเคชัน ดังนั้น release จึงใหญ่ ไม่ค่อยเกิดบ่อย และเสี่ยง
- คุณไม่สามารถ scale แต่ละส่วนอย่างอิสระได้ ถ้ามีเพียง payment logic ที่ติด CPU-bound คุณก็ยังต้องรันสำเนาของ ทั้ง แอปพลิเคชันเพิ่มเพื่อให้ทันความต้องการ
- ทีมเหยียบเท้ากันเอง คนจำนวนมาก commit เข้า codebase เดียวและรอ release pipeline ที่ใช้ร่วมกันตัวเดียว ก่อให้เกิดการแย่งชิงและภาระการประสานงาน
- คุณถูกล็อกอยู่กับ technology stack เดิม การจะรับเอาภาษาหรือ framework ใหม่มาใช้หมายถึงต้องเขียนใหม่หรือแบก monolith ทั้งก้อนติดตัวไปด้วย
ดังนั้นแรงผลักก็ชัดเจน: monolith ปรับให้เหมาะกับความเรียบง่ายในช่วงต้น แต่ monolith ขนาดใหญ่ที่ยุ่งวุ่นวายแลกความสามารถในการ deploy อย่างอิสระ การ scale อย่างอิสระ และความเป็นอิสระของทีมทิ้งไป
วิธีแก้
หัวข้อที่มีชื่อว่า “วิธีแก้”microservices architecture คือขั้วตรงข้ามที่ตั้งใจออกแบบมา แทนที่จะมีหน่วย deploy เดียว คุณแยกแอปพลิเคชันออกเป็นชุด service ที่เล็กลง แต่ละตัว deploy แยกอิสระได้ เป็นเจ้าของข้อมูลของตัวเอง และตรงกับ business capability หนึ่งอย่าง สิ่งที่เคยเป็น method call ภายใน process เดียวจึงกลายเป็น API call หรือ event ที่ข้ามเครือข่าย
ไดอะแกรมข้างล่างวางทั้งสองแบบไว้เคียงกัน กล่องเดียวที่ deploy ทั้งก้อน เทียบกับหลายกล่องที่ deploy แยกกันได้
flowchart TB
subgraph Monolith[Monolithic Architecture]
direction TB
M[Single Deployable Unit]
MO[Orders Module]
MP[Payments Module]
MI[Inventory Module]
M --- MO
M --- MP
M --- MI
MDB[(Shared Database)]
MO --> MDB
MP --> MDB
MI --> MDB
end
subgraph Micro[Microservices Architecture]
direction TB
SO[Order Service] --> SODB[(Orders DB)]
SP[Payment Service] --> SPDB[(Payments DB)]
SI[Inventory Service] --> SIDB[(Inventory DB)]
end การแยกแบบนี้ไม่ได้มาฟรี และไม่ได้ดีกว่าโดยอัตโนมัติ เป็นการเอาความเรียบง่ายแบบ in-process และความสอดคล้องที่ได้มาง่าย ๆ ของ monolith ไปแลกกับความเป็นอิสระและ scalability พร้อมรับต้นทุนการดูแลระบบแบบกระจายเข้ามาด้วย บทเรียนถัดไปจะชั่งน้ำหนักการแลกนี้อย่างตรงไปตรงมา
ผลลัพธ์ที่ตามมา
หัวข้อที่มีชื่อว่า “ผลลัพธ์ที่ตามมา”อะไรบ้างที่เปลี่ยนไปเมื่อคุณย้ายจากด้านซ้ายของไดอะแกรมไปด้านขวา:
- transaction เดียวที่ใช้ร่วมกันกลายเป็น workflow ที่ต้องประสานงานข้าม service ดังนั้น strong consistency จึงกลายเป็น eventual consistency ที่คุณต้องออกแบบรองรับ
- การ deploy ทั้งแอปพลิเคชันกลายเป็นการ deploy ขนาดเล็กที่อิสระจำนวนมาก
- ปุ่มปรับ scale เดียวกลายเป็นปุ่มปรับหนึ่งปุ่มต่อหนึ่ง service
- codebase เดียวที่ทุกคนเป็นเจ้าของกลายเป็น codebase จำนวนมาก แต่ละอันมีทีมหนึ่งเป็นเจ้าของ
ทั้งหมดนี้ไม่ได้หมายความว่า monolith ผิด monolith ที่มีโครงสร้างดี — modular monolith ที่มีขอบเขตภายในที่สะอาด — สามารถพาผลิตภัณฑ์ไปได้ไกลมาก และบ่อยครั้งก็เป็นจุดเริ่มต้นที่ฉลาดกว่า ประเด็นของบทเรียนนี้ไม่ใช่ว่าอย่างหนึ่งดีและอีกอย่างแย่ แต่คือทั้งสองทำ trade-off ที่ตรงข้ามกัน และคุณควรเลือก trade-off ที่สถานการณ์ของคุณเรียกร้องจริง ๆ
pattern ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “pattern ที่เกี่ยวข้อง”- What Is Microservices Architecture? — นิยามที่บทเรียนนี้นำมาเปรียบเทียบ
- Benefits and Drawbacks — trade-off ที่พิจารณาอย่างละเอียด
- When to Use Microservices — การเลือกว่าคุณควรอยู่ฝั่งไหนของไดอะแกรม
| Monolith | Microservices | |
|---|---|---|
| เหมาะกับ | ทีมเล็ก, domain ยังไม่ชัด, MVP | ทีมใหญ่, domain ชัดเจน, scale requirement สูง |
| Deploy | ทั้งหมดพร้อมกัน | แยก service อิสระ |
| Scale | ทั้งระบบ | เฉพาะ service ที่ต้องการ |
| Debug | ง่าย — stack trace เดียว | ยาก — ต้อง distributed tracing |
| Team | ทีมเดียว access ทุกอย่าง | ownership แยกตาม service |
| Consistency | Strong (ACID transaction) | Eventual (ต้องออกแบบ) |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”ย้ายไป Microservices ก่อน Domain ชัดเจน — decompose ผิด boundary แล้วต้อง refactor ใหม่
อาการ:
- service ที่แยกออกมาต้องเรียกกัน synchronous ใน 80% ของ operation
- ทุก feature ใหม่ต้องแก้หลาย service พร้อมกัน
- ควร start ด้วย Modular Monolith ก่อน แล้ว extract เมื่อ boundary ชัด
Microservices ที่ Scale ก่อนจำเป็น — เตรียม infrastructure สำหรับ 1M users ตอนมี 1000 users
อาการ:
- ทีมเสียเวลา 70% กับ infrastructure ไม่ใช่ product
- ปัญหาจริงที่เจอยังไม่ใช่ scale แต่เป็น feature velocity
💡 ตัวอย่างจากของจริง
Stack Overflow:
- รัน monolith มาตลอดและ serve request 1.3B ครั้งต่อเดือน
- ทีมเพียง ~10 คน
- พิสูจน์ว่า monolith ที่ optimize ดีเอาชนะ microservices ที่ implement ไม่ดีได้
Segment:
- migrate ไป microservices → complexity ระเบิด → migrate กลับ monolith (2020)
- บทเรียน: microservices คุ้มเมื่อ team scale ไม่ใช่เพราะ architecture fashion