5 คะแนน โดย GN⁺ 2023-10-31 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • แบ็กเอนด์มักเริ่มต้นด้วย โมโนลิธ แต่เมื่อหลายทีมฟีเจอร์เริ่มแก้ไข codebase เดียวกัน ความผูกพันกันของโค้ดอาจสูงขึ้นและความเร็วในการเปลี่ยนแปลงอาจช้าลง
  • ไมโครเซอร์วิสคือแนวทางที่แบ่งบริการตามฟังก์ชันทางธุรกิจ เพื่อให้ได้ การ deploy อย่างอิสระ และความเป็นอิสระของทีมขนาดเล็ก
  • หากบริการเล็กเกินไป ภาระด้านปฏิบัติการ จะมากกว่าประโยชน์ที่ได้รับ และคำว่า “micro” อาจทำให้การตัดสินใจคลาดเคลื่อนได้
  • การนำมาใช้เต็มรูปแบบมีต้นทุนครอบคลุมหลายด้าน ตั้งแต่การทำให้เป็นมาตรฐาน, การ provisioning ทรัพยากร, การป้องกันการเรียกใช้งานระยะไกล, CI/CD, งานปฏิบัติการ ไปจนถึง ความสอดคล้องในท้ายที่สุด
  • โดยทั่วไป การเรียนรู้ขอบเขตให้เพียงพอภายในโมโนลิธก่อน แล้วค่อยแยกออกทีละส่วนเมื่อปัญหาจากการเติบโตเริ่มรุนแรงขึ้นจะปลอดภัยกว่า ส่วนแนวทางเริ่มด้วยไมโครเซอร์วิสเหมาะเมื่อมีประสบการณ์และมีแพลตฟอร์มพร้อมแล้ว

ข้อจำกัดที่เผยให้เห็นเมื่อโมโนลิธเติบโตขึ้น

  • แบ็กเอนด์ของแอปพลิเคชัน JavaScript แบบ single-page สมัยใหม่มักเริ่มต้นเป็น บริการเว็บแบบ stateless เดี่ยว ที่ให้บริการ RESTful HTTP API และใช้ฐานข้อมูลเชิงสัมพันธ์
  • บริการนี้ประกอบด้วยคอมโพเนนต์หรือไลบรารีที่ implement ฟังก์ชันทางธุรกิจต่าง ๆ
  • ยิ่งมีทีมฟีเจอร์เข้าร่วมใน codebase เดียวกันมากขึ้น ความผูกพันระหว่างคอมโพเนนต์ก็ยิ่งสูงขึ้น และพื้นที่ทำงานระหว่างทีมก็มีแนวโน้มทับซ้อนกันมากขึ้น
    • ผลิตภาพลดลง และคาดการณ์ขอบเขตผลกระทบของการเปลี่ยนแปลงได้ยากขึ้น
  • เมื่อ codebase ซับซ้อนมากพอ จะไม่มีใครเข้าใจทั้งหมดได้อย่างสมบูรณ์ ทำให้การ implement ฟีเจอร์ใหม่และการแก้บั๊กใช้เวลานาน
  • แม้แบ็กเอนด์จะแบ่งเป็นไลบรารีตามทีม แต่การเปลี่ยนแปลงไลบรารีก็จะนำไปสู่การ redeploy บริการในท้ายที่สุด
    • หากมีบั๊กอย่าง memory leak เข้ามา บริการทั้งหมดอาจได้รับผลกระทบ
    • หาก rollback build ที่ผิดพลาด จะกระทบความเร็วของทุกทีม ไม่ใช่แค่ทีมที่สร้างบั๊ก

ความเป็นอิสระที่ขอบเขตของบริการมอบให้

  • วิธีหนึ่งในการลดปัญหาจากการเติบโตของแบ็กเอนด์แบบโมโนลิธ คือการแบ่งแบ็กเอนด์ออกเป็นชุดของ บริการที่ deploy ได้อย่างอิสระ
  • บริการต่าง ๆ สื่อสารกันผ่าน API และ API จะสร้างขอบเขตที่ล่วงล้ำได้ยากกว่าขอบเขตของคอมโพเนนต์ภายในโปรเซสเดียวกัน
  • สถาปัตยกรรมนี้เรียกว่า microservices architecture แต่คำว่า micro อาจก่อให้เกิดความเข้าใจผิดได้
    • บริการไม่จำเป็นต้องเล็กเสมอไป
    • บริการที่มีฟังก์ชันน้อยเกินไปอาจสร้างภาระด้านปฏิบัติการมากกว่าประโยชน์ที่ได้รับ
  • ชื่อที่เหมาะสมกว่าอาจเป็น service-oriented architecture แต่ชื่อนี้ก็มีภาระจากอดีตติดมาด้วย

ความเร็วที่เกิดจากทีมเล็กและบริการอิสระ

  • เมื่อแบ่งแบ็กเอนด์เป็นบริการตามฟังก์ชันทางธุรกิจ แต่ละบริการสามารถพัฒนาและปฏิบัติการโดย ทีมเดี่ยวขนาดเล็ก ได้
  • ทีมขนาดเล็กมีประสิทธิภาพกว่า เพราะต้นทุนการสื่อสาร เพิ่มขึ้นเป็นกำลังสอง ตามขนาดทีม
  • เมื่อแต่ละทีมกำหนดตาราง release ของตนเองและควบคุม codebase ได้ การประสานงานระหว่างทีมจะลดลงและการตัดสินใจจะเร็วขึ้น
  • หาก codebase ของบริการมีขนาดเล็ก นักพัฒนาจะเข้าใจได้ง่ายขึ้น และเวลาปรับตัวของพนักงานใหม่ก็ลดลงด้วย
    • codebase ขนาดเล็กช่วยลดการชะลอตัวของ IDE และช่วยเพิ่มผลิตภาพของนักพัฒนา
  • ด้วยขอบเขตบริการที่ชัดเจน นักพัฒนาจึงต้องเข้าใจเพียงส่วนเล็ก ๆ ไม่ใช่ทั้งหมด เมื่อเปลี่ยนแปลงบางส่วนของแบ็กเอนด์
  • แต่ละบริการสามารถ scale ได้อย่างอิสระ และเลือก technology stack ให้เหมาะกับความต้องการของตนเองได้
    • ผู้ใช้ API ไม่ต้องสนใจว่าฟังก์ชันถูก implement ภายในอย่างไร
    • ทดลองและประเมินเทคโนโลยีใหม่ได้ง่ายโดยไม่กระทบพื้นที่อื่นของระบบ
  • ไมโครเซอร์วิสแต่ละตัวสามารถมี data model และ data store ของตนเองได้
    • นักพัฒนาสามารถเปลี่ยน schema ได้โดยไม่กระทบบริการอื่น

ต้นทุนจริงที่ไมโครเซอร์วิสเพิ่มเข้ามา

  • ไมโครเซอร์วิสเพิ่มชิ้นส่วนที่เคลื่อนไหวได้ให้กับทั้งระบบมากขึ้น และต้นทุนนี้ไม่ได้ฟรี
  • ต้นทุนของการยอมรับไมโครเซอร์วิสอย่างเต็มรูปแบบจะคุ้มค่าก็ต่อเมื่อสามารถ กระจายการตัดจำหน่าย ไปยังทีมพัฒนาหลายสิบทีมได้
  • ประสบการณ์การพัฒนา

    • การที่แต่ละบริการใช้ภาษา ไลบรารี และ data store ต่างกันเป็นไปได้ แต่จะทำให้การบำรุงรักษาแอปพลิเคชันยากขึ้น
    • หาก software stack แตกต่างกันโดยสิ้นเชิง นักพัฒนาจะย้ายจากทีมหนึ่งไปอีกทีมหนึ่งได้ยาก
    • หากต้องให้ฟังก์ชันร่วมที่ทุกบริการต้องใช้ เช่น logging จำเป็นต้องรองรับไลบรารีสำหรับทุกภาษาที่เลือกใช้
    • จำเป็นต้องมี การทำให้เป็นมาตรฐาน ในระดับหนึ่ง
    • สามารถส่งเสริมการใช้เทคโนโลยีบางอย่างอย่างหลวม ๆ ได้ โดยมอบประสบการณ์การพัฒนาที่ดีให้กับทีมที่ปฏิบัติตามพอร์ตโฟลิโอของภาษาและเทคโนโลยีที่แนะนำ
  • การ provisioning ทรัพยากร

    • เพื่อรองรับบริการอิสระจำนวนมาก ต้องสามารถสร้างเซิร์ฟเวอร์, data store และทรัพยากรเอนกประสงค์อื่น ๆ ได้อย่างง่ายดาย
    • ไม่ควรปล่อยให้แต่ละทีมสร้างวิธีการสร้างทรัพยากรของตนเองอย่างกระจัดกระจาย
    • ทรัพยากรที่ provision แล้วต้องถูกตั้งค่าอีกครั้ง และการจัดการสิ่งนี้ต้องใช้ automation จำนวนมาก
  • การสื่อสาร

    • การเรียกใช้งานระยะไกลมีต้นทุนสูง และนำเสนอรูปแบบใหม่ ๆ ที่ระบบอาจล่มได้
    • เพื่อเตรียมรับมือความล้มเหลว จำเป็นต้องมีกลไกป้องกัน เช่น timeout, retry และ circuit breaker
    • เพื่อลดการสูญเสียด้านประสิทธิภาพจากการสื่อสารผ่านเครือข่าย ควรใช้การประมวลผลแบบ asynchronous และ batching ด้วย
    • องค์ประกอบเหล่านี้เพิ่มความซับซ้อนของระบบ
    • โมโนลิธก็อาจพบปัญหาคล้ายกันจากการเข้าถึงของไคลเอนต์ระยะไกลและการใช้ API ของ third-party แต่ขนาดของปัญหาจะเล็กกว่า
  • การผสานรวม การส่งมอบ และการ deploy อย่างต่อเนื่อง

    • continuous integration ทำให้การเปลี่ยนแปลงโค้ดถูก merge เข้ากับ main branch หลังจากรัน build และ test อัตโนมัติแล้ว
    • เมื่อการเปลี่ยนแปลงโค้ดถูก merge แล้ว ควรถูก publish และ deploy อัตโนมัติไปยังสภาพแวดล้อมที่คล้าย production
    • ในสภาพแวดล้อมนั้น จะรัน integration test และ end-to-end test เพื่อตรวจสอบว่าไมโครเซอร์วิสไม่ได้ทำให้บริการอื่นที่พึ่งพามันเสียหาย
    • การทดสอบไมโครเซอร์วิสแต่ละตัวไม่ได้ยากกว่าการทดสอบโมโนลิธ แต่ integration test ของไมโครเซอร์วิสทั้งหมดนั้นยากกว่ามาก
    • เมื่อบริการแต่ละตัวโต้ตอบกัน อาจเกิดพฤติกรรมที่ละเอียดอ่อนมากและคาดไม่ถึงได้
  • งานปฏิบัติการ

    • ต่างจากโมโนลิธ การจัดทีมปฏิบัติการแยกให้แต่ละทีมที่รับผิดชอบบริการมีต้นทุนสูงกว่ามาก
    • ทีมที่พัฒนาบริการมักต้องรับหน้าที่ on-call ของบริการนั้นด้วย
    • ทีมต้องตัดสินใจในแต่ละ sprint ว่าจะให้ความสำคัญกับงานพัฒนาหรือภาระด้านปฏิบัติการก่อน และเกิดแรงเสียดทานระหว่างสองอย่างนี้
    • การ debug ความล้มเหลวของระบบก็ยากขึ้นด้วย
    • ไม่สามารถนำแอปพลิเคชันทั้งหมดขึ้นบนเครื่อง local แล้วสั่ง step ผ่าน debugger ได้
    • เมื่อมีชิ้นส่วนที่เคลื่อนไหวได้มากขึ้น รูปแบบความล้มเหลวก็มีมากขึ้น
    • logging และ monitoring ที่ดีในทุกระดับจึงสำคัญมากขึ้น
  • ความสอดคล้องในท้ายที่สุด

    • เมื่อแบ่งแอปพลิเคชันออกเป็นหลายบริการ data model จะไม่ได้อยู่ใน data store เดียวอีกต่อไป
    • การอัปเดตเรคคอร์ดใน data store ต่างกันแบบ atomic และรับประกัน strong consistency นั้นช้า แพง และ implement ได้ยาก
    • สถาปัตยกรรมแบบนี้โดยทั่วไปจำเป็นต้องยอมรับ ความสอดคล้องในท้ายที่สุด

ควรแยกเมื่อใด

  • การแบ่งแอปพลิเคชันออกเป็นบริการจะเพิ่มความซับซ้อนของระบบโดยรวมอย่างมาก
  • โดยทั่วไป ควรเริ่มจากโมโนลิธ และแยกออกก็ต่อเมื่อมีเหตุผลเพียงพอเท่านั้น
  • การกำหนดขอบเขตระหว่างบริการให้ถูกต้องเป็นเรื่องยาก ดังนั้นการย้ายขอบเขตไปมาในโมโนลิธเพื่อหาจุดที่เหมาะสมจึงง่ายกว่า
  • เมื่อโมโนลิธเติบโตเต็มที่พอและปัญหาจากการเติบโตเริ่มรุนแรงขึ้น ก็สามารถค่อย ๆ แยกไมโครเซอร์วิสออกมาทีละตัวได้
  • แนวทางเริ่มด้วยไมโครเซอร์วิสควรเริ่มเฉพาะเมื่อมีประสบการณ์อยู่แล้ว และได้สร้างแพลตฟอร์มสำหรับสิ่งนี้ไว้แล้ว หรือได้คำนึงถึงเวลาที่ต้องใช้ในการสร้างแพลตฟอร์มแล้วเท่านั้น

1 ความคิดเห็น

 
GN⁺ 2023-10-31
ความคิดเห็นจาก Hacker News
  • โดยรวมเห็นด้วยกับบทความ แต่ถ้ามองอีกด้าน ก็มีกรณีที่คุ้มจะรับต้นทุน: แยกปัญหาความเข้ากันได้ของ dependency จาก third-party เก่า ๆ ออกเป็นบริการต่างหากเพื่อเลี่ยงการรีแฟกเตอร์ที่เจ็บปวดหรือการเขียนใหม่เอง, ทำให้ข้อจำกัดการ deploy ของระบบแกนหลักที่ต้องมี availability สูงไม่ไปขวางการ deploy ของระบบอื่น, และสามารถแยกข้อจำกัดด้านการออกแบบที่ลูกค้ารายใหญ่เปลี่ยนไม่ได้ออกไปได้
    ไม่ได้หมายความว่าฟรี แต่หมายถึงเป็นต้นทุนที่คุ้มจะจ่ายเพื่อไม่ให้ monolith ที่เรียบง่ายกลายเป็นกองขยะ หรือถูกการ deploy ที่เสี่ยงทำให้สั่นคลอน หรือถูกผูกติดกับพาร์ตเนอร์ธุรกิจที่มี tech stack แย่กว่า

    • นี่เหมือนแค่การมี บริการสักหนึ่งหรือสองตัว มากกว่า ผมไม่คิดว่านั่นเท่ากับ “microservices”
      microservices ไม่ใช่แนวทางที่ทำให้คอมโพเนนต์ภายในกลายเป็น service โดยพื้นฐาน แทนที่จะเป็น library หรือ class หรอกหรือ?
    • ขอเสริมอีกอย่าง ข้อดีคือสามารถใช้ ภาษาโปรแกรมมิง ที่ต่างกันได้ง่าย ไม่ใช่ทุกภาษาจะเหมาะกับทุกปัญหา
      ถ้าเขียน service สำหรับ inference ของ machine learning ด้วย Python, UI ที่ render ฝั่ง server ด้วย Rails, และ service ที่มี I/O กับ concurrency เยอะด้วย Go ภาระเพิ่มเติมจากการแยกทั้งสามออกเป็นบริการต่างหากก็อาจสมเหตุสมผล
    • มีหลายวิธีในการออกแบบให้ดี แต่นั่นไม่ได้หมายความว่าควรรีบนำ microservices มาใช้
      แม้ผมจะค่อนข้างชอบ microservices แต่การ optimize เร็วเกินไปและการขยายเร็วเกินไปก็อาจแย่พอ ๆ กับหนี้เทคนิคที่เกิดขึ้นเมื่อภายหลังต้อง optimize ไปในทิศทางที่ต่างออกไปโดยสิ้นเชิง
    • ข้อ 1 ผมไม่เข้าใจว่าทำไมการแยกออกเป็น service ต่างหากถึงดีกว่าการห่อด้วย interface ที่มี API ชัดเจน
    • กรณีแบบนั้นน่าจะเกิดกับ น้อยกว่า 10% ของ codebase ทั้งหมดเท่านั้น
  • เวลาที่ควรแยกอะไรบางอย่างออกเป็น service ต่างหาก คือเมื่อเกิดปัญหาที่แก้ด้วยวิธีที่รับมือไหวไม่ได้หากไม่ทำเช่นนั้น
    การเพิ่ม ความซับซ้อนทางสถาปัตยกรรม เพื่อบังคับ boundary ไม่ใช่คำตอบของการขาดวินัยในองค์กร แต่บริษัทเทคโนโลยีขนาดกลางกลับปฏิบัติกับมันแบบนั้นอยู่ตลอด ถ้าเกิดปัญหาเพราะ domain boundary อ่อน การแยก service ก็มีแนวโน้มว่าจะไม่ช่วยแก้ได้ดี

    • จุดรับผิดชอบสุดท้าย คือกลยุทธ์ที่เลื่อนการตัดสินใจออกไป จนถึงช่วงเวลาที่ต้นทุนของการเลื่อนต่อไปสูงกว่าต้นทุนของการตัดสินใจตอนนี้”
      อ้างอิง: https://www.oreilly.com/library/view/software-architects-han...
    • แม้จะบอกว่าการเพิ่มความซับซ้อนทางสถาปัตยกรรมเพื่อบังคับ boundary ไม่ใช่คำตอบของการขาดวินัยในองค์กร แต่ในความเป็นจริงเราก็ทำแบบนั้นอยู่เสมอ
      CI/CD ที่บล็อก PR จนกว่า test จะผ่าน ก็เป็นคำตอบเชิงเทคนิคที่มีต้นทุนสูงเพื่อแก้ปัญหาวินัยในองค์กรเหมือนกัน
    • ฟังดูเหมือน SISP ที่เป็นวิธีแก้กำลังตามหาปัญหา หรือเหมือนการโยนวิธีแก้ใส่ปัญหาโดยไม่เข้าใจรากสาเหตุ
    • บน HN ดูเหมือนมีมีมแพร่หลายว่า microservices ไม่มีทางมีเหตุผลรองรับได้เลย ซึ่งแปลกเพราะทั้งอุตสาหกรรมไม่ได้เป็นแบบนั้น
      ทุกคนยอมรับว่าการให้บริการภายนอกทำ domain name resolution นั้นสมเหตุสมผล และแทบไม่มีใครรวม recursive DNS resolver กับ cache เข้าไปใน monolith แต่การแยกความรับผิดชอบแบบเก่าแก่นี้กลับไม่ค่อยถูกยอมรับเป็นตัวอย่าง
    • ผมคิดเสมอว่านี่เป็นตัวอย่างที่ดีที่สุดของ การลงมือทำแบบเลื่อนออกไป น่าทึ่งที่บริษัทต่าง ๆ ทำผิดกันบ่อยแค่ไหน
      ปัญหาดูเหมือนอยู่ที่ว่ายิ่งเป็นพนักงานที่ “ดี” ก็ยิ่งมีแรงจูงใจมากเกินไปที่จะพิสูจน์คุณค่า จนนำไปสู่การออกแบบเกินจำเป็น และสุดท้ายก็กลายเป็นนรก
  • microservices
    grug สงสัยว่าทำไมสมองใหญ่ถึงหยิบปัญหาที่ยากที่สุดอย่างการแยกระบบให้ถูกต้อง แล้วใส่ network call เข้าไปอีก
    สำหรับ grug มันดูสับสนมาก
    https://grugbrain.dev/#grug-on-microservices

    • ดูเหมือน grug ไม่เคยเจอสถานการณ์ที่ทีมขนาดใหญ่เหยียบ domain และ data model ของกันและกัน, ยึดติดกับ implementation เฉพาะ, และการปล่อยฟีเจอร์ระดับทีมต้องใช้ความพยายามเชิงองค์กรมหาศาล microservices และแนวทางที่แต่ละทีมควบคุม data store ของตัวเองช่วยเพิ่ม ความเร็วของทีม
      “เราอยากย้ายจาก MySQL ไป DynamoDB เพื่อ modernize แนวทางการเข้าถึง FOO_TABLE เพราะ SCALE_REASONS แต่ 32 จาก 59 ทีมเข้าถึง FOO_TABLE โดยตรง หรือเรียก private method ของ class เราโดยตรง เพราะ priority ต่างกัน ทีมเหล่านั้นจึงไม่สามารถย้ายไปใช้ FOO_SERVICE หรือเปลี่ยนวิธี query ให้ใช้ sharded table ได้ ตอนนี้การขยาย FOO_TABLE กลายเป็นงานหลายไตรมาส และต้องเปิดทางให้ทีมต่าง ๆ อัปเดตอย่างช้า ๆ อีก 1–2 ปีคงจะ retire วิธีเก่าที่กำลังลุกเป็นไฟอยู่ตอนนี้ได้ ระหว่างนั้นก็สนุกกับ on-call ไปก่อน”
      ถ้าเป็น microservices ทีมจะรู้ว่า table ขยายไม่ได้ แต่ข้อมูลถูกให้บริการผ่าน API พวกเขาวางแผนและทำ migration ใน sprint ถัดไป และผู้ใช้ API ก็จะรู้สึกว่ามันเร็วขึ้นมาก
    • static type ไม่จำเป็นสำหรับ autocomplete JetBrains มี IDE ที่ autocomplete ได้แม้ในภาษาอย่าง Ruby หรือ Python
      ถ้าเปิด REPL ของ Ruby รุ่นใหม่ คุณจะได้หลายอย่างด้าน autocomplete และ syntax checking ที่คาดหวังจาก IDE ของภาษาที่มี static type
    • การนำ network call เข้ามาเป็นสิ่งที่ทรงพลังมาก ในทางปฏิบัติมันสร้าง ขอบเขตที่ข้ามไม่ได้ ซึ่งถูกบังคับทางกายภาพ และบริการสองตัวต้องปฏิบัติต่อกันเสมือนแยกจากกัน
      การแยกกันอยู่ไม่ใช่เรื่องที่ควรมองเบา ๆ แต่เป็นหนึ่งในความสามารถที่ทรงพลังที่สุดที่ใส่ลงในซอฟต์แวร์ได้ มันสามารถเพิ่ม performance, สร้าง failure boundary, และให้ security boundary ได้
      นี่เหมือนแนวคิดพื้นฐานของ actor model แทนที่คอมโพเนนต์สองตัวจะแชร์และเปลี่ยน memory ของกันและกัน ระบบที่ถูกแยกสองระบบอย่าง actor หรือ microservice จะสื่อสารกันผ่าน protocol ที่กำหนดไว้เท่านั้น
  • ผมมองว่าผู้คนเข้าใจ modularity ผิดไป แม้ modularity จะสำคัญ แต่ก็เริ่มคิดว่ายังมีหลักการสถาปัตยกรรมที่สำคัญอีกอย่างคือ หลักการวังวนเดียว
    ในระบบซอฟต์แวร์ วังวนคือ loop ของ data flow ถ้าส่งข้อมูลไปที่ใดที่หนึ่งแล้วได้รับผลลัพธ์ที่ประมวลผลแล้วกลับมา หรือได้รับผลกระทบจากข้อมูลนั้นไม่ทางใดก็ทางหนึ่ง ก็มีวังวนอยู่ ตัวแปรที่เปลี่ยนค่าได้เป็นตัวอย่างของวังวนขนาดเล็กมาก
    หลักการวังวนเดียวหมายความว่า ในอุดมคติระบบซอฟต์แวร์ควรมีวังวนเพียงหนึ่งเดียว หรือพูดอีกอย่างคือทุกองค์ประกอบควรรู้ว่าวังวนของตัวเองหมุนไปทางไหน
    เมื่อนำโมดูลที่ประกอบกันเป็นสองวังวนมารวมกัน ถ้าทิศทางของวังวนตรงกัน การประกอบจะง่าย แต่ถ้าทิศทางตรงข้ามกัน จะยุ่งยากเพราะต้องตัดสินใจว่าทิศทางของวังวนใหม่ควรเป็นอย่างไร ดังนั้นดีที่สุดคือให้วังวนของทุกโมดูลมีทิศทางเดียวกันและรวมเป็นวังวนเดียว
    หลักการนี้เป็นการทำให้แนวคิดอย่าง Flux pattern, CQRS, event sourcing และ immutability กลายเป็นแนวคิดทั่วไป

    • เป็นประเด็นที่ดีมาก และน่าจะเขียนบทความได้หลายตอนด้วยหัวข้อนี้อย่างเดียว บริการ A อาจเรียก B, B เรียก C แล้ว C ก็อาจเรียก A กลับมาอีก ซึ่งจะทำให้เกิดปัญหา
      หรือเกิดเรื่องไม่คาดคิดใน A จนทำให้ C ติดขัดก็ได้ ในอุดมคติ parent ควรเรียกเฉพาะ child และไม่ควรพึ่งพา parent ถ้าทำตรงนี้พลาด สถาปัตยกรรมก็พลาดด้วย
    • ชอบแนวคิดนี้
      เวลาอ่าน architecture diagram ครึ่งหนึ่งของงานคือการดูให้ออกว่าอะไรเป็น async/sync, push/pull และทิศทางกับลำดับของ flow เป็นอย่างไร
      Enterprise Service Bus และ message queue ดูเหมือนเป็นวิธีแก้สำหรับการสร้างวังวนในจุดที่ทิศทางของทั้งสองฝั่งไม่ตรงกัน
    • แนวคิดเรื่องวังวน นี้น่าสนใจ อยากรู้ว่ามีหนังสือหรือแหล่งข้อมูลออนไลน์ที่น่าศึกษาไหม
    • ผมไม่ค่อยเข้าใจย่อหน้ารองสุดท้าย โดยเฉพาะส่วนเหตุผล อยากทราบว่าช่วยอธิบายให้ละเอียดขึ้นได้ไหม
  • มีท่อนหนึ่งบอกว่าปัญหาคือ “จำนวนไลบรารีที่ต้องซัพพอร์ตเพื่อให้มีฟังก์ชันพื้นฐานที่ทุกบริการต้องใช้ เช่น logging แยกตามแต่ละภาษาที่นำมาใช้”
    นี่คือ เหตุผลใหญ่ที่สุด ที่เราหยุดใช้ microservices ด้วยเครื่องมือในปี 2023 โดยเฉพาะในสภาพแวดล้อม cloud-native ล้วน ๆ หรือ FaaS ที่ scale ได้ไม่จำกัด การใช้พลังสมองไปกังวลเรื่องนี้เป็นการสิ้นเปลืองอย่างสมบูรณ์ ยิ่งกับฐานลูกค้าอย่างธนาคารและสถาบันการเงินที่ตรวจสอบ dependency จาก third-party ทุกตัวอย่างเข้มงวดก็ยิ่งเป็นเช่นนั้น
    ตอนนี้เรารันด้วย deployment แบบไบนารี .NET monolith เดียว ขนาดประมาณ 250MB เมื่อบีบอัดด้วย gzip และยังไม่มีสัญญาณว่าจะเริ่มแตกร้าวเลย ถ้าคุณกำลังกังวลอย่างละเอียดว่า “exe ของผมไม่อยู่ใน L2 อีกต่อไปแล้ว” กับ deployment SaaS ขนาด 10–100MB ก็สบายใจได้ เส้นทางซอฟต์แวร์แบบ monolith ของคุณยังไม่ทันได้เริ่มด้วยซ้ำ
    ถ้าถึงเวลาต้องเขียนกองขยะสักกองพวกนี้ใหม่ การที่ทุก commit อยู่ในที่เดียวและสอดคล้องกันทั่วทั้งระบบจะง่ายกว่ามากไม่ใช่หรือ?

    • ผมมองว่านี่เป็น false dichotomy องค์กร microservices ส่วนใหญ่ที่ผมเจอมีภาษาที่อนุมัติให้ใช้แค่ 2–3 ภาษา ด้วยเหตุผลทำนองนี้ และถ้าทีมแสดงให้เห็นว่าไม่มีทางเลือกอื่น leadership ก็อาจอนุญาตข้อยกเว้นได้
      microservices ไม่ได้แปลว่าเป็นแดนไร้กฎหมาย และไม่ได้หมายความว่าทุกทีมจะทำอะไรก็ได้โดยไม่คำนึงถึงทั้งองค์กร ควรมีและสามารถมีกฎเพื่อรักษาความสอดคล้องระหว่างทีมในระดับหนึ่ง
    • จริง ๆ แล้วเรื่องนี้ง่ายกว่ามากใน microservices เพราะมี interface ที่ชัดเจน ให้ต้องซัพพอร์ต และโค้ดไม่ได้พันกันกับส่วนที่เหลือของ monolith เหมือนผมเปียฝรั่งเศส
      โค้ดที่ดีที่สุดคือโค้ดที่ทิ้งแล้วเขียนใหม่ได้ง่าย ในความเป็นจริง โค้ดเก่าจะผ่านมือคนมากขึ้นและแย่ลงเรื่อย ๆ และที่สำคัญที่สุดคือไม่มีใครอยากดูแลมัน
    • สำคัญกว่าคือมีนักพัฒนากี่คนที่ทำงานกับ monolith นั้น ขนาดไบนารีโดยทั่วไปไม่ใช่เหตุผลที่ทีมเริ่มแยก monolith ออกเป็นส่วน ๆ
    • ประโยคที่ว่า “เพราะทุกอย่างอยู่ในที่เดียว แต่ละ commit จึงสอดคล้องกันทั่วทั้งระบบ มันไม่ง่ายกว่ามากหรือ?” ทำให้รู้สึกตลกนิด ๆ เสมอ ทั้งสองฝ่ายพูดแบบนี้กันบ่อย แต่ดูเหมือนไม่ค่อยคิดจากมุมของอีกฝ่าย
      ฝั่งที่ชอบ microservices บอกว่ามันเปลี่ยนหรือเขียนใหม่ได้ง่าย เพราะมี สัญญาที่ชัดเจน ว่าบริการควรทำงานอย่างไร และ codebase ของบริการนั้นเล็กกว่ามาก
      ฝั่ง monolith บอกว่ามันเปลี่ยนหรือเขียนใหม่ได้ง่าย เพราะทุกอย่างเป็นกลุ่มด้ายก้อนใหญ่ และถ้าจะเปลี่ยนด้ายเส้นหนึ่งก็แค่ต้องรู้จุดที่เส้นนั้นไปพันกับเส้นอื่น
      ไม่รู้ว่าใครถูก สำหรับคนที่อยู่มานานและส่อง codebase ด้วยกล้องจุลทรรศน์มาหลายปี monolith อาจดีกว่า ส่วนคนอื่น ๆ microservices อาจดีกว่า
    • ไม่มีใครบอกว่า microservices ทั้งหมดห้ามใช้ ภาษา เดียวกัน
  • จากมุมมอง release management การเปลี่ยนจาก monolith ไปเป็น microservices มักเกิดขึ้นด้วยเหตุผลที่ผิด
    เหตุผลเดียวที่ดูสมเหตุสมผลจริง ๆ สำหรับการเปลี่ยนแปลงคือปัญหา scalability จากคอขวดด้านประสิทธิภาพ ที่เหลือก็แค่การย้ายความซับซ้อนของการพัฒนาซอฟต์แวร์ไปเป็นการดูแลรักษาระบบ
    แน่นอนว่านักพัฒนาน่าจะชอบ เพราะภาระใหญ่ในการต้องประสานกับทีมอื่นลดลง แต่การทำให้ชัดเจนว่าฟีเจอร์ถูก implement เมื่อไรและอย่างไร ถูกทดสอบอย่างถูกต้องข้าม microservices หรือไม่ และถูกเปิดใช้งานกับจัดการแบบรวมศูนย์ใน production อย่างไร จะยากขึ้นมากหากการสื่อสารระหว่างทีมพัฒนายังไม่ mature พอ และความไม่ mature นั่นเองมักเป็นเหตุผลจริงตั้งแต่แรกที่ทำให้ต้องแยก monolith

    • มีเหตุผลที่เหมาะสมมากมาย และเหตุผลที่ผิดก็มีมากมายเช่นกัน ถ้ามี stakeholder หลายฝ่าย ความต้องการทางธุรกิจเปลี่ยนตลอด และมีนักพัฒนามากกว่า 10 คน ก็อาจมีเหตุผลเพียงพอที่จะมีหน่วยที่ deploy, test และ release ได้อย่างอิสระ
      ในทางกลับกัน ถ้ามีนักพัฒนาน้อยและ context ถูกกำหนดไว้ชัดเจน การต้องจัดการ microservices หลายตัวเป็นเรื่องเจ็บปวด
      คำว่า “ที่เหลือก็แค่การย้ายความซับซ้อนของการพัฒนาซอฟต์แวร์ไปเป็นการดูแลรักษาระบบ” ฟังดูสมเหตุสมผลถ้าซอฟต์แวร์กำลังอยู่ระหว่างพัฒนาอย่างจริงจัง การพัฒนามีต้นทุนสูง ค่าใช้จ่ายในการดูแลระบบกระจายของทีมใหญ่อาจต่ำกว่าต้นทุนการพัฒนา monolith ขนาดมหึมาก็ได้ สุดท้ายก็ขึ้นอยู่กับสถานการณ์
    • นี่ก็เป็น เหตุผลเดียว ที่เราต้องการแยกส่วนนี้ของแอปออกเป็นองค์ประกอบต่างหาก มันใหญ่เกินไปจนดูแลรักษาอย่างมีประสิทธิภาพได้ยาก และเราจะปล่อยส่วนที่เหลือของแอปไว้เหมือนเดิม
  • บทความ “ต้นทุนของ Monolith” อยู่ที่ไหน? ตรงนี้ไม่เห็นมี ทุกคนกำลังทำ microservice แบบไม่รู้เรื่อง จึงเห็นแต่ปัญหานั้นเท่านั้น
    ถ้าทุกคนกำลังทำ monolith แบบไม่รู้เรื่อง ก็คงเห็นบทความ “monolith แย่” โผล่มาเป็นจำนวนมหาศาล
    ผู้คนไม่เข้าใจว่าท้ายที่สุดทั้งสองระบบสร้างปัญหาในปริมาณเท่ากัน ต่างกันเหมือนให้ช้างหนึ่งตัวทำงานกับให้หนู 1,000 ตัวทำงาน ทั้งคู่มีปัญหา เพียงแต่ปัญหาต่างกัน
    การเลือกอย่างใดอย่างหนึ่งไม่ได้ทำให้ปัญหาหายไป ไม่ว่าเลือกทางไหนก็มีปัญหา และต้องเลือกสักอย่างแล้วหาทางแก้ ถ้าบริษัทไหนมีอุปสรรคใหญ่ที่สุดคือ “ใช้สถาปัตยกรรมแบบไหน” ผมก็อยากไปทำงานที่นั่น

    • เหตุผลที่ตอนนี้เราหมกมุ่นกับ microservice มากเกินไป ก็เพราะในอดีตมีบทความ monolith แย่ อยู่ราว ๆ 8 ล้านล้านบทความ ตอนนี้เป็นปฏิกิริยาย้อนกลับจากการตระหนักว่าความคุ้มค่าต่อค่าใช้จ่ายไม่ได้เหมาะกับพวกเขา
  • ถ้า monolith ถูกแยกส่วนไว้อย่างดี มันต่างจาก microservice ที่ deploy อยู่ในตำแหน่งเดียวกันตรงไหน? ก็คงต่างกันแค่อินเทอร์เฟซ การเรียกฟังก์ชันกลายเป็น RPC และยอมรับ overhead เล็กน้อยเพื่อข้อดีของการจัดการคอมโพเนนต์แยกกัน เช่น การแพตช์
    microservice แบบกระจายกับ microservice ที่อยู่ตำแหน่งเดียวกันต่างกันตรงไหน? การ deploy ซับซ้อนกว่า แต่สามารถวาง process บนฮาร์ดแวร์ที่เหมาะสมกว่าได้อย่างชาญฉลาด failure mode เพิ่มขึ้น แต่ได้ fault tolerance ที่สูงขึ้น
    เรื่องนี้ไม่มีคำตอบครอบจักรวาล ถ้าต้องการข้อดีก็จ่ายต้นทุนไป ผมมองว่าการถกเถียง microservice ปะทะ monolith แบบนี้มักเกิดจากการใช้แพตเทิร์นผิดที่ ขาดเครื่องมือที่จะทำให้ชีวิตง่ายขึ้น หรือส่วนใหญ่คือการปล่อยซอฟต์แวร์อายุ 10 ปีไว้โดยไม่ refactor และออกแบบใหม่ต่อเนื่อง จนทำงานด้วยแล้วทรมานไม่ว่าสถาปัตยกรรมตั้งต้นจะเป็นแบบไหน

    • การย้ายจาก monolith ไป microservice ไม่ใช่การเปลี่ยนแปลงเล็กน้อย ต่อให้แยกส่วนไว้ดีแค่ไหนตั้งแต่แรกก็ตาม แน่นอนว่าถ้าออกแบบไม่ดี การย้ายก็จะยิ่งยากขึ้น
      คำว่า “การเรียกฟังก์ชันกลายเป็น RPC” ฟังดูง่าย แต่ทันทีที่เป็นแบบนั้น client app ต้องจัดการ execution path ทั้งหมด เช่น เข้าถึง DNS server ไม่ได้, resolve hostname ล้มเหลว, เข้าถึง host ไม่ได้, connection ถูกปฏิเสธ, authentication ถูกปฏิเสธ, ใบรับรอง TLS ไม่น่าเชื่อถือ, เกิน rate limit, 301 ย้ายถาวร, ผลลัพธ์ยังไม่พร้อม, timeout
      แม้แอปที่แยกส่วนดีแล้ว หากต้องจัดการสิ่งเหล่านี้ให้แข็งแรง ก็มีแนวโน้มสูงว่าต้องออกแบบใหม่ให้รองรับ asynchronous queue, การจำกัดอัตราคำขอ, result cache, retry แบบ backoff พร้อม logging, client credentials / trust store / resource URL ที่ตั้งค่าได้ และการ log ความล้มเหลว
      ในการเรียกฟังก์ชันแบบ local ยังต้องเพิ่มฟังก์ชัน RPC และพารามิเตอร์เพิ่มเติมเพื่อส่งข้อมูลที่เคยมีอยู่ใน context ด้วย นอกจากนี้ UI ของ monolith อาจต้องสื่อสาร network latency และความล้มเหลวให้ผู้ใช้รับรู้ ซึ่งก่อนหน้านี้ไม่เคยจำเป็น
      แม้แต่ monolith ที่ทำมาดีที่สุด ก็ยังต้องใช้ความพยายามมากพอสมควรในการ refactor ให้เป็น microservice
    • เบื้องหลังคำว่า “การเรียกฟังก์ชันกลายเป็น RPC” จะมี failure mode ใหม่ ๆ และงานท่อประปาเพิ่มขึ้นตามมา
      RPC call นี้ retry อย่างปลอดภัยได้ไหม? แล้ว exponential backoff ล่ะ? API load balancer ทำงานถูกต้องไหม ถ้าอย่างนั้นก็ต้องมี monitoring ด้วย จะ trace ระหว่าง microservice อย่างไร ตอนนี้ก็ต้องมีของอย่าง OpenTelemetry
      การ debug ด้วย breakpoint ในสถาปัตยกรรม microservice ยากขึ้นแค่ไหน? จะ rollback database transaction ระหว่าง microservice สองตัวอย่างไร? การเรียกฟังก์ชันธรรมดา ๆ จู่ ๆ ก็ซับซ้อนขึ้นมาก
    • เวลาพูดถึง microservice ฝ่ายสนับสนุนแทบจะลืมส่วน micro ซึ่งเป็นแก่นของแนวคิดนี้เสมอ ตัว service เองทุกคนรู้จักอยู่แล้ว และถ้าจำเป็นก็สร้างได้ ไม่ใช่แนวคิดที่ต้องโน้มน้าวกัน
      ทุกครั้งที่ปกป้องแนวคิดนี้ มักไม่ได้นับ fragmentation ที่เกิดจากข้อกำหนดว่า service ต้องเล็กมาก ๆ เข้าไปด้วย พูดให้ชัดขึ้น ถ้ามี monolith หนึ่งตัวกับ microservice หนึ่งตัว นั่นไม่ใช่ microservices คำว่า microservice สื่อโดยนัยว่าทุกอย่างถูกแยกเป็น service เล็ก ๆ และไม่มี monolith
      เหตุผลส่วนใหญ่ที่สนับสนุน microservice จะพังลงทันทีเมื่อเข้าใจว่ามันต้องเป็น “micro” จริง ๆ และถ้าละทิ้งข้อกำหนดนั้น ก็ไม่เหลือข้อเสนอใหม่หรือข้อเสนอที่ลึกซึ้งอะไรเลย
    • จากมุมมองนักพัฒนา ความแตกต่างอยู่ที่อินเทอร์เฟซ มีเครื่องมือสารพัดที่ช่วยรักษา monolith ให้ synchronize และถูกต้อง แต่เครื่องมือที่ช่วยเรื่อง inter-process communication ไม่สามารถทำ static evaluation ได้ และก็ไม่ได้ interactive เลย
      จากมุมมอง operations คำว่า “การ deploy ซับซ้อนกว่า” เป็นการพูดเบาไปมาก แต่ละ package คือสิ่งที่ต้องจัดการแยกกัน โดยมีลักษณะและปัญหาของตัวเอง
    • จะสูญเสีย atomic transaction ไป ด้วยเหตุนี้พฤติกรรมทางธุรกิจทั้งหมดจึงซับซ้อนขึ้นอีกหลายลำดับขั้น
  • คล้ายกับที่ frontend developer คิดว่าไม่สามารถสร้าง “เว็บแอปสมัยใหม่” ได้หากไม่มี frontend framework หรือ library อย่าง React/Vue/Svelte ทุกวันนี้ดูเหมือนจะมีความคิดว่า “monolith” เท่ากับการรันก้อนทาร์ขนาดมหึมาน่ากลัวเป็น single instance จึง “scale ไม่ได้” ซึ่งไม่สมเหตุสมผลเลย
    อีกอย่างที่เห็นคือปริมาณโค้ดรวมจะมากขึ้นมาก และ service ส่วนใหญ่ประกอบด้วย โค้ด business/domain 20% กับ การจัดการส่งรับข้อความผ่านเครือข่าย 80% ต่อให้ซ่อนอย่างไรก็ยังอยู่ตรงนั้น และต้องจัดการ network ไม่ทางใดก็ทางหนึ่ง
    เช่นเดียวกับความบ้าคลั่งฝั่ง frontend ลัทธิ microservice นี้จะจบลงก็ต่อเมื่อเศรษฐกิจพังจนไม่มีเงินค้ำจุนหอคอยบาเบลแห่งหายนะเหล่านี้
    เสริมว่า microservice ก็มีที่ที่เหมาะจะใช้ สำหรับบางบริษัทที่ได้คุณค่ามากกว่าต้นทุนนั้นถือว่าเหมาะ

    • สิ่งที่คนมักพลาดเกี่ยวกับ microservice คือเป้าหมายโดยทั่วไปไม่ได้อยู่ที่การแก้ปัญหาทางเทคนิค แต่อยู่ที่การแก้ ปัญหาองค์กรและคน
      มีข้อดีทางเทคนิคอย่างการ scale แยกตาม service แต่ข้อดีที่ใหญ่ที่สุดคือขอบเขตความเป็นเจ้าของที่ชัดเจน และการป้องกัน coupling ที่มากเกินไปโดยไม่จำเป็น
      ถ้าทีมเล็ก ๆ กำลังทำผลิตภัณฑ์เดียว microservice อาจยังไม่มีปัญหาองค์กรให้แก้ จึงอาจไม่จำเป็น ในกรณีนั้น microservice คือการ optimize ก่อนเวลาอันควร และเป็นการจ่ายต้นทุนเพื่อแก้ปัญหาที่ยังไม่มี
    • React/Vue/Svelte ไม่ใช่ framework แต่เป็น view library และผมจะยืนกรานจุดยืนนี้จนถึงที่สุด
      ยิ่งไปกว่านั้น ความซับซ้อนของ frontend ล้วนอยู่ในหัวทั้งนั้น: https://bower.sh/front-end-complexity
    • เห็นด้วยว่าเว็บไซต์จำนวนมากใช้ JavaScript และ framework มากเกินไป แต่เมื่อสร้างเว็บแอประดับ desktop ก็สงสัยว่าควรใช้อะไร เพื่อไม่ให้กลายเป็นความยุ่งเหยิงมหึมา หรือสุดท้ายต้องประดิษฐ์แนวคิดที่มีอยู่แล้วใน framework เหล่านี้ขึ้นมาใหม่
  • บทความดูเหมือนตั้งสมมติฐานว่าองค์กรพัฒนาซอฟต์แวร์พังอย่างสิ้นเชิงและควบคุมไม่ได้ เช่น ระหว่างสปรินต์ยังตัดสินใจไม่ได้ว่าจะทำอะไร แอบนำไลบรารีของบุคคลที่สามและภาษาที่ไม่รู้จักเข้ามา และแอบ deploy การเปลี่ยนแปลงที่ไม่ compatible ขึ้นโปรดักชันเงียบ ๆ
    ถ้านักพัฒนาไม่ได้โง่ ไมโครเซอร์วิสก็น่าจะง่ายกว่าด้วย

    • น่าเสียดายที่กรณีที่มีคนโง่อยู่ในทีม หรือทั้งทีมที่ทำโปรเจกต์เดียวกันเป็นคนโง่กันหมดนั้นพบได้ค่อนข้างบ่อย
      ผมมั่นใจว่าหากได้ทำงานในทีมที่ประกอบด้วยนักพัฒนาที่ฉลาดและมีความสามารถ ไมโครเซอร์วิสก็คงง่ายกว่า แน่นอนว่าแบบนั้นทุกอย่างก็คงง่ายขึ้นด้วย
    • นักพัฒนาทุกคนในช่วงไม่กี่เดือนแรกของงานใหม่ย่อมเป็นคนโง่อย่างเลี่ยงไม่ได้ เพราะต้องใช้เวลาในการดูดซับข้อมูลที่จำเป็นเพื่อทำความเข้าใจว่าระบบเดิมทำงานอย่างไร