อธิบายพอร์ตและอะแดปเตอร์
แยกแกนหลักด้วยพอร์ตและอะแดปเตอร์ที่ถอดเปลี่ยนได้
อธิบายพอร์ตและอะแดปเตอร์ เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน
แนวคิดรูปหกเหลี่ยม
พอร์ตและอะแดปเตอร์ — สถาปัตยกรรมหกเหลี่ยมของ Alistair Cockburn — วาดภาพแอปพลิเคชันของคุณเป็นรูปหกเหลี่ยม ด้านในคือ ตรรกะทางธุรกิจล้วน การโต้ตอบกับโลกภายนอกทุกอย่าง (เอชทีทีพี ฐานข้อมูล คิว นาฬิกา อีเมล) ต้องข้ามผ่านพอร์ต และแต่ละพอร์ตจะมีอะแดปเตอร์อย่างน้อยหนึ่งตัวมารองรับ รูปทรงนี้ไม่มีด้านบนหรือด้านล่างที่มีสิทธิพิเศษ ส่วนติดต่อผู้ใช้และฐานข้อมูลจึงมีสถานะสมมาตรกัน ทั้งคู่เป็นเพียงอะแดปเตอร์
พอร์ตคือส่วนต่อประสาน
พอร์ตคือส่วนต่อประสานที่แกนกลางของแอปพลิเคชันเป็นเจ้าของ ใช้แสดงความต้องการหรือความสามารถในภาษาของโดเมน พอร์ตต้องไม่ทำให้คำศัพท์ของโครงสร้างพื้นฐานเล็ดลอดเข้ามา — ไม่มี PDOStatement ไม่มี GuzzleResponse และไม่มีอีโลเควนต์
<?php
// Driven (outbound) port: the core needs to persist users
interface UserRepository
{
public function byId(UserId $id): ?User;
public function save(User $user): void;
}พอร์ตขับเคลื่อนและพอร์ตถูกขับเคลื่อน
พอร์ตมีอยู่สองรูปแบบ:
- พอร์ตขับเคลื่อน (หลัก ขาเข้า) — ส่วนต่อประสานที่โลกภายนอกเรียกเพื่อขับเคลื่อนแอป โดยทั่วไปคือส่วนต่อประสานของกรณีใช้งาน
- พอร์ตถูกขับเคลื่อน (รอง ขาออก) — ส่วนต่อประสานที่แอปเรียกเพื่อเข้าถึงโลกภายนอก เช่น คลังข้อมูล ระบบส่งจดหมาย และนาฬิกา
อะแดปเตอร์ขับเคลื่อนจะเรียกเข้าสู่แกนกลาง ส่วนแกนกลางจะเรียกออกผ่านอะแดปเตอร์ที่ถูกขับเคลื่อน
<?php
// Driving (inbound) port — the public capability of the core
interface RegisterUser
{
public function handle(string $email, string $plainPassword): UserId;
}แกนกลางเป็นผู้ใช้งานพอร์ตขับเคลื่อน
กรณีใช้งานใช้งานพอร์ตขับเคลื่อนและพึ่งพาพอร์ตที่ถูกขับเคลื่อน โปรดสังเกตว่า กรณีใช้งานรับ PasswordHasher และ Clock เป็นพอร์ตที่ฉีดเข้ามา — แม้แต่เวลาและการแฮชก็ถูกทำให้เป็นนามธรรม เพื่อให้แกนกลางทำงานได้อย่างแน่นอนและทดสอบได้
<?php
final class RegisterUserService implements RegisterUser
{
public function __construct(
private UserRepository $users,
private PasswordHasher $hasher,
private Clock $clock,
) {}
public function handle(string $email, string $plain): UserId {
if ($this->users->byEmail($email)) {
throw new EmailAlreadyTaken($email);
}
$user = User::register(
$email,
$this->hasher->hash($plain),
$this->clock->now()
);
$this->users->save($user);
return $user->id();
}
}อะแดปเตอร์ที่ถูกขับเคลื่อน
อะแดปเตอร์ที่ถูกขับเคลื่อนใช้งานพอร์ตที่ถูกขับเคลื่อนโดยใช้เทคโนโลยีจริง ในที่นี้อะแดปเตอร์ PDO รองรับ UserRepository คุณสามารถสลับเป็นด็อกทริน เรดิส หรือลูกค้าส่วนต่อประสานโปรแกรมประยุกต์เอชทีทีพีได้โดยไม่ต้องแตะต้องแกนกลาง
<?php
final class PdoUserRepository implements UserRepository
{
public function __construct(private PDO $pdo) {}
public function byId(UserId $id): ?User {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([(string) $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? User::fromRow($row) : null;
}
public function save(User $user): void {
// INSERT ... ON CONFLICT UPDATE
}
}อะแดปเตอร์ขับเคลื่อน
อะแดปเตอร์ขับเคลื่อนแปลงตัวกระตุ้นจากภายนอกให้เป็นการเรียกพอร์ตขับเคลื่อน ตัวควบคุมเอชทีทีพี คำสั่ง CLI และตัวรับข้อความ ล้วนเป็นอะแดปเตอร์ขับเคลื่อนที่สับเปลี่ยนกันได้สำหรับกรณีใช้งานเดียวกัน
<?php
// CLI driving adapter
final class RegisterUserCommand
{
public function __construct(private RegisterUser $register) {}
public function run(array $argv): int {
[$email, $password] = array_slice($argv, 1);
$id = $this->register->handle($email, $password);
fwrite(STDOUT, "Created user $id\n");
return 0;
}
}อะแดปเตอร์ในหน่วยความจำสำหรับการทดสอบ
ประโยชน์สูงสุดคือ พอร์ตที่ถูกขับเคลื่อนทุกพอร์ตจะมีตัวจำลองที่ทำงานรวดเร็ว การทดสอบจะใช้กรณีใช้งานจริงร่วมกับอะแดปเตอร์ในหน่วยความจำ นาฬิกาที่ให้ผลแน่นอน และตัวแฮชที่ไม่ทำงานใด ๆ
<?php
final class FixedClock implements Clock {
public function __construct(private DateTimeImmutable $t) {}
public function now(): DateTimeImmutable { return $this->t; }
}
final class PlainHasher implements PasswordHasher {
public function hash(string $p): string { return 'h:' . $p; }
}
$service = new RegisterUserService(
new InMemoryUsers(),
new PlainHasher(),
new FixedClock(new DateTimeImmutable('2026-01-01'))
);
echo 'wired OK', PHP_EOL;อะแดปเตอร์มีหน้าที่แปล ไม่ใช่ตัดสินใจ
ข้อผิดพลาดที่พบบ่อยคือการปล่อยให้กฎทางธุรกิจรั่วไหลเข้าไปในอะแดปเตอร์ หลักจำง่ายคือ อะแดปเตอร์มีหน้าที่ แปลรูปแบบข้อมูลและโพรโทคอลเท่านั้น หากพบ if ที่เกี่ยวกับการกำหนดราคา สิทธิ์ หรือสถานะอยู่ภายในตัวควบคุมหรือคลังข้อมูล ตรรกะนั้นควรอยู่ในแกนหลัก
- การแมประหว่างเจสันกับอ็อบเจ็กต์ถ่ายโอนข้อมูล: อะแดปเตอร์
- การเติมข้อมูลเอนทิตีจากเอสคิวแอล: อะแดปเตอร์
- "VIP ได้ส่วนลด 10%": แกนหลัก
หนึ่งพอร์ต หลายอะแดปเตอร์
พอร์ตช่วยให้สลับการใช้งานได้ และยังรองรับอะแดปเตอร์แบบ ขนาน ได้ด้วย อะแดปเตอร์อีเมล SMS และ Slack สามารถนำมาประกอบร่วมกันผ่าน NotificationPort ได้ แกนหลักเรียกใช้เมธอดเดียว ส่วนการเชื่อมต่อเป็นผู้กำหนดว่าจะให้ตอบกลับกี่ช่องทาง
<?php
interface Notifier { public function send(string $to, string $msg): void; }
final class CompositeNotifier implements Notifier {
/** @param Notifier[] $channels */
public function __construct(private array $channels) {}
public function send(string $to, string $msg): void {
foreach ($this->channels as $c) $c->send($to, $msg);
}
}
$notifier = new CompositeNotifier([new EmailNotifier(), new SmsNotifier()]);
echo 'composed', PHP_EOL;เฮกซากอนเชื่อมโยงกับโฟลเดอร์อย่างไร
โครงสร้าง PHP ที่ใช้งานได้จริงสำหรับบริบทที่มีขอบเขต:
Domain/— เอนทิตี อ็อบเจ็กต์ค่า และบริการโดเมนApplication/Port/In/— อินเทอร์เฟซของพอร์ตขับเคลื่อน (กรณีใช้งาน)Application/Port/Out/— อินเทอร์เฟซของพอร์ตที่ถูกขับเคลื่อน (คลังข้อมูล นาฬิกา)Application/— การนำกรณีใช้งานไปใช้งานจริงInfrastructure/Adapter/In/— ตัวควบคุม CLI และตัวรับข้อความInfrastructure/Adapter/Out/— อะแดปเตอร์ PDO/Doctrine/เอชทีทีพี
จุดประกอบระบบ (การกำหนดค่าคอนเทนเนอร์ DI) จะเชื่อมอะแดปเตอร์ In และ Out เข้ากับพอร์ต
ทดสอบเฮกซากอนทั้งระบบ
นอกเหนือจากการทดสอบหน่วย พอร์ตยังช่วยให้มี การทดสอบการยอมรับ ที่รวดเร็ว โดยขับเคลื่อนแอปพลิเคชันผ่านพอร์ตหลักและตรวจสอบผลผ่านอะแดปเตอร์รองในหน่วยความจำ วิธีนี้ครอบคลุมกรณีใช้งานทั้งหมดโดยไม่ต้องใช้เอชทีทีพีหรือฐานข้อมูล ต่อมาชุดการทดสอบเดิมยังสามารถทำงานกับอะแดปเตอร์จริงในรูปแบบการทดสอบการผสานรวมได้ ทำให้มีกลยุทธ์การทดสอบเป็นชั้น ๆ โดยไม่ต้องเขียนใหม่
<?php
// Acceptance test: real use case, fake driven adapters, no I/O
$users = new InMemoryUsers();
$service = new RegisterUserService($users, new PlainHasher(),
new FixedClock(new DateTimeImmutable('2026-01-01')));
$id = $service->handle('dev@coddykit.com', 'pw');
assert($users->byId($id) !== null);
echo 'acceptance: user persisted via in-memory adapter', PHP_EOL;ตรวจสอบอย่างรวดเร็ว
ข้อความใดเกี่ยวกับพอร์ตและอะแดปเตอร์ถูกต้อง?
สรุป
แนวทางพอร์ตและอะแดปเตอร์แยกแกนหลักไว้เบื้องหลังอินเทอร์เฟซ:
- พอร์ต คืออินเทอร์เฟซที่ใช้ภาษาของโดเมนและมีแกนหลักเป็นเจ้าของ
- พอร์ตขับเคลื่อน ถูกเรียกใช้โดยอะแดปเตอร์ขาเข้า ส่วน พอร์ตที่ถูกขับเคลื่อน ถูกเรียกใช้โดยแกนหลักและได้รับการตอบสนองจากอะแดปเตอร์ขาออก
- อะแดปเตอร์มีหน้าที่ แปล โพรโทคอลและรูปแบบเท่านั้น — ไม่ตัดสินใจทางธุรกิจ
- พอร์ตเดียวกันรองรับอะแดปเตอร์ได้หลายแบบ (ตัวจำลองสำหรับการทดสอบ ตัวประกอบสำหรับการกระจายไปหลายปลายทาง)
- จุดประกอบระบบเชื่อมต่อทุกอย่างเข้าด้วยกัน ส่วนเฮกซากอนไม่ผูกกับเฟรมเวิร์ก
คำถามที่พบบ่อย
บทเรียน “อธิบายพอร์ตและอะแดปเตอร์” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “อธิบายพอร์ตและอะแดปเตอร์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- จากสถาปัตยกรรมแบบเลเยอร์สู่สถาปัตยกรรมสะอาด
- อธิบายพอร์ตและอะแดปเตอร์
- กรณีใช้งานและบริการแอปพลิเคชัน
- การกลับทิศทางการพึ่งพาในการใช้งานจริง