0Pricing
PHP Academy · บทเรียน

จากโมโนลิทสู่ไมโครเซอร์วิส

ตัดสินใจว่าควรแยกส่วนใดและกำหนดขอบเขตบริการอย่างไร

จากโมโนลิทสู่ไมโครเซอร์วิส เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

จากโมโนลิทสู่ไมโครเซอร์วิส

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

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

เหตุผลที่ควร (และไม่ควร) แยก

เหตุผลที่ดีในการแยก:

  • ความสามารถในการนำขึ้นใช้งานและความรับผิดชอบของทีมที่แยกจากกันได้
  • การขยายทรัพยากรของระบบย่อยที่มีภาระงานสูงได้อย่างอิสระ
  • การแยกเทคโนโลยีและสภาพแวดล้อมขณะทำงาน รวมถึงการจำกัดผลกระทบเมื่อเกิดข้อขัดข้อง

เหตุผลที่ไม่ดีคือ “โค้ดยุ่งเหยิง” (ควรปรับโครงสร้างโมโนลิทก่อน) หรือการทำตามกระแส หากบริการสองแห่งต้องนำขึ้นใช้งานพร้อมกันเสมอ หรือต้องใช้ตารางฐานข้อมูลร่วมกัน บริการเหล่านั้นก็คือบริการเดียวที่สวมหมวกสองใบ

บริบทที่มีขอบเขต

การออกแบบที่ขับเคลื่อนด้วยโดเมนมีเครื่องมือสำหรับกำหนดขอบเขตที่ชัดเจนที่สุด นั่นคือ บริบทที่มีขอบเขต ภายในบริบทหนึ่ง คำศัพท์จะมีความหมายที่แม่นยำเพียงหนึ่งเดียว “ลูกค้า” ใน การเรียกเก็บเงิน (ผู้รับใบแจ้งหนี้) แตกต่างจาก “ลูกค้า” ใน ฝ่ายสนับสนุน (ผู้เขียนคำขอ)

บริบทที่มีขอบเขตแต่ละบริบทเป็นตัวเลือกที่เหมาะสมอย่างยิ่งสำหรับการเป็นบริการ ขอบเขตควรสอดคล้องกับภาษาทางธุรกิจและวิธีที่องค์กรสื่อสารกันจริง ซึ่งเป็นกฎของ Conway ในทางปฏิบัติ

การเกาะกลุ่มสูง การเชื่อมโยงต่ำ

ขอบเขตบริการที่ดีจะเก็บสิ่งที่ เปลี่ยนแปลงไปด้วยกันไว้ภายใน และผลักสิ่งที่เปลี่ยนแปลงอย่างอิสระออกไป วัดการแยกที่เสนอโดยพิจารณาจาก:

  • มีฟีเจอร์กี่รายการที่ต้องแก้ไข สองบริการพร้อมกัน? (ควรมีน้อย)
  • รูปแบบการเรียกใช้ระหว่างบริการทั้งสอง ถี่แค่ไหน? (ควรเป็นการเรียกที่รวมงานเป็นก้อนใหญ่)

หากการทำฟีเจอร์หนึ่งต้องคร่อมขอบเขตอยู่เสมอ แสดงว่ากำหนดขอบเขตไว้ผิดตำแหน่ง

<?php
// Chatty boundary smell: N network calls to render one view
foreach ($order->lineItems as $item) {
    $product = $catalogApi->get($item->productId); // one call PER item!
    $names[] = $product['name'];
}

// Coarse-grained: one batch call across the boundary
$ids   = array_map(fn($i) => $i->productId, $order->lineItems);
$names = $catalogApi->getMany($ids); // single round-trip
echo count($names) . " products in one call\n";

ฐานข้อมูลต่อบริการ

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

ดังนั้น การสืบค้นข้ามบริการที่เคยเป็น SQL JOIN ในโมโนลิท จะกลายเป็นการเรียก API หรือใช้แบบจำลองการอ่านที่จำลองข้อมูล นี่คือต้นทุนที่ต้องจ่าย และนั่นคือจุดประสงค์ของการแยก

<?php
// In the monolith: one JOIN across domains
$sql = 'SELECT o.id, c.email
        FROM orders o JOIN customers c ON c.id = o.customer_id';

// After split: Orders service holds only the foreign id;
// it asks the Customers service for the rest (or keeps a local read model).
$order = $orderRepo->find($id);          // local
$customer = $customersApi->get($order->customerId); // network call
echo $order->id . ' / ' . $customer->email . "\n";

รูปแบบต้นไทรรัด

อย่าเขียนโมโนลิทใหม่ทั้งหมดในครั้งเดียว ให้ใช้รูปแบบ ต้นไทรรัดเพื่อแยกส่วนต่าง ๆ ออกมาอย่างค่อยเป็นค่อยไป: ส่งการรับส่งข้อมูลบางส่วนไปยังบริการใหม่ผ่านส่วนหน้า/พร็อกซี พัฒนาบริการนั้นต่อ และเลิกใช้เส้นทางโค้ดเดิมก็ต่อเมื่อเส้นทางใหม่รองรับความสามารถทั้งหมดแล้ว

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

<?php
// Facade routing: peel off one capability at a time
function route(string $path): string {
    $migrated = ['/invoices', '/invoices/pdf']; // moved to billing-svc
    foreach ($migrated as $prefix) {
        if (str_starts_with($path, $prefix)) {
            return 'http://billing-svc' . $path;
        }
    }
    return 'http://legacy-monolith' . $path; // everything else, for now
}
echo route('/invoices/pdf'), "\n";
echo route('/users/42'), "\n";

การแยกโมดูล

ลำดับการแยกที่เหมาะกับการปฏิบัติจริง:

  1. ค้นหาโมดูลที่มี การพึ่งพาจากภายนอกขาเข้าน้อยและมีความเป็นเจ้าของข้อมูลที่ชัดเจน
  2. ห่อหุ้มการเรียกภายในกระบวนการของโมดูลนั้นไว้หลังอินเทอร์เฟซในโมโนลิทก่อน
  3. ย้ายข้อมูลที่โมดูลเป็นเจ้าของไปไว้ในสคีมา/ฐานข้อมูลของโมดูลเอง
  4. เปลี่ยนการทำงานของอินเทอร์เฟซให้ใช้ไคลเอนต์เครือข่าย
  5. เปลี่ยนเส้นทางการรับส่งข้อมูลผ่านส่วนหน้า แล้วลบโค้ดเดิม

การห่อหุ้มด้วยอินเทอร์เฟซภายในโมโนลิทก่อนจะช่วยลดความเสี่ยงของขั้นตอนการเชื่อมต่อเครือข่าย

<?php
// Step 2: hide the implementation behind a port the monolith calls
interface InvoiceService {
    public function generate(string $orderId): string; // returns invoice id
}

// Today: local class. Tomorrow: HTTP client to billing-svc.
// The monolith's calling code never changes.
final class LocalInvoiceService implements InvoiceService {
    public function generate(string $orderId): string { return 'inv-1'; }
}

ความสอดคล้องของข้อมูลแบบกระจาย

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

บริการคำสั่งซื้อไม่สืบค้นบริการลูกค้าในทุกคำขอ แต่จะเก็บแบบจำลองย่อยขนาดเล็กไว้ (เฉพาะฟิลด์ที่จำเป็น) และอัปเดตด้วยเหตุการณ์ CustomerUpdated วิธีนี้ตัดการพึ่งพาขณะทำงานและขั้นการส่งต่อที่เพิ่มเวลาแฝงออกไป

<?php
// Orders service keeps a tiny local projection of customer data
function onCustomerUpdated(PDO $db, array $evt): void {
    $db->prepare(
        'INSERT INTO customer_read_model (id, email)
         VALUES (:id, :email)
         ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email'
    )->execute(['id' => $evt['id'], 'email' => $evt['email']]);
}

ความเป็นจริงด้านการปฏิบัติงาน

ไมโครเซอร์วิสย้ายความซับซ้อนจากโค้ดไปอยู่ที่การปฏิบัติงาน ก่อนแยกบริการ คุณต้องมีสิ่งต่อไปนี้:

  • การบันทึกแบบรวมศูนย์และการติดตามระบบแบบกระจาย (มีรหัสสหสัมพันธ์ข้ามแต่ละช่วงการเรียก)
  • CI/CD ต่อบริการและการนำขึ้นใช้งานแยกกันโดยมีเวอร์ชัน
  • การตรวจสอบสุขภาพ ระยะหมดเวลา การลองใหม่ และตัวตัดวงจรสำหรับทุกการเรียก
  • การทดสอบสัญญา เพื่อไม่ให้ผู้ให้บริการทำลายผู้ใช้บริการโดยที่ไม่มีใครสังเกตเห็น

หากทีมของคุณยังดูแลบริการเดียวให้ดีไม่ได้ บริการสิบแห่งจะทำให้สถานการณ์แย่ลงสิบเท่า

การกำหนดขนาดบริการให้เหมาะสม

คำว่า “ไมโคร” ทำให้เข้าใจผิด — ควรกำหนดขนาดบริการจาก ความสามารถทางธุรกิจและความรับผิดชอบของทีม ไม่ใช่จากจำนวนบรรทัดโค้ด หากแบ่งละเอียดเกินไป (เป็นนาโนเซอร์วิส) ฟีเจอร์เดียวจะแตกแขนงเป็นการเรียกผ่านเครือข่ายจำนวนมาก หากแบ่งหยาบเกินไป คุณก็จะกลับไปมีโมโนลิทอีกครั้ง

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

ทางเลือกโมโนลิทแบบโมดูล

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

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

<?php
// Modules talk only through interfaces, never each other's tables.
namespace App\Billing;          // owns billing_* tables
interface BillingFacade {
    public function invoiceForOrder(string $orderId): string;
}

namespace App\Sales;            // owns sales_* tables
final class Checkout {
    public function __construct(private \App\Billing\BillingFacade $billing) {}
    // Sales never SELECTs from billing_* directly - only via the facade.
}

ตรวจสอบอย่างรวดเร็ว

การสังเกตการแยกที่ไม่ดี

สรุปทบทวน

การกำหนดขอบเขตบริการให้ดี:

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

ถัดไป: บริการเหล่านี้สื่อสารกันอย่างไรจริง ๆ — REST และ gRPC

คำถามที่พบบ่อย

บทเรียน “จากโมโนลิทสู่ไมโครเซอร์วิส” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “จากโมโนลิทสู่ไมโครเซอร์วิส” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “จากโมโนลิทสู่ไมโครเซอร์วิส”

ตัดสินใจว่าควรแยกส่วนใดและกำหนดขอบเขตบริการอย่างไร คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “จากโมโนลิทสู่ไมโครเซอร์วิส” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม

ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. จากโมโนลิทสู่ไมโครเซอร์วิส
  2. การสื่อสารระหว่างบริการ: REST และ gRPC
  3. เกตเวย์ API และการค้นพบบริการ
  4. ความทนทาน: ตัวตัดวงจรและการลองใหม่
← กลับไปที่ PHP Academy