Animated Architecture Patterns

Strangler Fig

วาง facade ไว้หน้าระบบ legacy แล้วย้าย route ไปที่ service ใหม่ทีละอัน

หรือที่เรียกกันว่า Strangler pattern, Strangler application

Clients web · mobile Strangler facade routes requests by path route goes to /catalog /orders /users /billing Legacy monolith one big deployable catalog catalog no traffic orders orders no traffic users users no traffic billing billing no traffic legacy legacy legacy legacy catalog service orders service users service billing service new new new new decommissioned

ขั้นที่ 1 · วาง facade ไว้ข้างหน้า วาง facade (API gateway, reverse proxy หรือ routing layer) ไว้หน้าระบบ legacy ตอนนี้ client คุยกับ facade แทน แล้ว facade ก็ส่งทุก route ต่อไปที่ monolith เหมือนเดิม ยังไม่มีอะไรย้ายเลย แต่ตอนนี้เราคุมได้แล้วว่าแต่ละ request จะไปที่ไหน

ขั้นที่ 2 · แยก /catalog ออกมา เลือก capability มาหนึ่งอันที่ขอบเขตชัด สร้างใหม่เป็น service ตัวใหม่ แล้ว สลับ route ของมัน ที่ facade โค้ดส่วนเดียวกันใน monolith ก็จะไม่ได้ traffic จาก facade อีก และลบทิ้งทีหลังได้ เมื่อเปลี่ยนให้ module legacy อื่น ๆ ที่เรียกมันอยู่ไปเรียก service ใหม่แทนด้วยแล้ว

ขั้นที่ 3 · ทยอยลอกออกมาอีก ทำแบบเดิมซ้ำไปทีละ route จะเรียงลำดับยัง⁠ไงก็ได้ ขอให้ได้ประโยชน์หรือช่วยลดความเสี่ยง แต่ละขั้นเล็กและย้อนกลับได้: ถ้า service ใหม่ทำงานผิดปกติ ก็แค่สลับ route กลับไปที่ legacy

ขั้นที่ 4 · ปิด monolith ทิ้ง พอ route สุดท้ายย้ายออกไป ตัว monolith ก็ไม่ได้รับ request อะไรอีก ปิดทิ้งได้เลย ระบบใหม่โตล้อมรอบระบบเก่าจนแทนที่ได้หมด เหมือนต้นไทรที่ขึ้นรัดต้นไม้ (strangler fig) ที่เป็นที่มาของชื่อแพตเทิร์นนี้

ปัญหา

ระบบ legacy สำคัญเกินกว่าจะปิดทิ้ง และแพงเกินกว่าจะแก้ต่อไปเรื่อย ๆ การ rewrite ใหม่ทีเดียวทั้งระบบดูน่าลอง แต่มันแปลว่าต้องผ่านหลายเดือนหรือหลายปีที่ไม่ได้ส่งคุณค่าอะไรใหม่ออกไปเลย ต้องคอยทำให้สองระบบตรงกัน และปิดท้ายด้วยวัน cut-over ที่น่ากลัวสุด ๆ อีกหนึ่งวัน การ rewrite หลายครั้งไปไม่ถึงวันนั้นด้วยซ้ำ

ทำงานยัง⁠ไง

  1. ดักไว้ก่อน วาง facade ไว้หน้าระบบ legacy ทุก request จะได้ผ่านอะไรสักอย่างที่คุณคุมได้
  2. แทนที่ทีละส่วน สร้าง implementation ใหม่ของ capability เดียว ปกติก็คือหนึ่ง route หรือหนึ่ง bounded context
  3. เปลี่ยนเส้นทาง แก้ routing ของ facade ให้ส่วนนั้นไปที่ implementation ใหม่ และเปลี่ยนให้ module legacy อื่น ๆ ที่เรียกส่วนนั้นอยู่ไปเรียกตัวใหม่ด้วย โค้ด legacy ของส่วนนั้นก็จะเงียบไป
  4. ทำซ้ำ แล้วปลดระวาง ทำต่อไปเรื่อย ๆ จนไม่มีอะไรถูก route ไปที่ระบบเก่าแล้ว จากนั้นก็ปิดมันทิ้ง

การย้ายระบบเลยกลายเป็น release เล็ก ๆ ที่ย้อนกลับได้ต่อกันเป็นสายยาว แทนที่จะเป็นการกระโดดครั้งเดียวที่ย้อนกลับไม่ได้

ใช้ตอนไหนดี

  • ตอนแทนที่หรือย้าย platform ของระบบใหญ่ที่ต้องให้บริการต่อไปตลอดช่วงนั้น
  • ตอนที่ดักระบบได้ที่ขอบเขตบางจุด: HTTP route, message topic, UI shell, การวางไฟล์
  • ตอนที่อยากส่งคุณค่าใหม่ ๆ ออกไประหว่างย้ายระบบ ไม่ใช่รอส่งแค่ตอนจบ

ได้อะไร เสียอะไร

  • ข้อมูลที่ใช้ร่วมกันคือส่วนที่ยาก ย้าย route ง่าย แต่ service ใหม่กับ monolith มักใช้ database ร่วมกันอยู่ช่วงหนึ่ง ต้องวางแผนให้ชัดเรื่องความเป็นเจ้าของข้อมูล การ sync (เช่น CDC) และการ cut-over ในที่สุด
  • Facade เป็น infrastructure สำคัญ มันต้องมี high availability และเร็ว และต้องไม่งอก business logic ของตัวเองขึ้นมา
  • มีสองโลกอยู่ช่วงหนึ่ง เตรียมใจไว้ว่าจะมีเรื่องที่ซ้ำกัน (auth, logging, deployment) จนกว่าการย้ายจะจบ รักษาจังหวะให้เดินต่อ ของที่ "ชั่วคราว" จะได้ไม่กลายเป็นของถาวร
  • ไม่ใช่ทุกระบบจะหั่นเป็นส่วนได้ batch job ที่ผูกกันแน่น หรือ UI ที่ไม่มีรอยต่อ อาจต้องใช้ branch by abstraction หรือ parallel run แทน

ข้อควรรู้ตอนลงมือทำ

  • facade มักเป็นของที่คุณรันอยู่แล้ว: API gateway, ingress controller, CDN หรือ load balancer ที่ route ตาม path ได้ หรือ BFF
  • วาง anti-corruption layer ไว้ระหว่าง service ใหม่กับ model ของ legacy ตัว domain model เก่าจะได้ไม่รั่วเข้ามาในโค้ดใหม่
  • ใช้ feature flag หรือ weighted routing เพื่อทยอยย้าย route (1% → 10% → 100%) และใช้ shadow traffic หรือ parallel run เทียบผลลัพธ์ก่อนสลับ
  • ติดตามความคืบหน้าให้เห็นชัด: route ที่ย้ายแล้ว สัดส่วน traffic ที่ยังไปที่ legacy และ module ที่ลบไปแล้ว