แอกกริเกต รีโพซิทอรี และแฟกทอรี
ปกป้องค่าคงที่ของโดเมนด้วยแอกกริเกตและจัดเก็บข้อมูลอย่างเป็นระเบียบ
แอกกริเกต รีโพซิทอรี และแฟกทอรี เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน
การปกป้องเงื่อนไขคงตัว
เมื่อคุณมีเอนทิตีและอ็อบเจ็กต์ค่าแล้ว คุณต้องใช้รูปแบบเพื่อรักษากลุ่มของสิ่งเหล่านี้ให้ สอดคล้องกัน และ จัดเก็บอย่างเป็นระเบียบ DDD มีคำตอบเป็นรูปแบบเชิงยุทธวิธีสามอย่าง ได้แก่ แอกกริเกต (ขอบเขตความสอดคล้อง), รีโพซิทอรี (สิ่งนามธรรมด้านการจัดเก็บที่มีลักษณะเหมือนคอลเลกชัน) และ แฟกทอรี (การสร้างที่ซับซ้อน) บทเรียนนี้จะแสดงให้เห็นว่ารูปแบบเหล่านี้ทำงานร่วมกันใน PHP อย่างไร
แอกกริเกตคืออะไร
แอกกริเกต คือกลุ่มของเอนทิตีและอ็อบเจ็กต์ค่าที่ถือเป็นหน่วยเดียวสำหรับการเปลี่ยนแปลงข้อมูล เอนทิตีหนึ่งรายการคือ Aggregate Root ซึ่งเป็นสมาชิกเพียงรายการเดียวที่โค้ดภายนอกสามารถถือการอ้างอิงได้ การแก้ไขทั้งหมดต้องดำเนินการผ่านราก ซึ่งจะบังคับใช้เงื่อนไขคงตัวของแอกกริเกต แอกกริเกตยังเป็น ขอบเขตธุรกรรม โดยจะถูกโหลดและบันทึกแบบเป็นอะตอมิก
รากปกป้องทั้งแอกกริเกต
ผู้เรียกจากภายนอกจะไม่แตะต้องสมาชิกภายในโดยตรง หากต้องการเพิ่มรายการสินค้า คุณจะเรียกเมธอดบนราก ซึ่งจะตรวจสอบความถูกต้องและรักษาความสอดคล้อง เช่น ยอดรวมและขีดจำกัด วิธีนี้ทำให้เงื่อนไขคงตัวอยู่ในที่เดียว
<?php
final class LineItem {
public function __construct(
public readonly string $sku,
public readonly int $qty,
public readonly int $unitCents
) {}
public function subtotal(): int { return $this->qty * $this->unitCents; }
}
final class Order { // Aggregate Root
/** @var LineItem[] */
private array $items = [];
public function __construct(public readonly string $id) {}
public function addItem(string $sku, int $qty, int $unitCents): void {
if ($qty < 1) { throw new DomainException('qty must be >= 1'); }
$this->items[] = new LineItem($sku, $qty, $unitCents);
}
public function total(): int {
return array_sum(array_map(fn(LineItem $i) => $i->subtotal(), $this->items));
}
}
$o = new Order('o1');
$o->addItem('A', 2, 500);
$o->addItem('B', 1, 300);
echo $o->total(), PHP_EOL; // 1300
การออกแบบแอกกริเกตขนาดเล็ก
ข้อผิดพลาดที่พบได้บ่อยคือการทำให้แอกกริเกตมีขนาดใหญ่เกินไป เช่น Order ที่เป็นเจ้าของกราฟ Customer ทั้งหมดด้วย แนวทางโดยสรุปมีดังนี้:
- ทำแอกกริเกตให้ เล็ก และอ้างอิงแอกกริเกตอื่นด้วยรหัส แทนการถืออ็อบเจ็กต์นั้นไว้
- ธุรกรรมหนึ่งรายการควรแก้ไข แอกกริเกตหนึ่งรายการ และควรประสานงานข้ามแอกกริเกตด้วยเหตุการณ์โดเมน
- เงื่อนไขคงตัวที่ต้องเป็นจริงเสมอจะเป็นตัวกำหนดขอบเขต
อ้างอิงด้วยรหัสประจำตัว
คำสั่งซื้อจะเก็บอ็อบเจ็กต์ค่า customerId ไม่ใช่เอนทิตี Customer วิธีนี้ทำให้ขอบเขตความสอดคล้องกระชับและหลีกเลี่ยงการโหลดกราฟอ็อบเจ็กต์ขนาดใหญ่ ความสอดคล้องข้ามแอกกริเกตจะเกิดขึ้นในท้ายที่สุด โดยจัดการผ่านเหตุการณ์แทนการใช้ธุรกรรมขนาดใหญ่เพียงรายการเดียว
<?php
final class CustomerId {
public function __construct(public readonly string $value) {}
}
final class Order {
public function __construct(
public readonly string $id,
public readonly CustomerId $customerId // reference, not object
) {}
}
$order = new Order('o1', new CustomerId('cus_99'));
echo $order->customerId->value, PHP_EOL;
สัญญาของรีโพซิทอรี
รีโพซิทอรี ทำให้ดูเหมือนว่ามีคอลเลกชันในหน่วยความจำของรากแอกกริเกต โดเมนจะพึ่งพาเพียง ส่วนติดต่อ เท่านั้น ส่วนการนำไปใช้งานจริง เช่น ด็อกทริน PDO หรือหน่วยความจำ จะอยู่ในชั้นโครงสร้างพื้นฐาน รีโพซิทอรีจัดการกับ แอกกริเกตทั้งก้อน ไม่ใช่แถวข้อมูลบางส่วน
<?php
interface OrderRepository {
public function ofId(string $id): ?Order;
public function save(Order $order): void;
public function nextIdentity(): string;
}
การนำไปใช้งานในหน่วยความจำ
รีโพซิทอรีในหน่วยความจำมีคุณค่าอย่างมากสำหรับการทดสอบระดับหน่วยที่รวดเร็วและไม่ต้องใช้ฐานข้อมูล เนื่องจากโดเมนพึ่งพาเพียงส่วนติดต่อ คุณจึงสลับการนำไปใช้งานได้อย่างอิสระ ซึ่งเป็นตัวอย่างของการกลับทิศทางการพึ่งพา
<?php
interface OrderRepository {
public function ofId(string $id): ?object;
public function save(object $order): void;
public function nextIdentity(): string;
}
final class Order { public function __construct(public readonly string $id) {} }
final class InMemoryOrderRepository implements OrderRepository {
private array $store = [];
public function ofId(string $id): ?object { return $this->store[$id] ?? null; }
public function save(object $order): void { $this->store[$order->id] = $order; }
public function nextIdentity(): string { return 'o_' . bin2hex(random_bytes(4)); }
}
$repo = new InMemoryOrderRepository();
$repo->save(new Order('o1'));
var_dump($repo->ofId('o1') !== null);
รีโพซิทอรีไม่ใช่ DAO
รีโพซิทอรี ไม่ใช่ DAO ทั่วไปสำหรับการสร้าง อ่าน แก้ไข และลบข้อมูล แต่เปิดเผย คำค้นที่มีความหมายต่อโดเมน เช่น findOverdueOrders() และสร้างแอกกริเกตทั้งหมดกลับคืนมาโดยยังคงเงื่อนไขคงตัวไว้อย่างครบถ้วน รีโพซิทอรีจงใจซ่อนรายละเอียดคำสั่งฐานข้อมูลและการแมปออบเจ็กต์ เพื่อให้โดเมนไม่ต้องรับรู้เรื่องการจัดเก็บข้อมูล หลีกเลี่ยงการนำตัวสร้างคำค้นหรือเมธอดทั่วไปอย่าง save($anyEntity) เข้ามาในโดเมน
แฟกทอรีสำหรับการสร้างที่ซับซ้อน
เมื่อการสร้างแอกกริเกตเกี่ยวข้องกับตรรกะจริง เช่น การสร้างอัตลักษณ์ การประกอบอ็อบเจ็กต์ค่า และการบังคับใช้เงื่อนไขคงตัวขณะสร้าง ให้ย้ายตรรกะนั้นไปไว้ใน แฟกทอรี ซึ่งอาจเป็นคลาสเฉพาะหรือตัวสร้างแบบตั้งชื่อชนิดสแตติก วิธีนี้ทำให้ตัวสร้างของเอนทิตีมีหน้าที่ชัดเจน และรวมกฎของการสร้างที่ถูกต้องไว้ในที่เดียว
<?php
final class Order {
private function __construct(
public readonly string $id,
public readonly string $customerId
) {}
public static function place(string $customerId): self {
if ($customerId === '') { throw new DomainException('customer required'); }
return new self('o_' . bin2hex(random_bytes(4)), $customerId);
}
}
$order = Order::place('cus_1');
echo $order->id, PHP_EOL;
การทำงานร่วมกัน
ลำดับการทำงานทั่วไปในบริการแอปพลิเคชันมีดังนี้:
- แฟกทอรี หรือตัวสร้างแบบตั้งชื่อ จะสร้างแอกกริเกตที่ถูกต้อง
- เมธอดของรากแอกกริเกตจะบังคับใช้เงื่อนไขคงตัวระหว่างการใช้งาน
- รีโพซิทอรี จะจัดเก็บและสร้างแอกกริเกตทั้งหมดกลับคืนมาในภายหลัง
บริการแอปพลิเคชันจะประสานงานสิ่งเหล่านี้ภายในธุรกรรมหนึ่งรายการต่อแอกกริเกต โดยพึ่งพาเพียงส่วนติดต่อ
การบังคับใช้เงื่อนไขคงตัวของแอกกริเกตทั้งหมด
คุณค่าที่แท้จริงของรากอยู่ที่การบังคับใช้เงื่อนไขคงตัวซึ่งครอบคลุมสมาชิกหลายรายการ ในที่นี้คำสั่งซื้อจะปฏิเสธรายการสินค้าหากรายการนั้นทำให้ยอดรวมเกินวงเงินเครดิต ซึ่งเป็นกฎที่ LineItem รายการเดียวไม่สามารถบังคับใช้ได้โดยลำพัง เนื่องจากการเปลี่ยนแปลงทั้งหมดต้องผ่านราก กฎนี้จึงไม่สามารถถูกข้ามได้
<?php
final class Order {
private array $items = [];
public function __construct(
public readonly string $id,
private int $creditLimitCents
) {}
public function addItem(int $cents): void {
if ($this->total() + $cents > $this->creditLimitCents) {
throw new DomainException('Exceeds credit limit');
}
$this->items[] = $cents;
}
public function total(): int { return array_sum($this->items); }
}
$o = new Order('o1', 1000);
$o->addItem(600);
try { $o->addItem(600); } catch (DomainException $e) { echo $e->getMessage(), PHP_EOL; }
echo $o->total(), PHP_EOL; // 600
ตรวจสอบอย่างรวดเร็ว
การออกแบบแอกกริเกต
สรุปทบทวน
คุณได้เรียนรู้วิธีปกป้องเงื่อนไขคงตัวและจัดเก็บข้อมูลอย่างเป็นระเบียบแล้ว แอกกริเกต สร้างขอบเขตความสอดคล้องและขอบเขตธุรกรรม โดยแก้ไขได้เฉพาะผ่านราก และรักษาขนาดให้เล็กด้วยการอ้างอิงแอกกริเกตอื่นด้วยรหัส รีโพซิทอรี นำเสนอรากแอกกริเกตในรูปคอลเลกชันผ่านส่วนติดต่อของโดเมน ซ่อนเครื่องมือแมปออบเจ็กต์และคำสั่งฐานข้อมูล พร้อมเปิดให้ใช้ตัวแทนการทดสอบในหน่วยความจำได้ แฟกทอรี รวมศูนย์การสร้างที่ซับซ้อนและบังคับใช้เงื่อนไขคงตัว เมื่อใช้ร่วมกัน ทั้งหมดนี้จะช่วยให้แบบจำลองโดเมนสอดคล้องกันและไม่ต้องรับรู้เรื่องการจัดเก็บข้อมูล
คำถามที่พบบ่อย
บทเรียน “แอกกริเกต รีโพซิทอรี และแฟกทอรี” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “แอกกริเกต รีโพซิทอรี และแฟกทอรี” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “แอกกริเกต รีโพซิทอรี และแฟกทอรี”
ปกป้องค่าคงที่ของโดเมนด้วยแอกกริเกตและจัดเก็บข้อมูลอย่างเป็นระเบียบ คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “แอกกริเกต รีโพซิทอรี และแฟกทอรี” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม
ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- องค์ประกอบพื้นฐาน DDD: เอนทิตีและออบเจ็กต์ค่า
- แอกกริเกต รีโพซิทอรี และแฟกทอรี
- เหตุการณ์โดเมนและบริการโดเมน
- บริบทที่มีขอบเขตและการทำแผนผังบริบท