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

จากสถาปัตยกรรมแบบเลเยอร์สู่สถาปัตยกรรมสะอาด

ทำความเข้าใจว่าเหตุใดการพึ่งพาควรชี้เข้าด้านใน

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

เหตุใดจึงใช้สถาปัตยกรรมสะอาด

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

กฎการพึ่งพา

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

  • เอนทิตี: กฎระดับองค์กร
  • กรณีใช้งาน: กฎระดับแอปพลิเคชัน
  • อะแดปเตอร์: ตัวควบคุม ตัวนำเสนอ ช่องทางเชื่อมต่อ
  • กรอบงานและตัวขับ: ฐานข้อมูล เอชทีทีพี เว็บ

ตัวอย่างสถาปัตยกรรมแบบชั้นที่ผูกโยงกัน

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

<?php
class OrderService
{
    public function place(Request $request)
    {
        $user = User::find($request->user_id); // Eloquent
        $total = 0;
        foreach ($request->items as $i) {
            $total += Product::find($i['id'])->price * $i['qty'];
        }
        if ($user->is_vip) {
            $total *= 0.9; // business rule trapped in infra code
        }
        Order::create(['user_id' => $user->id, 'total' => $total]);
        return response()->json(['total' => $total]);
    }
}

เอนทิตี: โดเมนที่ไม่ขึ้นกับกรอบงาน

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

<?php
final class Money
{
    public function __construct(public readonly int $cents) {
        if ($cents < 0) throw new InvalidArgumentException('negative money');
    }
    public function multiply(float $factor): self {
        return new self((int) round($this->cents * $factor));
    }
}

final class Order
{
    /** @param array<int,int> $lineCents */
    public function __construct(private array $lineCents, private bool $vip) {}
    public function total(): Money {
        $sum = array_sum($this->lineCents);
        $money = new Money($sum);
        return $this->vip ? $money->multiply(0.9) : $money;
    }
}

echo (new Order([1000, 2000], true))->total()->cents, PHP_EOL; // 2700

กรณีใช้งานเป็นเจ้าของลำดับงาน

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

<?php
interface OrderRepository {
    public function save(Order $order): void;
}

final class PlaceOrder
{
    public function __construct(private OrderRepository $orders) {}

    public function execute(array $lineCents, bool $vip): int {
        $order = new Order($lineCents, $vip);
        $this->orders->save($order);
        return $order->total()->cents;
    }
}

ขอบเขตคือส่วนต่อประสาน

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

ทิศทางการพึ่งพาของซอร์สโค้ด: EloquentOrderRepository → OrderRepository (ส่วนต่อประสาน) ไม่ใช่ทิศทางกลับกัน

<?php
// Lives in infrastructure layer, points INWARD to the domain interface
final class EloquentOrderRepository implements OrderRepository
{
    public function save(Order $order): void {
        OrderModel::create(['total' => $order->total()->cents]);
    }
}

การทดสอบโดยไม่ใช้โครงสร้างพื้นฐาน

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

<?php
final class InMemoryOrders implements OrderRepository {
    public array $saved = [];
    public function save(Order $o): void { $this->saved[] = $o; }
}

$repo = new InMemoryOrders();
$useCase = new PlaceOrder($repo);
$total = $useCase->execute([1000, 2000], true);

assert($total === 2700);
assert(count($repo->saved) === 1);
echo "PASS total=$total saved=" . count($repo->saved) . PHP_EOL;

ตัวควบคุมกลายเป็นอะแดปเตอร์แบบบาง

ตอนนี้ตัวควบคุมเป็นอะแดปเตอร์: แปลงเอชทีทีพีเป็นการเรียกกรณีใช้งาน และแปลงผลลัพธ์กลับเป็นเอชทีทีพี ตัวควบคุมไม่มีกฎธุรกิจ หากเปลี่ยนจาก REST เป็น CLI หรือผู้ปฏิบัติงานคิว กรณีใช้งานก็ยังไม่ต้องแก้ไข

<?php
final class OrderController
{
    public function __construct(private PlaceOrder $placeOrder) {}

    public function store(Request $request): JsonResponse {
        $total = $this->placeOrder->execute(
            lineCents: $request->input('lineCents'),
            vip: (bool) $request->input('vip'),
        );
        return new JsonResponse(['total' => $total], 201);
    }
}

สถาปัตยกรรมที่บอกโดเมนอย่างชัดเจน

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

  • src/Ordering/Domain/ — เอนทิตี ออบเจ็กต์ค่า
  • src/Ordering/Application/ — กรณีใช้งาน ส่วนต่อประสานพอร์ต
  • src/Ordering/Infrastructure/ — คลังข้อมูลอีโลเควนต์ ตัวควบคุมเอชทีทีพี

แต่ละบริบทที่มีขอบเขตชัดเจนเป็นโฟลเดอร์ระดับบนสุด ส่วนกรอบงานอยู่ตรงขอบ

การบังคับใช้กฎการพึ่งพา

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

# deptrac.yaml
deptrac:
  layers:
    - name: Domain
      collectors: [{ type: directory, value: src/.*/Domain/.* }]
    - name: Application
      collectors: [{ type: directory, value: src/.*/Application/.* }]
    - name: Infrastructure
      collectors: [{ type: directory, value: src/.*/Infrastructure/.* }]
  ruleset:
    Domain: []                       # Domain may depend on nothing
    Application: [Domain]
    Infrastructure: [Application, Domain]

การข้ามขอบเขตด้วยวัตถุถ่ายโอนข้อมูล

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

<?php
final class OrderSummary // boundary DTO, no behavior, no domain types
{
    public function __construct(
        public readonly string $orderId,
        public readonly int $totalCents,
    ) {}
}

final class PlaceOrderV2 {
    public function __construct(private OrderRepository $orders) {}
    public function execute(array $lineCents, bool $vip): OrderSummary {
        $order = new Order($lineCents, $vip);
        $this->orders->save($order);
        return new OrderSummary('ord_1', $order->total()->cents);
    }
}

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

ภายใต้กฎการพึ่งพา อนุญาตให้พึ่งพาไปในทิศทางใด

สรุปทบทวน

คุณได้เปลี่ยนจากโครงสร้างแบบชั้นที่ผูกโยงกันมาเป็นสถาปัตยกรรมสะอาด:

  • กฎการพึ่งพา: การพึ่งพาของซอร์สโค้ดชี้เข้าด้านในเท่านั้น
  • เอนทิตีเก็บกฎระดับองค์กรไว้ใน PHP ที่ไม่ขึ้นกับกรอบงาน
  • กรณีใช้งานประสานงานผ่านส่วนต่อประสานพอร์ต และส่งคืนวัตถุถ่ายโอนข้อมูล ไม่ใช่เอชทีทีพี
  • ตัวควบคุมและคลังข้อมูลของตัวแมปวัตถุสัมพันธ์เป็นอะแดปเตอร์ด้านนอกที่พึ่งพาเข้าด้านใน (DIP)
  • โครงสร้างควรสื่อถึงโดเมนอย่างชัดเจน และเครื่องมืออย่างดีพแทรกจะบังคับใช้กฎนี้ใน CI

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

บทเรียน “จากสถาปัตยกรรมแบบเลเยอร์สู่สถาปัตยกรรมสะอาด” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “จากสถาปัตยกรรมแบบเลเยอร์สู่สถาปัตยกรรมสะอาด” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ 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. อธิบายพอร์ตและอะแดปเตอร์
  3. กรณีใช้งานและบริการแอปพลิเคชัน
  4. การกลับทิศทางการพึ่งพาในการใช้งานจริง
← กลับไปที่ PHP Academy