ข้ามไปยังเนื้อหา

Backends for Frontends

คุณเอา API Gateway มาใช้แล้วและได้ผลดี แต่ client แต่ละชนิดไม่เหมือนกัน web app วิ่งบนเน็ตเร็วและต้องการ response แบบละเอียดครบถ้วนเพื่อ render dashboard ที่กว้าง ส่วน mobile app วิ่งบนสัญญาณมือถือที่ไม่นิ่ง จึงต้องการ response ที่เล็กกระชับ เอาเฉพาะ field ที่หน้าจอเล็กแสดงจริง ส่วน third-party integration ต้องการ endpoint ที่นิ่ง อนุรักษ์นิยม มีเวอร์ชันชัด และเปลี่ยนน้อยที่สุด gateway ตัวเดียวที่พยายามเอาใจทั้งสามฝ่ายจะค่อย ๆ งอก logic แบบมีเงื่อนไขและกรณีพิเศษ “ใส่ field นี้เฉพาะ mobile” เพิ่มขึ้นไม่หยุด

gateway ตัวเดียวที่ client ทุกชนิดใช้ร่วมกันจะกลายเป็นจุดที่ทุกทีมมาแย่งกันแก้และโตไม่หยุด ทีม client ทุกทีมต้องนัดแนะการเปลี่ยนแปลงผ่าน codebase ก้อนเดียวกัน ทีม mobile ต้องรอทีม web และทีม web ก็ต้องรอทีม mobile สุดท้าย API ก็ไหลไปหารูปร่างกลาง ๆ ที่ไม่เข้ากับ client ตัวไหนเลย หนักไปสำหรับ mobile บางไปสำหรับ web แถมความเป็นเจ้าของยังคลุมเครือ เพราะเมื่อสามทีมพึ่ง gateway ตัวเดียวกัน แล้วใครคือเจ้าของ คำถามคือคุณจะรักษาข้อดีของจุดเข้าจุดเดียวไว้ พร้อมกับให้ client แต่ละตัวพัฒนา API ของตัวเองตามจังหวะของตัวเองได้อย่างไร

Backends for Frontends (BFF) เอา gateway pattern มาใช้ซ้ำหนึ่งชุดต่อ client หนึ่งชนิด แทนที่จะมี gateway ตัวเดียว คุณรัน gateway แยกให้ client แต่ละชนิด ได้แก่ web BFF, mobile BFF, third-party BFF แต่ละ BFF เปิด API ที่ปั้นรูปร่างมาให้พอดีกับ client ของตัวเอง mobile BFF คืน payload ที่ตัดทอนแล้ว web BFF คืนผลรวมแบบละเอียดครบ ส่วน third-party BFF เปิด contract ที่นิ่งและเปลี่ยนเวอร์ชันช้า ๆ

จุดเปลี่ยนที่สำคัญที่สุดคือเรื่องความเป็นเจ้าของ แต่ละ BFF มีทีมที่สร้าง client นั้นเป็นเจ้าของ ทีม mobile ดูแล mobile BFF และปล่อยการเปลี่ยนแปลงทั้งฝั่ง client และฝั่ง backend พร้อมกันได้เลย ไม่ต้องนัดแนะข้ามทีม ส่วน service ภายในที่ใช้ร่วมกันยังคงอยู่หลัง BFF ทุกตัวโดยไม่ถูกแตะ

flowchart LR
  Web[Web App] --> WBFF[Web BFF]
  Mobile[Mobile App] --> MBFF[Mobile BFF]
  Third[Third-party client] --> TBFF[Third-party BFF]
  WBFF --> O[Order Service]
  WBFF --> C[Customer Service]
  MBFF --> O
  MBFF --> I[Inventory Service]
  TBFF --> O
  TBFF --> C
Backends for Frontends — gateway ที่ปรับแต่งหนึ่งตัวต่อ client หนึ่งชนิด แต่ละตัวเป็นเจ้าของโดยทีมของ client นั้น เหนือบริการภายในที่ใช้ร่วมกัน

แต่ละ BFF เป็น deployable แยกของตัวเอง พร้อม configuration ที่ปรับมาให้เข้ากับ client ของตัวเอง ตัวอย่างข้างล่างคือ configuration ของ route สอง BFF ที่วางอยู่บน service ภายในชุดเดียวกัน สังเกตว่า mobile BFF ตัด field ทิ้งและตั้ง limit ไว้เข้ม ส่วน web BFF คืนผลรวมแบบละเอียดครบ

# mobile-bff.yaml — small payloads, conservative limits for cellular clients
listen: ":443"
routes:
- path: /m/order/:id
aggregate:
- get: http://order-service/orders/:id
as: order
- get: http://inventory-service/stock/:sku
as: stock
response_shape:
fields: [order.id, order.status, order.total, stock.available] # trimmed
rate_limit:
requests_per_minute: 60
---
# web-bff.yaml — rich aggregates for a fast connection and a wide screen
listen: ":443"
routes:
- path: /w/order/:id
aggregate:
- get: http://order-service/orders/:id
as: order
- get: http://customer-service/customers/:customerId
as: customer
- get: http://inventory-service/stock/:sku
as: stock
response_shape:
fields: [order, customer, stock] # full objects
rate_limit:
requests_per_minute: 240

สิ่งที่คุณได้:

  • API ที่ตัดเย็บให้พอดีตัว client แต่ละตัวได้ response ที่ปั้นมาให้เข้ากับหน้าจอ เครือข่าย และความต้องการของตัวเองพอดี เล็กสำหรับ mobile ละเอียดสำหรับ web นิ่งสำหรับ third party โดยไม่ต้องประนีประนอมกับฝ่ายอื่น
  • ความเป็นเจ้าของและความเป็นอิสระที่ชัดเจน ทีม client แต่ละทีมเป็นเจ้าของ BFF ของตน และส่งการเปลี่ยนแปลง client กับ backend พร้อมกัน ขจัดการประสานงานข้ามทีมที่ gateway ที่ใช้ร่วมกันบังคับให้ทำ
  • blast radius เล็กลง การเปลี่ยนแปลงหรือบั๊กใน mobile BFF ทำให้ web client พังไม่ได้ เพราะเป็นคนละ deployable กัน

สิ่งที่คุณต้องจ่าย:

  • มีการทำซ้ำบ้าง กฎการจัดเส้นทาง การเชื่อมต่อการยืนยันตัวตน และ logic การรวมผลลัพธ์เกิดซ้ำกันในหลาย BFF concern ที่ใช้ร่วมกันสามารถถูกแยกออกไปเป็น library ที่ใช้ร่วมกันได้ แต่คุณยอมรับว่ามีชิ้นส่วนเคลื่อนไหวมากกว่า gateway เดียว
  • มีองค์ประกอบให้ทำงานมากขึ้น BFF หลายตัวหมายถึงหลายสิ่งที่ต้อง deploy เฝ้าติดตาม และทำให้พร้อมใช้งาน แทนที่จะเป็นเพียงตัวเดียว
  • มีขอบเขตที่ต้องคอยควบคุม แต่ละ BFF ต้องคงสภาพเป็นชั้นปรับแต่งบาง ๆ ถ้า BFF เฉพาะ client เริ่มดูดซับ business logic คุณก็จะสร้างปัญหา God-object ที่ pattern นี้ตั้งใจจะแก้ขึ้นมาใหม่ — เพียงแต่กระจายอยู่ในหลายที่
  • API Gateway — pattern แบบ gateway เดียวที่ BFF ทำให้พิเศษขึ้นต่อ client หนึ่งชนิด
  • Service Discovery — วิธีที่แต่ละ BFF หาตำแหน่งของ service ภายในที่ต้องเรียก
  • API Composition — การรวมผลลัพธ์ที่แต่ละ BFF ทำเพื่อประกอบการตอบกลับที่ปรับให้เข้ากับ client
ข้อดีข้อแลกเปลี่ยน
แต่ละ client type ได้ API ที่ optimize ให้ตัวเองเพิ่มจำนวน backend ที่ต้องดูแล — N client types = N BFF
mobile client ไม่ต้องรับ payload ที่ออกแบบสำหรับ weblogic ซ้ำระหว่าง BFF ถ้าไม่ระวัง
team ของแต่ละ frontend ดูแล BFF ของตัวเองได้BFF อาจกลายเป็น fat middleware ถ้าใส่ business logic
เปลี่ยน frontend โดยไม่กระทบ BFF ของ client อื่นเพิ่ม hop ใน request chain — latency สูงขึ้นเล็กน้อย

Shared BFF — BFF เดียวสำหรับทุก client type ทำให้กลับไปเป็นปัญหาเดิม

อาการ:

  • BFF มีเงื่อนไข if mobile / if web ปะปนกัน
  • ทีม mobile และ web ทะเลาะกันเรื่อง API shape
  • release ของ BFF กระทบทุก client พร้อมกัน

Business Logic BFF — ใส่ business rule ไว้ใน BFF แทน service

อาการ:

  • pricing logic, discount rule อยู่ใน BFF
  • เปลี่ยน business rule ต้อง deploy BFF ใหม่ทุกตัว

💡 ตัวอย่างจากของจริง

SoundCloud:

  • เป็นผู้บัญญัติคำว่า “Backend for Frontend” ในปี 2015
  • มี BFF แยกสำหรับ web, mobile, และ public API
  • แต่ละ BFF ดูแลโดย frontend team ของตัวเอง

Netflix:

  • มี BFF แยกสำหรับ Smart TV, Mobile, Browser
  • แต่ละ device type ได้ response format ที่ optimize สำหรับ screen size และ bandwidth
แนวคิดหลักของ Backends for Frontends คืออะไร?
ปัญหาของ gateway เดียวที่ใช้ร่วมกันอันใดที่ BFF แก้เป็นหลัก?
ใครเป็นเจ้าของ BFF ตัวหนึ่ง?
ข้อใดเป็นต้นทุนที่แท้จริงของ BFF pattern?