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

แตก 1 context ออก​เป็น service จริง

บท​ที่​แล้ว​เรา​ตั้ง​กฎ​ไว้​ว่า แตก​เมื่อ​มี force จริง​เท่านั้น — และ​ตอน​นี้ force มา​ถึง Delivery จริงๆ: โหลด​ของ​ฝั่ง​จัด​ส่ง​พุ่ง​ไม่​พร้อม​กับ​ที่​เหลือ (ช่วง​มื้อ​เที่ยง​ไร​เด​อร์ทั้ง​เมือง​ออนไลน์​พร้อม​กัน) และ​มี​ทีม​แยก​ที่​ควร​เป็น​เจ้าของ​มัน​เต็ม​ตัว ปล่อย​ของ​ตาม​จังหวะ​ตัวเอง​ได้ ทั้ง​คอร์ส​ที่​ผ่าน​มา​เรา​ลงแรง​ทำBounded ContextBounded Contextขอบเขต​ที่ ubiquitous language และ model หนึ่ง​ชุด​มี​ความหมาย​คง​เส้น​คง​วา นอก​ขอบเขต​นี้​คำ​เดียวกัน​อาจ​หมาย​ถึง​คนละ​อย่าง เช่น 'order' ใน Ordering กับ 'ticket' ใน Kitchen คือ​มุมมอง​ของ​สิ่ง​เดียวกัน​คนละ modelStrategic Designให้​ขอบ​คม — แต่ละ context เป็น​เจ้าของ schema ตัวเอง คุย​กัน​ผ่านIntegration EventIntegration Eventevent ที่​สื่อสาร 'ข้าม' Bounded Context เป็น contract แบบ serializable ที่​มี​แต่ id/ค่า​พื้นฐาน (ไม่มี domain object) เช่น OrderConfirmedIntegrationEvent(Guid OrderId, DateTimeOffset ConfirmedAt) ต่าง​จาก Domain Event ที่​อยู่​ใน​โพรเซส​เดียวArchitectureล้วน — จน​ตอน​นี้​การ​แตก​เหลือ​แค่​งาน​เล็กๆ งาน​เดียว บท​สุดท้าย​นี้​จะ ดึง Delivery ออก​จากModular MonolithModular Monolithสถาปัตยกรรม​ที่​ทุก context เป็น module/assembly แยก​กัน​ชัดเจน​แต่​รัน​ใน​โพรเซส​เดียวกัน แต่ละ module เป็น​เจ้าของ model/schema ของ​ตัวเอง ห้าม​อ้างอิง domain type ข้าม module ตรง ๆ เชื่อม​ได้​เฉพาะ​ผ่าน integration event หรือ query ที่​แปล​แล้วArchitectureให้​เป็น service คนละโพร​เซ​สจริงๆ แล้ว​ดู​ว่า​มี​อะไร​เปลี่ยน​บ้าง และ​ที่​สำคัญ​กว่า — มี​อะไร ไม่ เปลี่ยน​เลย

📦 code ตัวอย่าง

code เต็ม​ของ​คอร์ส​นี้​อยู่​ที่ repo kaen-food-ordering (กำลัง​จัด​ทำ) — บท​นี้​แยก module Delivery ออก​จาก host ก้อน​เดิม​เป็น project ที่ deploy แยก โดย​เพิ่ม RabbitMQ transport ของ WolverineFx เข้าไป​ทั้ง​สอง​ฝั่ง สแนป​เพ็ต​ใน​บท​เน้น​ให้​เห็น “เส้น​แบ่ง​ระหว่าง​สิ่ง​ที่​แก้​กับ​สิ่ง​ที่​ไม่​แตะ” ตัว​จริง​ใน repo จะมี config ครบ​กว่า​นี้

ทวน​ภาพ​จาก​บท 6 ก่อน ตอน​อยู่​ใน monolith เมื่อ Ordering ยืนยัน​ออเดอร์ handler ของ​มัน​เขียน state กับ event ลงTransactional OutboxTransactional Outboxpattern แก้ dual-write problem: เขียน event ลง​ตาราง outbox ใน transaction เดียว​กับ​ข้อมูล​จริง แล้ว​ให้ relay แยก​ต่างหาก​ส่ง event ออก​ไป​หลัง commit เท่านั้น — WolverineFx ทำ​สิ่ง​นี้​ให้​บน EF Core ผ่าน IDbContextOutbox<T>Architectureใน​ธุรกรรม​เดียว แล้ว relay ของ Wolverine ก็​ปล่อย OrderConfirmedIntegrationEvent เข้า in-process queue ให้ handler ของ Delivery รับ​ไป​เปิด​งาน​รับ-ส่ง (Assignment) — ทั้งหมด​เกิด​ใน​โพรเซส​เดียว เป็นการ​ส่ง​ข้อความ​ภายใน​เครื่อง

handler ฝั่ง Delivery หน้าตา​แบบ​นี้ และ​มัน​คือ​หัวใจ​ของ​บท​นี้​ทั้ง​บท เพราะ​เดี๋ยว​เรา​จะ​ยก​มัน​ข้าม​เส้น process โดย ไม่​แตะ​แม้​บรรทัด​เดียว:

Delivery handler — ตัว​นี้​จะ​ไม่​ถูก​แก้​ตลอด​ทั้ง​บท
// FoodOrdering.Delivery/Integration/WhenOrderConfirmed.cs
public class WhenOrderConfirmed
{
public async Task Handle(OrderConfirmedIntegrationEvent evt, DeliveryDbContext db)
{
// idempotent guard ระดับ domain: ออเดอร์นี้มีงานรับ-ส่งแล้วหรือยัง
var already = await db.Assignments.AnyAsync(a => a.OrderId == evt.OrderId);
if (already) return;
db.Assignments.Add(Assignment.Create(evt.OrderId)); // สถานะ Unassigned
await db.SaveChangesAsync();
}
}

สังเกต​ว่า handler ตัว​นี้​รับ​แค่ OrderConfirmedIntegrationEvent (ที่​มี Guid OrderId ล้วน) กับ DeliveryDbContext ของ​ตัวเอง — มัน​ไม่รู้จัก Order ของ Ordering, ไม่รู้​ว่า​ใคร​ส่ง event มา, และ​ไม่รู้​ด้วย​ซ้ำ​ว่า​ตอน​นี้​ตัวเอง​รัน​อยู่​ใน​โพรเซส​เดียว​กับ​ผู้​ส่ง​หรือ​คนละ​เครื่อง มัน​เห็น​แค่ สัญญา กับ DB ของ​ตัวเอง เท่านั้น

นี่​คือ​ผล​ตอบแทน​ของ​งาน​ที่​ลงแรง​มา​หกบท เมื่อ Delivery ข้าม​เส้น process ออก​ไป สาม​อย่าง​นี้ อยู่​เหมือน​เดิม​ทุก​ประการ:

  • ตัว handler — code ด้าน​บน​คือ file เดียวกัน​ก่อน​และ​หลัง​แตก ไม่มี if (distributed) ไม่มี​การ​เปลี่ยน signature เพราะ​มัน​พึ่งพา​แค่​สัญญา​กับ DbContext ของ​ตัวเอง ทั้ง​สอง​อย่าง​เดิน​ทางข้าม​เครื่อง​ไป​ด้วย​กัน​ได้
  • สัญญา (contract)OrderConfirmedIntegrationEvent(Guid OrderId, DateTimeOffset ConfirmedAt) เป็นMessage ContractMessage Contractรูปแบบ​ข้อมูล (schema) ของ integration event ที่​ตกลง​กัน​ระหว่าง​ผู้​ส่ง​และ​ผู้รับ ต้อง stable และ​มี​แต่ id/ค่า​พื้นฐาน เพราะ​เปลี่ยน​แล้วกระทบ​ทุก context ที่ subscribe อยู่ — ใน​ทาง​ปฏิบัติ​ของ code คือ Published Language นั่นเองArchitectureที่​แบน​ราบ serialize ได้ มี​แต่ id กับ​ค่า​พื้นฐาน​มา​ตั้งแต่​บท 4 อยู่​แล้ว มัน​เคย​เป็น​แค่ “ข้อความ​ใน​โพรเซส” วัน​นี้​กลาย​เป็น “ข้อความ​บน​สาย” โดยที่​รูปร่าง​ไม่​ต้อง​เปลี่ยน​เลย เพราะ​มัน​ถูก​ออกแบบ​ให้​พร้อม​ข้าม​สาย​มา​ตั้งแต่​ต้น
  • เส้น​ขอบ / Anti-Corruption LayerAnti-Corruption Layer (ACL)ชั้น​แปล​ภาษา/model ที่​กั้น​ระหว่าง context สอง​อัน ไม่​ให้ model ของ​อีก​ฝ่าย​รั่ว​เข้า​มา​ปน​เปื้อน เช่น PaymentGatewayAdapter ที่​แปล Money/Order ของ Ordering เป็น DTO ของ​ผู้​ให้​บริการ​ชำระ​เงิน​ภายนอกStrategic Design — Delivery ไม่​เคย​เอื้อม​เข้าไป​ใน model ของ Ordering อยู่​แล้ว ตัว​แปล​ที่​ขอบ (บท 5) จบ​ภาษา​ของ Ordering ตั้งแต่​ที่​รอย​ต่อ ทำให้​ข้าง​ใน Delivery พูด​แต่ Assignment ล้วน เส้น​นี้​กัน model รั่ว​ได้​เท่า​กัน​ไม่​ว่า​อีก​ฝ่าย​จะ​อยู่​ห่าง​ไป​กี่​เครื่อง

กล่าว​อีก​แบบ: สิ่ง​ที่​ทำให้​แตก​ได้​ถูก​ทำ​เสร็จ​ไป​หมด​แล้ว​ใน​บท​ก่อนๆ — สัญญา id-only, การ​เป็น​เจ้าของ schema แยก​กัน, ACL ที่​ขอบ, outbox ที่​มัด state กับ event ให้ atomic บท​นี้​ไม่​ได้​แก้ model มัน​แค่​เปลี่ยน​ว่า​ข้อความ​เดินทาง​ด้วย​อะไร

สาม​สิ่ง​ที่​เปลี่ยน​จริง​ล้วน​อยู่​ที่ ขอบ​นอก​สุด — ไม่มี​อัน​ไหน​แตะ domain: (1) hosting/deployment — จาก1 process กลาย​เป็น​สอง, (2) transport config — เพิ่มMessage BrokerMessage Brokerตัวกลาง​ที่​รับ-ส่ง​ข้อความ​ระหว่าง context ที่​แยก​เป็น service จริง​แล้ว เช่น RabbitMQ ทำให้​ผู้​ส่ง​ไม่​ต้อง​รู้จัก​ผู้รับ​โดยตรง (decoupled) — เป็น​สิ่ง​ที่​ต้อง​มี​เมื่อ​แตก​จาก modular monolith ไป​เป็น distributed serviceArchitectureเข้า​มา​เป็น​ตัวกลาง, (3) ปลายทาง​ที่ outbox ผลัก​ข้อความ​ไป — จาก in-process queue เป็น RabbitMQ exchange

เริ่ม​ที่ Ordering host เดิม — เรา​ไม่​ลบ​อะไร​จาก​บท 6 เลย แค่ ต่อ​ท้าย transport ใหม่​เข้าไป:

Ordering host — เพิ่ม RabbitMQ ต่อ​จาก outbox เดิม (domain ไม่​แตะ)
builder.UseWolverine(opts =>
{
var pg = builder.Configuration.GetConnectionString("ordering")!;
// --- ของเดิมจากบท 6: durable outbox บน EF Core ยังอยู่ครบ ไม่แก้ ---
opts.Services.AddDbContextWithWolverineIntegration<OrderingDbContext>(x => x.UseNpgsql(pg));
opts.PersistMessagesWithPostgresql(pg, "wolverine");
opts.Policies.AutoApplyTransactions();
// --- ของใหม่ในบทนี้: transport ข้ามเครื่อง ---
var rabbit = builder.Configuration.GetConnectionString("rabbit")!;
opts.UseRabbitMq(rabbit)
.AutoProvision() // สร้าง exchange/queue ให้เองถ้ายังไม่มี
.UseConventionalRouting(); // route ตามชนิดข้อความอัตโนมัติ ไม่ต้อง map ทีละใบ
});

แล้ว​ตั้ง โพรเซส​ใหม่ สำหรับ Delivery service — มัน​มี DB กับ outbox ของ​ตัวเอง (แยก​จาก Ordering คนละ​ฐาน​ข้อมูล) แล้ว subscribe สัญญา​เดิม​ผ่าน transport เดียวกัน:

Delivery service — โพรเซส​ใหม่ subscribe contract เดิม​ผ่าน RabbitMQ
builder.UseWolverine(opts =>
{
var pg = builder.Configuration.GetConnectionString("delivery")!;
// Delivery เป็นเจ้าของ DB + outbox ของตัวเอง
opts.Services.AddDbContextWithWolverineIntegration<DeliveryDbContext>(x => x.UseNpgsql(pg));
opts.PersistMessagesWithPostgresql(pg, "wolverine");
opts.Policies.AutoApplyTransactions();
// transport เดียวกัน — conventional routing พา OrderConfirmedIntegrationEvent
// เข้า WhenOrderConfirmed ของ Delivery ให้เอง โดยผู้ส่งไม่ต้องรู้จัก Delivery เลย
var rabbit = builder.Configuration.GetConnectionString("rabbit")!;
opts.UseRabbitMq(rabbit).AutoProvision().UseConventionalRouting();
});

และ​นี่​คือ​จุด​ที่ outbox กลาย​เป็น relay ออก broker: บรรทัด outbox.SaveChangesAndFlushMessagesAsync() ใน​ฝั่ง Ordering ยัง​ทำงาน​คำ​เดิม​เป๊ะ — เซฟ state กับ event ใน transaction เดียว แล้ว flush หลัง commit สิ่ง​ที่​ต่าง​คือ ปลายทาง​ของ flush เดิม relay ผลัก​ข้อความ​เข้า in-process queue ตอน​นี้​มัน​ผลัก​ออก RabbitMQ exchange แทน ที่ handler ที่​เรียก PublishAsync ไม่รู้​เลย​ว่า​ปลายทาง​ย้าย​ไป​คนละ​เครื่อง — มัน​เห็น​แค่ outbox เหมือน​เดิม transactional outbox ที่​เรา​วาง​ไว้​ใน​บท 6 จึง​กลาย​เป็น​สะพาน​ที่​ทำให้​ข้อความ​ข้าม​สาย​ได้​อย่าง​เชื่อถือ​ได้​ทันที ไม่​ต้อง​ประกอบ​ใหม่

มี​จุด​หนึ่ง​ที่​ต้อง​ระวัง: ตอน​อยู่​ใน monolith OrderConfirmedIntegrationEvent มี handler ใน​เครื่อง​สอง​ตัว — Kitchen กับ Delivery — เรา fan-out ให้​ทั้ง​คู่​ด้วย opts.MultipleHandlerBehavior = MultipleHandlerBehavior.Separated; (Wolverine แยก​คิว​ให้​แต่ละ handler แล้ว​ยิง​ข้อความ​ใบ​เดียว​เข้า​ทั้ง​สอง) พอ​เรา​แตก Delivery ออก​ไป Kitchen ยัง​อยู่​ใน​เครื่อง แต่ Delivery ไป​อยู่​นอก​เครื่อง​แล้ว — event ใบ​เดียวกัน​ต้อง​ไป​ให้​ถึง​ทั้ง​สอง​ปลายทาง​ที่​คนละ​ที่

ปกติ conventional routing จะ​ส่ง​ข้อความ​ออก broker ก็​ต่อ​เมื่อ​ไม่มี handler ใน​เครื่อง ซึ่ง​จะ​ทำให้ Delivery (นอก​เครื่อง) อด​รับ เพราะ Kitchen (ใน​เครื่อง) ยัง “ดูด” event ไว้ ทาง​แก้​คือ​บรรทัด​เดียว:

Ordering host — ให้ event ที่​มี handler ใน​เครื่อง ยัง​ออก​นอก​เครื่อง​ด้วย
// additive = route "ทั้ง" ในเครื่อง (Kitchen) และออก broker (Delivery service) พร้อมกัน
opts.Policies.ConventionalLocalRoutingIsAdditive();

ConventionalLocalRoutingIsAdditive() เปลี่ยน​กติกา​จาก “ใน​เครื่อง​หรือ​ข้าม​เครื่อง อย่าง​ใด​อย่าง​หนึ่ง” เป็น “ใน​เครื่อง และ ข้าม​เครื่อง” — Kitchen ที่​ยัง​เป็น module ใน​เครื่อง​ก็​รับ event ต่อ​ไป​เหมือน​เดิม ขณะ​ที่ Delivery ที่​แยก​ออก​ไป​ก็​รับ​ผ่าน RabbitMQ ด้วย นี่​คือ​เสน่ห์​ของ​การ​แตก​ที​ละ context: เรา​ไม่​ต้อง​แตก​ทุก​อัน​พร้อม​กัน ตัว​ที่​ยัง​อยู่​ใน​เครื่อง​กับ​ตัว​ที่​แยก​ออก​ไป​อยู่​ร่วม​กัน​ได้​บน​สัญญา​ใบ​เดียวกัน

เพราะ​ข้อความ​ข้าม​สาย​จริง​แล้ว At-least-once deliveryAt-least-once Deliveryการ​รับประกัน​ของ message broker ว่า​ข้อความ​จะ​ถูก​ส่ง​ถึง​ผู้รับ 'อย่าง​น้อยหนึ่ง​ครั้ง' แต่​อาจ​ซ้ำ​ได้​ใน​กรณี​ล่ม/retry ผู้รับ​จึง​ต้อง​เป็น idempotent consumer เพื่อ​รองรับ​ความ​ซ้ำ​นี้Architecture ก็​เลิก​เป็น​ทฤษฎี — RabbitMQ retry เมื่อ​เน็ต​กระตุก ข้อความ​เดิม​มา​ซ้ำ​ได้​จริง​ทุก​วัน แต่​เรา​เตรียม​ไว้​แล้ว: WhenOrderConfirmed เป็นIdempotent ConsumerIdempotent Consumerผู้รับ event ที่​ออกแบบ​ให้​ประมวล​ผล​ข้อความ​เดิม​ซ้ำ​ได้​โดย​ผลลัพธ์​เหมือน​เดิม (ไม่ใช่​ผล​ซ้ำซ้อน) จำเป็น​เพราะ at-least-once delivery การันตี​แค่ 'ส่ง​ถึง​อย่าง​น้อยหนึ่ง​ครั้ง' ไม่ใช่ 'พอดี​หนึ่ง​ครั้ง'Architectureที่​เช็ก Assignments.AnyAsync(...) ก่อน​ทุก​ครั้ง ข้อความ​ซ้ำ​จึง​ไม่​ทำให้​เกิด​งาน​รับ-ส่ง​ซ้ำ — guard ตัว​เดิม​ที่​เขียน​ไว้​ตอน​ยัง​เป็น monolith กลาย​เป็น​เกราะ​ที่​จำเป็น​จริง​ตอน​ข้าม​เครื่อง

flowchart TB
  subgraph BEFORE["ก่อนแตก · โพรเซสเดียว = modular monolith"]
    direction TB
    OB1["Ordering handler<br/>outbox.SaveChangesAndFlushMessagesAsync"]
    Q1["in-process queue ของ Wolverine"]
    DH1["Delivery handler<br/>WhenOrderConfirmed → Assignment.Create"]
    OB1 -->|"OrderConfirmedIntegrationEvent"| Q1
    Q1 -->|"in-process dispatch"| DH1
  end
  subgraph AFTER["หลังแตก · Delivery เป็น service คนละโพรเซส"]
    direction TB
    OB2["Ordering host: handler เดิม<br/>code ไม่แก้แม้บรรทัดเดียว"]
    BROKER{{"RabbitMQ<br/>message broker"}}
    DH2["Delivery service: handler เดิม<br/>code ไม่แก้แม้บรรทัดเดียว"]
    OB2 -->|"OrderConfirmedIntegrationEvent · contract เดิม"| BROKER
    BROKER -->|"ยิงข้ามเครือข่าย · at-least-once"| DH2
  end
  BEFORE ==>|"เปลี่ยนแค่ transport + hosting · boundary/contract คงเดิม"| AFTER
  classDef same fill:#bbf7d0,stroke:#166534,color:#1a1a1f;
  classDef infra fill:#fde68a,stroke:#92400e,color:#1a1a1f;
  class OB1,DH1,OB2,DH2 same;
  class Q1,BROKER infra;

คำ​บรรยาย​ภาพ: กล่อง​เขียว​คือ สิ่ง​ที่​ไม่​เปลี่ยน — handler ฝั่ง Ordering และ Delivery เป็น code ชุด​เดิม​ทั้ง​ก่อน​และ​หลัง​แตก กล่อง​เหลือง​คือ สิ่ง​ที่​เปลี่ยน — เดิม​ข้อความ​วิ่ง​ผ่าน in-process queue (บน) พอ​แตก​แล้ว​มัน​วิ่ง​ผ่าน RabbitMQ ข้าม​เครือข่าย​แทน (ล่าง) สัญญา OrderConfirmedIntegrationEvent ที่​แล่น​บน​ลูกศร​เป็น​ใบ​เดิม​ไม่​เปลี่ยน​รูป และ​เส้น​ขอบ context ก็​อยู่​ที่​เดิม — การ​แตก​จึง​เป็นการ​สลับ ตัวกลาง​ลำเลียง ไม่ใช่​การ​รื้อ model หรือ​ขยับ​เส้น​ขอบ

ถอย​มา​มอง​ทั้ง​การ​เดินทาง​บน​เว็บ​นี้ มัน​ปิด​วง​พอดี:

  • Event Storming — ทั้ง​ทีม​ยืน​หน้า​ผนัง Post-it เรียง event บน​ไทม์​ไลน์ จน​เห็น​ว่า​ตรง​รอย​ต่อ​ของ event คำ​ศัพท์​เปลี่ยน​ชุด​และ​คน​ที่​คุย​เปลี่ยนหน้า นั่น​คือ​จุด​ที่ หลาย context โผล่​ขึ้น​มา​ให้​เห็น — เรา​ได้ Ordering, Kitchen, Delivery, Payment มา​จาก​ตรง​นั้น
  • ไตร​ภาค​ปั้น code — เอา Ordering ที่​เป็น core มา​ลงมือ​ปั้น​จริง: model domain ที่​ถือ​กฎ​ธุรกิจ, port/อะ​แดป​เตอร์, domain event, และ test ทั้ง​พีระมิด — จน​ได้ context หนึ่ง​ที่​แน่น​และ​เชื่อถือ​ได้
  • คอร์ส​นี้ — เอา4 context นั้น​มา ต่อ​ให้​ถึงกัน ด้วย context map, integration event, ACL, และ outbox จน​สื่อสาร​ข้าม​ขอบ​ได้​อย่าง​เชื่อถือ​ได้ แล้ว​ปิด​ท้าย​ด้วย​การ แตก​หนึ่ง​ใน​นั้น​ออก​เป็น service จริง

เส้นทาง​ทั้งหมด​คือ​สี่​จังหวะ: ค้น​ให้​เจอ → สร้าง​ให้​ถือ​กฎ → ต่อ​ให้​ถึงกัน → แตก​เมื่อ​จำเป็น และ​ประเด็น​ที่​อยาก​ทิ้ง​ไว้​เป็น​บท​สุดท้าย​คือ — จังหวะ​สุดท้าย​ถูก​เพราะ​สาม​จังหวะ​แรก​ทำ​มา​ถูก การ​แตก Delivery วัน​นี้​เป็น​แค่​การ​เพิ่ม transport กับ​ตั้ง​โพรเซส​ใหม่ ไม่ใช่​การ​นั่ง​เลาะ model ที่​พัน​กัน ก็​เพราะ​เรา​ไม่​เคย​ปล่อย​ให้​มัน​พัน​กัน​ตั้งแต่​แรก ขอบเขต​ที่​ลาก​ไว้​คม​ตั้งแต่ Event Storming คือ​สิ่ง​ที่​ทำให้​วัน​แตก​จริง​กลาย​เป็น​วัน​ธรรมดา ไม่ใช่​วัน​รื้อ​ใหญ่ — และ​นั่น​คือ​ทั้งหมด​ที่ strategic design พยายาม​ซื้อ​ไว้​ให้​เรา


🔗 อ้างอิง​เพิ่มเติม​ใน DevIQ

เจาะ​ลึก​แนวคิด​ใน​บท​นี้​ต่อ​ได้ที่​คลัง​อ้างอิง DevIQ:

  • Strangler Fig Pattern — วิธี​ค่อยๆ เลาะ context ออก​จาก monolith ที​ละ​ตัวอย่าง​ปลอดภัย แบบ​ที่​บท​นี้​แตก Delivery ออก​ไป​โดย​ไม่​แตะ​ตัว​ที่​เหลือ
  • Adapter — แก่น​ของ​บท​นี้: สลับ transport หลัง port เดียวกัน (in-process → RabbitMQ) โดย code ที่​เรียก​ใช้​ไม่รู้ตัว
  • Anti-Corruption Layer — เส้น​ขอบ​ที่​แปล​ภาษา​ที่​รอย​ต่อ ทำให้ Delivery ไม่​เคย​ผูก​กับ model ของ Ordering จึง​ยก​ข้าม​เครื่อง​ได้​ง่าย
  • Bounded Context — เส้น​ที่​เรา​ลาก​ไว้​คม​ตลอด​คอร์ส และ​เป็น​เหตุผล​ว่า​ทำไม​การ​แตก​วัน​นี้​ถึง​ถูก

เช็กความเข้าใจ — บทที่ 8

ข้อ 1 / 3

ตอนแตก Delivery ออกจาก monolith เป็น service จริง อะไรเปลี่ยนและอะไรอยู่เหมือนเดิม?