กรณีใช้งานและบริการแอปพลิเคชัน
แสดงการทำงานทางธุรกิจเป็นกรณีใช้งานที่ไม่ผูกกับเฟรมเวิร์ก
กรณีใช้งานและบริการแอปพลิเคชัน เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน
กรณีใช้งานคืออะไร
กรณีใช้งาน (หรือเรียกว่าบริการแอปพลิเคชันหรืออินเทอร์แอกเตอร์) บันทึกการดำเนินการเฉพาะของแอปพลิเคชันไว้เพียงหนึ่งรายการ เช่น ลงทะเบียนผู้ใช้ สร้างคำสั่งซื้อ หรือ ยกเลิกการสมัครสมาชิก กรณีใช้งานจะประสานงานเอนทิตีและพอร์ตเพื่อทำตามเจตนาเดียว สิ่งสำคัญคือกรณีใช้งาน ไม่ผูกกับเฟรมเวิร์ก: ไม่มี Request ไม่มี Response และไม่มีตัวช่วยส่วนกลาง มีเพียง PHP ธรรมดาที่เรียกใช้จากที่ใดก็ได้
อ็อบเจ็กต์คำสั่งและผลลัพธ์
กรณีใช้งานรับ อ็อบเจ็กต์ถ่ายโอนข้อมูลคำสั่ง ที่ไม่เปลี่ยนแปลงเป็นอินพุต และส่งคืน อ็อบเจ็กต์ถ่ายโอนข้อมูลผลลัพธ์ อ็อบเจ็กต์ถ่ายโอนข้อมูลเป็นเพียงตัวเก็บข้อมูล — ไม่มีพฤติกรรมและไม่มีตรรกะตรวจสอบนอกเหนือจากโครงสร้าง คุณสมบัติ readonly (PHP 8.1+) ทำให้แก้ไขโดยไม่ได้รับอนุญาตไม่ได้
<?php
final class RegisterUserCommand
{
public function __construct(
public readonly string $email,
public readonly string $plainPassword,
) {}
}
final class RegisterUserResult
{
public function __construct(public readonly string $userId) {}
}โครงสร้างภายในบริการแอปพลิเคชัน
บริการจะแปลงคำสั่งเป็นการดำเนินการของโดเมน โดยทำหน้าที่ประสานงานระดับแอปพลิเคชัน เช่น ตรวจสอบความไม่ซ้ำ การจัดเก็บข้อมูล และการส่งคืนตัวระบุ ขณะเดียวกันจะมอบหมาย กฎ ให้เอนทิตีเป็นผู้จัดการ
<?php
final class RegisterUser
{
public function __construct(
private Users $users,
private PasswordHasher $hasher,
) {}
public function __invoke(RegisterUserCommand $c): RegisterUserResult {
if ($this->users->existsByEmail($c->email)) {
throw new EmailAlreadyRegistered($c->email);
}
$user = User::register(
UserId::generate(),
new Email($c->email),
$this->hasher->hash($c->plainPassword),
);
$this->users->add($user);
return new RegisterUserResult((string) $user->id());
}
}เก็บตรรกะไว้ในเอนทิตี
โปรดระวัง แบบจำลองโดเมนไร้เนื้อหา: เอนทิตีถูกลดเหลือเพียงเมธอดอ่านค่าและกำหนดค่า ขณะที่ตรรกะทั้งหมดไปอยู่ในบริการ สิ่งที่ไม่เปลี่ยนแปลงต้องอยู่บนเอนทิตี กรณีใช้งานควรอ่านเหมือนสคริปต์สั้น ๆ ที่บอกเจตนา ไม่ใช่กำแพงกฎทางธุรกิจ
<?php
final class User
{
private function __construct(
private UserId $id,
private Email $email,
private string $passwordHash,
private bool $active = false,
) {}
public static function register(UserId $id, Email $e, string $hash): self {
return new self($id, $e, $hash); // invariants enforced here
}
public function activate(): void {
if ($this->active) throw new AlreadyActive();
$this->active = true;
}
public function id(): UserId { return $this->id; }
}ขอบเขตธุรกรรม
กรณีใช้งานคือ ขอบเขตธุรกรรมตามธรรมชาติ: กรณีใช้งานหนึ่งรายการเท่ากับหน่วยงานหนึ่งหน่วยที่สอดคล้องกัน แทนที่จะกระจาย beginTransaction() ไปทั่วบริการ ให้ครอบบริการเหล่านั้นด้วยเดคอเรเตอร์แบบทำธุรกรรม เพื่อให้แกนหลักไม่ขึ้นกับการจัดเก็บข้อมูล
<?php
interface TransactionManager {
public function transactional(callable $work): mixed;
}
final class TransactionalRegisterUser
{
public function __construct(
private RegisterUser $inner,
private TransactionManager $tx,
) {}
public function __invoke(RegisterUserCommand $c): RegisterUserResult {
return $this->tx->transactional(fn() => ($this->inner)($c));
}
}การตรวจสอบความถูกต้องควรอยู่ที่ใด
แบ่งการตรวจสอบความถูกต้องออกเป็นสองระดับ:
- การตรวจสอบอินพุต (รูปแบบ ฟิลด์ที่จำเป็น) เกิดขึ้นในอะแดปเตอร์ขับเคลื่อนหรือตัวตรวจสอบเฉพาะทาง ก่อนที่กรณีใช้งานจะเริ่มทำงาน
- การตรวจสอบโดเมน (สิ่งที่ไม่เปลี่ยนแปลง กฎทางธุรกิจ) อยู่ในอ็อบเจ็กต์ค่าและเอนทิตี และจะโยนข้อยกเว้นโดเมน
กรณีใช้งานจะถือว่าอินพุตมีรูปแบบถูกต้อง และทำหน้าที่บังคับใช้ความหมาย
<?php
final class Email
{
public function __construct(public readonly string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email: $value");
}
}
}
try { new Email('nope'); } catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; }
echo (new Email('a@b.com'))->value, PHP_EOL;ส่งคืนผลลัพธ์โดยไม่ใช้เอชทีทีพี
มีสองรูปแบบสำหรับส่งคืนข้อมูลโดยยังไม่ผูกกับเฟรมเวิร์ก:
- ส่งคืนอ็อบเจ็กต์ถ่ายโอนข้อมูลผลลัพธ์ (เรียบง่ายและทำงานแบบพร้อมกัน)
- พอร์ตผลลัพธ์ / ผู้นำเสนอ — กรณีใช้งานส่งผลลัพธ์ไปยังขอบเขตผลลัพธ์ที่ฉีดเข้ามา ทำให้อะแดปเตอร์เป็นผู้กำหนดรูปแบบ (เจสัน, HTML, CLI) วิธีนี้ทำให้แม้แต่รูปแบบการตอบกลับก็อยู่นอกแกนหลัก
<?php
interface RegisterUserOutput {
public function present(RegisterUserResult $r): void;
}
final class RegisterUserWithPresenter {
public function __construct(private Users $users, private PasswordHasher $h) {}
public function __invoke(RegisterUserCommand $c, RegisterUserOutput $out): void {
$user = User::register(UserId::generate(), new Email($c->email), $this->h->hash($c->plainPassword));
$this->users->add($user);
$out->present(new RegisterUserResult((string) $user->id()));
}
}เหตุการณ์โดเมนจากกรณีใช้งาน
กรณีใช้งานมักบันทึก เหตุการณ์โดเมน ที่เอนทิตีสร้างขึ้น แล้วส่งเหตุการณ์เหล่านั้นออกไปหลังธุรกรรมยืนยันเสร็จ วิธีนี้แยกผลข้างเคียงต่าง ๆ (ส่งอีเมลต้อนรับ อัปเดตแบบจำลองการอ่าน) ออกจากขั้นตอนการทำงานหลัก
<?php
trait RecordsEvents {
private array $events = [];
protected function record(object $e): void { $this->events[] = $e; }
public function releaseEvents(): array {
$e = $this->events; $this->events = []; return $e;
}
}
final class UserRegistered {
public function __construct(public readonly string $userId) {}
}
// Use case calls $user->releaseEvents() and hands them to a dispatcher
echo 'event recorded pattern', PHP_EOL;หนึ่งคลาสต่อหนึ่งกรณีใช้งาน
ควรใช้ คลาสการทำงานเดียว (มีเมธอดสาธารณะหนึ่งเมธอด ซึ่งมักเป็น __invoke) แทนบริการขนาดใหญ่ที่มีสิบเมธอด ข้อดีคือ:
- มีความรับผิดชอบเดียวและตั้งชื่อได้ชัดเจน (
CancelSubscriptionแทนSubscriptionService::cancel) - ตัวสร้างฉีดเฉพาะสิ่งที่การดำเนินการ นี้ต้องใช้
- ครอบด้วยเดคอเรเตอร์ได้ง่าย (ธุรกรรม การบันทึกเหตุการณ์ การอนุญาต)
การเชื่อมต่อที่จุดประกอบระบบ
กรณีใช้งานจะไม่สร้างการพึ่งพาขึ้นมาเอง แต่ จุดประกอบระบบเป็นผู้ทำหน้าที่นี้ ตัวอย่างต่อไปนี้คือการเชื่อมต่อด้วยตนเอง ซึ่งสามารถวางไว้ในคำจำกัดความของคอนเทนเนอร์ DI ได้
<?php
$pdo = new PDO('sqlite::memory:');
$users = new PdoUsers($pdo);
$hasher = new BcryptHasher();
$register = new RegisterUser($users, $hasher);
// Decorate with a transaction boundary
$register = new TransactionalRegisterUser($register, new PdoTransactionManager($pdo));
// Driving adapter calls it
$result = $register(new RegisterUserCommand('dev@coddykit.com', 's3cret!'));
echo $result->userId, PHP_EOL;ข้อกังวลร่วมข้ามส่วนด้วยเดคอเรเตอร์
การบันทึกเหตุการณ์ เมตริก และการอนุญาตเป็น ข้อกังวลที่ตัดข้ามหลายส่วน — ควรนำสิ่งเหล่านี้ออกจากเนื้อหาของกรณีใช้งาน ให้ครอบบริการด้วยเดคอเรเตอร์ที่ใช้อินเทอร์เฟซเดียวกัน เพื่อให้แกนหลักมุ่งเน้นที่ขั้นตอนการทำงาน ขณะที่ข้อกังวลของโครงสร้างพื้นฐานประกอบอยู่รอบนอก
<?php
interface RegisterUserHandler {
public function __invoke(RegisterUserCommand $c): RegisterUserResult;
}
final class LoggingRegisterUser implements RegisterUserHandler {
public function __construct(
private RegisterUserHandler $inner,
private LoggerInterface $log,
) {}
public function __invoke(RegisterUserCommand $c): RegisterUserResult {
$this->log->info('register.start', ['email' => $c->email]);
$r = ($this->inner)($c);
$this->log->info('register.ok', ['id' => $r->userId]);
return $r;
}
}ตรวจสอบอย่างรวดเร็ว
กฎ "อีเมลต้องไม่ซ้ำและมีรูปแบบถูกต้อง" ควรอยู่ที่ใด?
สรุป
กรณีใช้งานที่ไม่ผูกกับเฟรมเวิร์กทำให้ได้ชั้นแอปพลิเคชันที่สะอาด:
- มี คลาสการทำงานเดียว ต่อการดำเนินการหนึ่งรายการ รับอ็อบเจ็กต์ถ่ายโอนข้อมูลคำสั่ง และส่งคืนอ็อบเจ็กต์ถ่ายโอนข้อมูลผลลัพธ์ (หรือส่งไปยังพอร์ตผลลัพธ์)
- เอนทิตีและอ็อบเจ็กต์ค่าเป็นเจ้าของ สิ่งที่ไม่เปลี่ยนแปลง ส่วนบริการทำหน้าที่ประสานงานเท่านั้น — หลีกเลี่ยงแบบจำลองไร้เนื้อหา
- กรณีใช้งานเป็น ขอบเขตธุรกรรม และควรครอบด้วยเดคอเรเตอร์แทนการใส่
beginTransactionไว้ภายใน - แยกการตรวจสอบอินพุต (อะแดปเตอร์/อ็อบเจ็กต์ค่า) ออกจากการตรวจสอบโดเมน (เอนทิตี)
- เหตุการณ์โดเมนช่วยแยกผลข้างเคียงออกจากกัน ส่วนจุดประกอบระบบทำหน้าที่เชื่อมการพึ่งพา
คำถามที่พบบ่อย
บทเรียน “กรณีใช้งานและบริการแอปพลิเคชัน” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “กรณีใช้งานและบริการแอปพลิเคชัน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “กรณีใช้งานและบริการแอปพลิเคชัน”
แสดงการทำงานทางธุรกิจเป็นกรณีใช้งานที่ไม่ผูกกับเฟรมเวิร์ก คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “กรณีใช้งานและบริการแอปพลิเคชัน” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม
ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- จากสถาปัตยกรรมแบบเลเยอร์สู่สถาปัตยกรรมสะอาด
- อธิบายพอร์ตและอะแดปเตอร์
- กรณีใช้งานและบริการแอปพลิเคชัน
- การกลับทิศทางการพึ่งพาในการใช้งานจริง