Style Specifics (รายละเอียดเฉพาะของสไตล์)
หัวข้อต่อไปนี้ แม้จะไม่ครอบคลุมทุกแง่มุม แต่ก็อธิบายลักษณะสำคัญบางส่วนของโทโพโลยี microservices รวมถึงองค์ประกอบบางอย่างที่มีเฉพาะใน microservices เท่านั้น
Bounded Context (ขอบเขตบริบท)
มาเจาะลึกแนวคิดเรื่อง bounded context ซึ่งเรากล่าวถึงไปแล้วว่าเป็นปรัชญาขับเคลื่อนของ microservices กันต่อ แต่ละ service จำลองฟังก์ชัน ซับโดเมน หรือ workflow เฉพาะอย่างหนึ่ง ดังนั้น bounded context แต่ละอันจึงประกอบด้วยทุกสิ่งที่จำเป็นต่อการทำงานภายในฟังก์ชันหรือซับโดเมนนั้น— รวมถึง service ที่ประกอบด้วย logical component และคลาสต่าง ๆ สคีมาฐานข้อมูล และฐานข้อมูลที่ service นั้นต้องใช้ในการทำงาน แต่ละ service ถูกออกแบบมาให้แทนซับโดเมนหรือฟังก์ชันหนึ่งอย่างเฉพาะเจาะจง
ปรัชญานี้เป็นตัวขับเคลื่อนการตัดสินใจหลายอย่างของสถาปนิกใน microservices ตัวอย่างเช่น ใน monolith นักพัฒนามักจะแชร์คลาสร่วมกัน เช่น Address ระหว่างส่วนต่าง ๆ ของแอปพลิเคชันที่ไม่เกี่ยวข้องกัน แต่เนื่องจากสถาปัตยกรรม microservices พยายามหลีกเลี่ยง coupling สถาปนิกที่สร้างระบบด้วยสไตล์นี้จะเลือกความซ้ำซ้อนแทนการ couple เพื่อให้โค้ด ทั้งหมด อยู่ภายใน bounded context ของฟังก์ชันหรือซับโดเมนนั้น
Microservices นำแนวคิดของสถาปัตยกรรมที่แบ่งตามโดเมน (domain-partitioned) ไปสู่ขั้นสุด ในหลาย ๆ แง่ สถาปัตยกรรมนี้คือรูปธรรมของแนวคิดเชิงตรรกะใน domain-driven design
Granularity (ความละเอียดของ service)
สถาปนิกที่ออกแบบ microservices มักจะดิ้นรนเพื่อหา granularity ที่เหมาะสม และสุดท้ายก็มักทำให้ service เล็กเกินไป เพราะตีความคำว่า micro ตามตัวอักษรมากเกินไป จากนั้นก็ต้องมาสร้าง communication link เชื่อมระหว่าง service เพื่อให้งานสำเร็จ ซึ่งขัดกับเจตนารมณ์เดิม และสุดท้ายก็กลายเป็น Big Ball of Distributed Mud
คำว่า microservice คือฉลาก ไม่ใช่คำอธิบาย
Martin Fowler
พูดอีกอย่างคือ ผู้ริเริ่มคำนี้จำเป็นต้องเรียกสไตล์ใหม่นี้ว่า อะไรสักอย่าง และพวกเขาเลือกคำว่า microservices เพื่อสร้างความแตกต่างจากสถาปัตยกรรมสไตล์ที่ครองตลาดในตอนนั้น (ราวปี 2007) นั่นคือ service-oriented architecture ซึ่งอาจถูกเรียกว่า "gigantic services" ก็ได้ อย่างไรก็ตาม นักพัฒนาจำนวนมากตีความคำว่า microservices เป็นเหมือนบัญญัติ ไม่ใช่คำอธิบาย จึงสร้าง service ที่ละเอียดเกินไป
จุดประสงค์ของขอบเขต service ใน microservices คือการจับภาพโดเมนหรือ workflow หนึ่ง ๆ ในบางแอปพลิเคชัน ขอบเขตตามธรรมชาติของบางส่วนในระบบอาจมีขนาดใหญ่ เพียงเพราะกระบวนการทางธุรกิจบางอย่างผูกติดกันแน่นกว่าส่วนอื่น ต่อไปนี้คือแนวทางที่สถาปนิกใช้ช่วยหาขอบเขตที่เหมาะสม:
จุดประสงค์ (Purpose)
ขอบเขตที่ชัดเจนที่สุดอ้างอิงจากแรงบันดาลใจของสถาปัตยกรรมสไตล์นี้ นั่นคือ problem domain โดยหลักการแล้ว แต่ละ microservice ควรมีความเชื่อมโยงกันเชิงฟังก์ชัน (functionally cohesive) โดยรับผิดชอบพฤติกรรมสำคัญเพียงหนึ่งอย่างในนามของแอปพลิเคชันโดยรวม
ธุรกรรม (Transactions)
Bounded context คือ workflow ทางธุรกิจ และบ่อยครั้งที่เอนทิตีซึ่งต้องทำงานร่วมกันในธุรกรรมหนึ่ง ๆ จะบอกเป็นนัยถึงขอบเขต service ที่ดี เนื่องจากธุรกรรมมักก่อปัญหาใน distributed architecture การออกแบบระบบโดยตั้งเป้าหลีกเลี่ยงธุรกรรมข้าม service มักให้ผลลัพธ์การออกแบบที่ดีกว่า
Choreography (การประสานงานแบบไม่มีศูนย์กลาง)
กลุ่ม service ชุดหนึ่งอาจแยกโดเมนออกจากกันได้ดีเยี่ยม แต่ต้องแลกมาด้วยการสื่อสารจำนวนมากเพื่อให้ทำงานได้ สถาปนิกอาจพิจารณารวม service เหล่านี้กลับเข้าเป็น service ที่ใหญ่ขึ้น เพื่อลด overhead ของการสื่อสาร
การทำซ้ำ (iteration) เป็นวิธีเดียวที่จะรับประกันการออกแบบ service ที่ดี สถาปนิกแทบไม่เคยค้นพบระดับ granularity การพึ่งพาข้อมูล และรูปแบบการสื่อสารที่สมบูรณ์แบบตั้งแต่ครั้งแรก พวกเขาต้องทำซ้ำไปเรื่อย ๆ เพื่อขัดเกลาการออกแบบ โดยเฉพาะอย่างยิ่งเมื่อเรียนรู้เกี่ยวกับระบบและฟังก์ชันทางธุรกิจมากขึ้น
Data Isolation (การแยกข้อมูล)
อีกข้อกำหนดหนึ่งของ microservices ซึ่งขับเคลื่อนโดยแนวคิด bounded-context เช่นกัน คือ data isolation สถาปัตยกรรมสไตล์อื่น ๆ จำนวนมากใช้ฐานข้อมูลเดียวสำหรับ persistence แต่ microservices พยายามหลีกเลี่ยง coupling ทุกรูปแบบ รวมถึง การใช้ schema และฐานข้อมูลร่วมกันเป็นจุดเชื่อมต่อ (integration point)
Data isolation เป็นอีกปัจจัยหนึ่งที่ต้องพิจารณาเมื่อดู granularity ของ service ระวังกับดัก Entity Trap (กล่าวถึงใน "The Entity Trap" )—อย่าออกแบบ service ให้เหมือนเอนทิตีเดี่ยว ๆ ในฐานข้อมูล สถาปนิกคุ้นเคยกับการใช้ relational database เพื่อรวมค่าต่าง ๆ ในระบบให้เป็นหนึ่งเดียว สร้าง single source of truth แต่นั่นไม่ใช่ทางเลือกอีกต่อไปเมื่อคุณกระจายข้อมูลไปทั่วสถาปัตยกรรม ดังนั้นสถาปนิกทุกคนต้องตัดสินใจว่าจะจัดการปัญหานี้อย่างไร ไม่ว่าจะระบุให้โดเมนหนึ่งเป็น source of truth สำหรับข้อเท็จจริงบางอย่าง แล้วประสานงานกับมันเพื่อดึงค่า หรือกระจายข้อมูลผ่านการทำ database replication หรือ caching
แม้ data isolation ระดับนี้จะสร้างความปวดหัว แต่ก็เปิดโอกาสใหม่ ๆ ด้วยเช่นกัน เมื่อไม่ต้องรวมทุกอย่างไว้ในฐานข้อมูลเดียวอีกต่อไป แต่ละทีมก็เลือกเทคโนโลยีฐานข้อมูลที่เหมาะสมที่สุดสำหรับงบประมาณ โครงสร้างการจัดเก็บ ลักษณะการทำงาน และอื่น ๆ ของ service ตัวเองได้ ข้อดีอีกอย่างของระบบที่ decouple กันสูงคือ ทีมใดก็ตามสามารถเปลี่ยนทิศทางและเลือกฐานข้อมูล (หรือ dependency อื่น) ที่เหมาะสมกว่าได้ โดยไม่กระทบทีมอื่น ซึ่งไม่ได้รับอนุญาตให้ผูกติดกับรายละเอียดการ implement (เราจะพูดถึง data isolation และข้อพิจารณาเรื่องฐานข้อมูลอย่างละเอียดกว่านี้ใน "Data Topologies" )
API Layer (เลเยอร์ API)
สถาปัตยกรรม microservices ส่วนใหญ่มีเลเยอร์ API (มักเรียกว่า API Gateway ) คั่นอยู่ระหว่างผู้ใช้ระบบ (ไม่ว่าจะเป็น user interface หรือการเรียกจากระบบอื่น) กับ microservices เลเยอร์ API อาจถูก implement เป็น reverse-proxy แบบง่าย ๆ หรือ gateway ที่ซับซ้อนกว่าซึ่งรวม cross-cutting concern เช่น security, naming services และอื่น ๆ (รายละเอียดเพิ่มเติมอยู่ใน "Operational Reuse" )
แม้เลเยอร์ API จะมีประโยชน์หลายอย่าง แต่เพื่อให้ยังคงยึดตามปรัชญาของสถาปัตยกรรมนี้ ไม่ควรใช้มันเป็น mediator หรือ orchestrator business logic ที่น่าสนใจทั้งหมดในสถาปัตยกรรมนี้ควรอยู่ภายใน bounded context และการใส่ orchestration หรือ business logic อื่น เข้าไปใน mediator ถือเป็นการละเมิดกฎนี้ สถาปนิกมักใช้ mediator ใน technically partitioned architecture ในขณะที่ microservices เป็น domain partitioned อย่างชัดเจน
Tip (เคล็ดลับ)
เมื่อใช้เลเยอร์ API ใน microservices architecture ให้ใส่เฉพาะ request routing และ cross-cutting concern เช่น security, monitoring, logging และอื่น ๆ ระวังอย่าใส่ business-related logic ใด ๆ เข้าไปในเลเยอร์ API
Operational Reuse (การใช้ซ้ำเชิงปฏิบัติการ)
เมื่อ microservices เลือกความซ้ำซ้อนมากกว่า coupling แล้วสถาปนิกจะจัดการกับส่วนของสถาปัตยกรรมที่ได้ประโยชน์จริง ๆ จากการ couple กันอย่างไร เช่นเรื่องปฏิบัติการอย่าง monitoring, logging และ circuit breaker ปรัชญาของ service-oriented architecture แบบดั้งเดิมคือใช้ซ้ำฟังก์ชันให้มากที่สุดเท่าที่ทำได้ ทั้งในระดับโดเมนและระดับปฏิบัติการ แต่ใน microservices สถาปนิกพยายามแยกสองเรื่องนี้ออกจากกัน
เมื่อทีมสร้าง microservice หลายตัวแล้ว สมาชิกในทีมจะเริ่มตระหนักว่าแต่ละ microservice มีองค์ประกอบร่วมที่ได้ประโยชน์ จากความคล้ายคลึงกัน ตัวอย่างเช่น หากองค์กรปล่อยให้แต่ละทีม service implement monitoring แยกกันเอง จะมั่นใจได้อย่างไรว่าแต่ละทีมทำอย่างถูกต้อง แล้วองค์กรจะจัดการเรื่องอย่างการอัปเกรดอย่างไร—แต่ละทีมต้องรับผิดชอบอัปเกรด เครื่องมือ monitoring เวอร์ชันใหม่เองหรือไม่ และจะใช้เวลานานแค่ไหน รูปแบบ Sidecar เสนอทางแก้ปัญหานี้ ( Figure 18-2 )
Figure 18-2. รูปแบบ Sidecar ใน microservices
ใน Figure 18-2 เรื่องปฏิบัติการที่ใช้ร่วมกัน (circuit breaker, logging, monitoring) ปรากฏอยู่ในแต่ละ service ในรูปแบบ component แยกต่างหาก ซึ่งแต่ละอันอาจเป็นเจ้าของโดยทีมนั้น ๆ เอง หรือโดยทีม infrastructure ส่วนกลาง Sidecar component จัดการเรื่องปฏิบัติการทั้งหมดที่ได้ประโยชน์จากการ couple กัน ดังนั้นเมื่อถึงเวลาต้องอัปเกรดเครื่องมือ monitoring ทีม infrastructure ส่วนกลางก็แค่อัปเดต sidecar และแต่ละ microservice ก็จะได้รับฟังก์ชันใหม่ไปด้วย (ดู "Team Topology Considerations" )
เมื่อแต่ละ service มี Sidecar component ร่วมกัน สถาปนิกก็สามารถสร้าง service mesh เพื่อให้ทีมต่าง ๆ ควบคุมเรื่องร่วมเหล่านี้ได้เป็นหนึ่งเดียวทั่วทั้งสถาปัตยกรรม sidecar component เหล่านี้เชื่อมต่อกันเพื่อสร้าง operational interface ที่สอดคล้องกันทั่วทุก microservice ดังแสดงใน Figure 18-3
Figure 18-3. service plane เชื่อมต่อ sidecar ต่าง ๆ เข้าด้วยกันใน service mesh
ใน Figure 18-3 แต่ละ sidecar เชื่อมเข้ากับ service plane service plane คือซอฟต์แวร์เชื่อมต่อ (มักอยู่ในรูปผลิตภัณฑ์อย่าง Istio ) ที่เชื่อม sidecar แต่ละตัวเข้าด้วยกันผ่าน interface ที่สอดคล้องกัน จนกลายเป็น service mesh
แต่ละ service กลายเป็น node หนึ่งใน mesh โดยรวม ดังแสดงใน Figure 18-4 service mesh สร้างคอนโซลที่ให้ทีมต่าง ๆ ควบคุม operational coupling ได้ทั่วทั้งระบบ เช่น ระดับ monitoring, logging และเรื่องปฏิบัติการแบบ cross-cutting อื่น ๆ
Figure 18-4. service mesh สร้างมุมมองแบบองค์รวมของด้านปฏิบัติการของ microservices
สถาปนิกใช้ service discovery เป็นวิธีสร้างความยืดหยุ่น (elasticity) ให้กับสถาปัตยกรรม microservices Service discovery คือวิธีตรวจจับและระบุตำแหน่ง service ต่าง ๆ ในเครือข่ายโดยอัตโนมัติ เมื่อมี request เข้ามา แทนที่จะเรียก service เดียวโดยตรง request จะผ่าน service discovery tool ซึ่งสามารถตรวจสอบจำนวนและความถี่ของ request แล้วสร้าง instance ของ service ใหม่ขึ้นมาเพื่อรองรับเรื่อง scale หรือ elasticity ได้ สถาปนิกมักรวม service discovery ไว้ใน service mesh ให้เป็นส่วนหนึ่งของทุก microservice เลเยอร์ API มักถูกใช้เป็นที่โฮสต์ service discovery เพื่อให้ user interface หรือระบบที่เรียกใช้อื่น ๆ มีที่เดียวสำหรับค้นหาและสร้าง service ได้อย่างยืดหยุ่นและสอดคล้องกัน
Frontends (ฟรอนต์เอนด์)
Microservices เน้นการ decouple ซึ่งในอุดมคติแล้วควรครอบคลุมทั้ง user interface และเรื่อง backend ด้วย อันที่จริง วิสัยทัศน์ดั้งเดิมของ microservices รวม UI ไว้เป็นส่วนหนึ่งของ bounded context ด้วย ตามหลักการ bounded-context ใน DDD แต่ในทางปฏิบัติ ข้อจำกัดของการแบ่งส่วนที่เว็บแอปพลิเคชันต้องการ (และข้อจำกัดภายนอกอื่น ๆ) ทำให้เป้าหมายนั้นทำได้ยาก นี่จึงเป็นเหตุผลที่เรามักเห็น UI สองสไตล์สำหรับสถาปัตยกรรม microservices
สไตล์แรก ดังแสดงใน Figure 18-5 คือ monolithic frontend ซึ่งมี UI เดียวที่เรียกผ่านเลเยอร์ API เพื่อตอบสนอง request ของผู้ใช้ frontend นี้อาจเป็นแอปพลิเคชันเดสก์ท็อป มือถือ หรือเว็บแบบเต็มรูปแบบก็ได้ ตัวอย่างเช่น เว็บแอปพลิเคชันจำนวนมากทุกวันนี้ใช้ JavaScript web framework สร้าง UI เดียว
Figure 18-5. สถาปัตยกรรม microservices ที่มี user interface แบบ monolithic
ตัวเลือก UI แบบที่สองคือ micro-frontends ดังแสดงใน Figure 18-6
Figure 18-6. รูปแบบ micro-frontend ใน microservices
แนวทาง micro-frontend ใช้ component ในระดับ UI เพื่อสร้างระดับ granularity และ isolation ที่สอดคล้องกันระหว่าง UI กับ backend service ทำให้เกิดความสัมพันธ์ระหว่าง component ของ UI กับ backend service ที่เกี่ยวข้องกัน
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ micro-frontends เราขอแนะนำหนังสือ Building Micro-Frontends ฉบับพิมพ์ครั้งที่ 2 โดย Luca Mezzalira (O'Reilly, 2025)
Communication (การสื่อสาร)
ใน microservices สถาปนิกและนักพัฒนามักดิ้นรนเพื่อหา service granularity ที่เหมาะสม ซึ่งส่งผลต่อทั้ง data isolation และการสื่อสาร การหารูปแบบการสื่อสารที่ถูกต้องช่วยให้ทีมรักษาความ decoupled ของ service ไว้ได้ ในขณะที่ยัง ประสานงานกันได้อย่างมีประโยชน์
โดยพื้นฐานแล้ว สถาปนิกต้องเลือกระหว่างการสื่อสารแบบ synchronous หรือ asynchronous การสื่อสารแบบ synchronous ต้องการให้ผู้ส่งรอการตอบกลับจากผู้รับ สถาปัตยกรรม microservices มักใช้ protocol-aware heterogeneous interoperability เมื่อสื่อสารระหว่าง service มาแยกคำศัพท์ซับซ้อนนี้ออกเป็นส่วน ๆ เพื่อทำความเข้าใจว่ามันหมายถึงอะไรและทำไมจึงสำคัญ:
Protocol-aware (รับรู้โปรโตคอล)
เนื่องจาก microservices ไม่มี integration hub ส่วนกลาง แต่ละ service จึงต้องรู้วิธีเรียก service อื่น ดังนั้น สถาปนิกจึงมักกำหนดมาตรฐานว่า อย่างไร service แต่ละตัวจะเรียกกัน เช่น REST ระดับหนึ่ง, message queue และอื่น ๆ นั่นหมายความว่า service ต้องรู้ (หรือค้นหา) ว่าจะใช้โปรโตคอลไหนในการเรียก service อื่น
Heterogeneous (หลากหลายเทคโนโลยี)
เนื่องจาก microservices เป็นสถาปัตยกรรมแบบ distributed แต่ละ service อาจถูกเขียนด้วย technology stack ที่ต่างกันได้ Heterogeneous บ่งบอกว่า microservices รองรับสภาพแวดล้อมแบบ polyglot อย่างเต็มที่ ซึ่งแต่ละ service ใช้แพลตฟอร์มที่แตกต่างกัน
Interoperability (ความสามารถในการทำงานร่วมกัน)
หมายถึงการที่ service เรียกหากันเอง แม้สถาปนิกใน microservices จะพยายามลดการเรียก method แบบ transactional แต่ service ก็มักเรียก service อื่นผ่านเครือข่ายเพื่อประสานงานและแลกเปลี่ยนข้อมูลกันอยู่เสมอ