จากบทความเรื่อง Principles for Microservice Design: Think IDEALS, Rather than SOLID
ทำการอธิบายแนวทางการออกแบบตามแนวคิด Microservices ได้อย่างน่าสนใจ
โดยทำการหยิบเอาแนวคิดจาก SOLID
สำหรับการออกแบบระบบที่พัฒนาด้วย Object-Oriented Programming
มาประยุกต์ใช้กับ Microservies
ซึ่งสรุปไว้ในชื่อว่า IDEALS ระกอบไปด้วย
- Interface segregation
- Deployability
- Event-driven
- Availability over consistency
- Loose coupling
- Single responsibility
มาดูในรายละเอียดกันนิดหน่อย
เรื่องที่ 1 Interface Segregation
เป็นแนวคิดมาจาก Interface Segregation Principle (ISP) ใน S.O.L.I.D
เพื่อไม่ให้เกิด fat interface หรือ interface ขนาดใหญ่
ซึ่งมีความสามารถมากกว่าที่ผู้ใช้งานหรือ client แต่ละคนต้องการ
ดังนั้นสิ่งที่ควรทำคือ แยก interface ไปในแต่ละผู้ใช้งาน
การออกแบบ Microservices ก็เช่นกัน
ควรแยก interface ของการใช้งานตามผู้ใช้งาน
เพื่อให้เหมาะสมต่อการใช้งาน
ยกตัวอย่างเช่น web, mobile และ 3-party application
เรามักจะเรียก interface เหล่านี้ว่า Backend for Frontend (BFF) แสดงดังรูป
แน่นอนว่ามันมีทั้งข้อดีและข้อเสีย ก็ต้อง trade-off กันว่า
แบบไหนที่เหมาะกับระบบงานของเราด้วย
เรื่องที่ 2 Deployability
ในการออกแบบ service นั้น
เรามักจะมองเพียงในมุมของการแบ่งเป็น module หรือ service
ว่าจะแยกหรือรวมหรือทำงานกันอย่างไร
แต่สิ่งหนึ่งที่ขาดหายไปคือ การ deploy
เมื่อมีการเปลี่ยนแปลง เช่นปรับเปลี่ยน version ของบาง service แล้ว
ส่งผลกระทบต่อผู้ใช้งานน้อยที่สุด
แน่นอนว่า business ก็จะมีความสุขไปด้วย
ดังนั้นสิ่งที่ต้องคิด คุย วางแผน และสร้างระบบขึ้นมาในการ deploy service
จะประกอบไปด้วย
- Configuration services
- Scaling services
- ปรับปรุงคุณภาพและความเร็วของ process การทำงานตั้งแต่ Commit, build, test และ deploy เพื่อให้ได้รับ feedback ที่รวดเร็ว เช่นระบบ Continuous Delivery
- ลดเวลา downtime ของระบบจากการ deploy service ใหม่ ๆ เช่น Blue-green deployment และ Canary release
- การจัดการ version ของแต่ละ service เพื่อใช้ในการจัดการการ deploy
- การ monitoring ทั้ง Centralize logging, Distributed tracing การทำงานของแต่ละ service เพื่อช่วยในการชี้เป้าหรือต้นเหตุของปัญหาได้อย่างรวดเร็ว
เรื่องที่ 3 Event-Driven
รูปแบบในการติดต่อสื่อสารระหว่าง service นั้นประกอบไปด้วย
- Synchronous
- HTTP call เช่นเรียกไปยัง RESTFul API
- RPC-like เช่นพวก gRPC หรือ GrahpQL
- Asynchronous ผ่าน messaging/broker server เช่น Apache Kafka, RabbitMQ และ Apache ActiveMQ เป็นต้น
โดยแต่ละรูปแบบนั้นต้องใช้ให้ถูกตามแต่ละ use case ไป
ถ้าใช้งานแบบ Asynchronous แล้ว
มักจะออกแบบตามแนวทางจอง Event-Driven Architecture (EDA)
เพื่อช่วยปรับปรุงเรื่องของ scaling และรองรับการทำงานได้สูงขึ้น
เนื่องจากแต่ละ request ที่เข้ามาจะถูกส่งเข้า queue/topic
ไม่ต้อง block หรือรอจนกว่า response กลับมา
แต่ก็เพิ่มความซับซ้อนของระบบเข้ามา
เรื่องที่ 4 Availability over Consistency
จากแนวคิดของ CAP theorem คือ
- Consistency
- Availability
- Partition-tolerance
ซึ่งให้เลือกได้ 2 จาก 3 ข้อเท่านั้น
แต่ไม่ได้บอกว่าจะไม่สนใจข้อที่ไม่ได้เลือกนะ
โดยตามแนวคิด Microservices นั้นจะแยกการทำงานเป็น service ย่อย ๆ
และแต่ละ service จะมีการเก็บข้อมูลที่แยกกัน
เพื่อให้เป็นอิสระแก่กัน
ส่งผลให้เรามักจะเลือก Availability และ Partition-tolerance จาก CAP theorem
คำถามคือ แล้วในส่วนของ Consistency ละ มันสำคัญมาก ๆ นะ
แน่นอนว่า ก็ไม่ได้ละทิ้งเรื่องนี้ไป
โดยเขาจะเรียก Consistency นี้ว่า Eventual Consistency
หรือแปลให้เข้าใจง่าย ๆ คือ เดี๋ยวจะเท่ากัน !!
วิธีการจัดการในเรื่องของ Eventual Consistency
เพื่อให้เหมาะกับงาน คน และเทคโนโลยีด้วยยกตัวอย่างเช่น
- Service data replication pattern
- Command Query Responsibility Segregation pattern (CQRS)
- Event Sourcing pattern
ยกตัวอย่างของ CQRS
เรื่องที่ 5 Loose Coupling
เมื่อทำการแยก service มาเป็น service ย่อย ๆ แล้ว
สิ่งที่ควรเข้าใจคือ เรื่องของการผูกมัดหรือความสัมพันธ์ระหว่าง service
ไม่ควรผูกมัดกันเลยเป็นดีที่สุด
หรือถ้าไม่ได้ ก็ควรจะผูกมัดกันแบบหลวม ๆ หรือ Loose Coupling
ยกตัวอย่าง เรามักจะแยกส่วนของ implementation ของ service
ออกจากการเรียกใช้จากผู้ใช้งานผ่าน interface ต่าง ๆ
ทำให้แต่ละ service เปลี่ยน implementation ได้ง่าย
แต่ก็ยังมีการผูกมัดในด้านการติดต่อสื่อสาร
รวมไปถึง data อีกที่ต้องระมัดระวังไว้ให้ดี
ดังนั้นจึงมีรูปแบบในการออกแบบเพื่อให้ Loose Coupling ดังนี้
- ใช้งาน messaging ทั้งแบบ Point-to-Point และ Publish-subscribe
- ใช้งาน BFF และ API gateway
- Contract-first design
- Hypermedia
- Facade และ Adapter/Wrapper patterns
- Database per microservice pattern
เรื่องที่ 6 Single Responsibility
เป็นแนวคิดมาจาก Single Responsibility Principle (SRP) ใน S.O.L.I.D
นั่นคือแต่ละ service ควรมีหน้าที่การทำงานชัดเจน ไม่เยอะหรือน้อยจนเกินไป
ถ้าเยอะเกินไปแล้ว ก็จะยากเพราะว่า มีส่วนที่ต้องดูและจัดการเยอะ
ถ้าน้อยเกินไปแล้ว ก็จะยากเพราะว่า มีส่วนที่ต้องดูและจัดการเยอะ แถมกระจายกันอีก
ดังนั้นควรแยกให้เหมาะสมกับความต้องการของระบบงานนั้น ๆ ด้วย
หรือมันคือการทำ domain modeling
ซึ่งนำแนวคิดมาจาก Domain-Driven Design (DDD) เพื่อช่วยทำให้เราเข้าใจว่า
- แต่ละ service มีขอบเขตการทำงานอย่างไร
- แต่ละ service มีการติดต่อกันทั้งภายในและภายนอกกันอย่างไร
เหมือนเรามีแผนที่นำทางในการเดินทางนั่นเอง
เนื่องจากบ่อยครั้งเรามักจะเดินทางแบบมั่ว ๆ ซึ่งเป็นสิ่งที่ไม่ดีเลย
ดังนั้นเรื่องของความเข้าใจในทุก ๆ ฝ่ายที่เกี่ยวข้อง มันสำคัญมาก ๆ
น่าจะเป็นอีกหนึ่งแนวทางที่ช่วยให้การออกแบบดีขึ้น
รวมทั้งยังช่วยแก้ไขปัญหาอีกด้วย
ออกแบบที่ว่าดีแล้ว อย่าลืมลงมือทำเพื่อรับบ feedback เพื่อปรับปรุงกันด้วย