顯示具有 SDD 標籤的文章。 顯示所有文章
顯示具有 SDD 標籤的文章。 顯示所有文章

2026年9月15日 星期二

Laravel 點數系統的並發一致性:從 Redis Lock 到真正的 Concurrency Test

 Laravel 點數系統的並發一致性:從 Redis Lock 到真正的 Concurrency Test

您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/loyalty-api.git

在點數系統裡,「加點」和「扣點」看起來只是簡單的數字加減:

text
balance + 100
balance - 20

但當系統開始面對真實的 API 流量後,問題就不再只是 CRUD。 例如一個會員目前有 100 點,同時收到多個扣點請求:

text
Request A:扣 20
Request B:扣 20
Request C:扣 20
...

如果多個 Request 同時讀到相同的餘額,再各自更新資料,就可能產生資料競爭(Race Condition)。 因此,點數系統真正需要處理的問題是: 如何確保多個請求同時操作同一個會員的點數時,最終餘額與交易紀錄仍然一致? 這也是我在 Laravel 點數系統中實作並發控制時,實際遇到的一個問題。 一、點數系統真正需要保護的是「整個交易流程」 單純使用:

text
$account->balance += $amount;
$account->save();

並不足以保證資料一致性。 因為一次點數操作其實包含多個步驟:

text
取得 PointAccount
        ↓
讀取目前餘額
        ↓
檢查餘額是否足夠
        ↓
更新 balance
        ↓
更新統計欄位
        ↓
建立 PointTransaction

這些操作必須被視為一個完整的交易單位。 因此目前的 PointService 將核心流程放在:

text
DB::transaction(function () {
    // PointAccount 更新
    // PointTransaction 建立
});

這樣當流程中任何一步發生例外時,資料庫可以一起 Rollback。 例如:

text
balance 更新成功
        ↓
PointTransaction 建立失敗
        ↓
Exception
        ↓
Transaction Rollback
        ↓
balance 恢復

這避免只更新了一半資料。 二、Database Transaction 解決的是 Atomicity DB::transaction() 很重要,但它本身並不代表所有並發問題都解決了。 Transaction 主要解決的是: 這一組資料庫操作要嘛全部成功,要嘛全部失敗。 例如:

text
DB::transaction(function () {
    $account->update([
        'balance' => 50,
    ]);

    PointTransaction::create([
        // ...
    ]);
});

如果後面發生例外:

text
throw new RuntimeException('Something went wrong');

前面的資料庫修改也會被 Rollback。 因此測試中也驗證了:

text
原本 balance = 100

更新 balance → 50
建立交易紀錄
發生 Exception

↓

Rollback

balance = 100
transaction count 沒有增加

但這仍然不是完整的並發控制。 三、Row Lock:SELECT ... FOR UPDATE 因此在取得 PointAccount 時,另外使用:

text
$customer->pointAccount()
    ->lockForUpdate()
    ->first();

Laravel 的 lockForUpdate() 對應到資料庫層的 Row-Level Lock。 概念上相當於:

text
SELECT *
FROM point_accounts
WHERE customer_id = ?
FOR UPDATE;

當 Transaction 還沒有結束時,其他交易如果也想取得相同資料列的寫入鎖,就必須等待。 這對點數扣除非常重要。 例如:

text
Account balance = 100

Transaction A
    ↓
SELECT ... FOR UPDATE
    ↓
取得鎖
    ↓
balance = 100
    ↓
扣 80
    ↓
balance = 20

Transaction B
    ↓
等待 A 完成
    ↓
取得最新 balance = 20
    ↓
發現不足 80
    ↓
拒絕交易

因此可以避免兩個交易同時根據舊餘額進行扣點。 四、為什麼還需要 Redis Distributed Lock? 如果只有資料庫 Row Lock,資料庫本身可以控制同一筆資料的交易競爭。 但目前點數服務的設計又增加了一層:

text
$lock = Cache::lock($lockKey, $this->lockTTL);

$lock->block($this->lockWaitSeconds, function () {
    return DB::transaction(function () {
        // ...
    });
});

Lock Key 使用:

text
point_customer:{customer_id}:tenant:{tenant_id}

也就是: 同一個 Tenant 下的同一個 Customer,使用同一把分散式鎖。 這樣如果系統未來由多個 Application Instance 處理 Request:

text
Application A ─┐
Application B ─┼── Redis Lock ── Customer
Application C ─┘

不同 Application Instance 仍然可以透過 Redis 協調。 這就是 Distributed Lock 的價值。 五、Redis Lock、DB Transaction、Row Lock 是不同層次 這三個東西並不是互相取代。 可以把它理解成:

text
Redis Distributed Lock
        ↓
控制「同一個業務對象」的操作競爭

DB Transaction
        ↓
確保一組資料庫操作 Atomic

SELECT ... FOR UPDATE
        ↓
確保資料庫 Row 在 Transaction 中受到並發保護

也就是:

text
Request
   ↓
Redis Lock
   ↓
DB Transaction
   ↓
SELECT ... FOR UPDATE
   ↓
更新 PointAccount
   ↓
建立 PointTransaction
   ↓
Commit
   ↓
Release Redis Lock

這樣才能形成完整的保護鏈。 六、PointAccount 不存在時也是一個並發問題 另外一個容易被忽略的情況是: 如果會員第一次使用點數,PointAccount 還不存在呢? 例如:

text
Request A → 發現沒有 PointAccount
Request B → 發現沒有 PointAccount

如果沒有額外處理,兩個 Request 都可能嘗試建立帳戶。 目前的實作會:

  1. 先查詢 PointAccount
  2. 不存在就建立
  3. 建立後重新取得並鎖定
  4. 如果資料庫 Unique Constraint 發生衝突,重新取得已存在的帳戶 概念上:
text
Customer
           ↓
   PointAccount exists?
       /          \
     Yes           No
      ↓             ↓
 lockForUpdate    Create
                    ↓
             Unique Constraint
                    ↓
             重新取得 Account

這裡有一個重要觀念: Application Lock 與 Database Constraint 應該互相搭配,而不是只依賴其中一個。 Redis Lock 可以降低競爭。 Database Unique Constraint 則是最後一層資料完整性保護。 七、Idempotency 解決的是另一種問題 即使並發控制完整,也還有另一種常見問題:

text
外部系統
    ↓
POST /point-transactions
    ↓
伺服器其實已成功
    ↓
Response 因網路問題沒有收到
    ↓
Client Retry
    ↓
又送一次

這時候問題不是 Race Condition,而是: 同一個業務請求被重送。 所以目前 Point Transaction API 已經加入:

text
Idempotency-Key

例如:

text
Idempotency-Key: unique-key-12345

第一次:

text
Idempotency-Key = ABC
amount = 100

→ 建立交易

第二次:

text
Idempotency-Key = ABC
amount = 100

→ 不重新建立交易
→ 返回第一次交易結果

這和 Redis Lock 解決的是不同問題。 八、Concurrency 與 Idempotency 不應該混在一起 這兩個概念很容易被混淆。 Concurrency 解決: 多個不同 Request 同時操作同一筆資料。 例如:

text
Request A → redeem 20
Request B → redeem 20
Request C → redeem 20

Idempotency 解決: 同一個業務 Request 因 Retry 被送多次。 例如:

text
Request A
Idempotency-Key = ABC

Request A Retry
Idempotency-Key = ABC

因此完整的點數 API 需要同時考慮:

text
Point API
                   │
        ┌──────────┴──────────┐
        ↓                     ↓
   Concurrency            Idempotency
        ↓                     ↓
 Redis Lock             Idempotency-Key
 DB Transaction         避免重複交易
 Row Lock

九、測試過程中發現一個很重要的問題 目前測試已經包含:

text
for ($i = 0; $i < 100; $i++) {
    $this->pointService->earn(...);
}

例如測試:

text
100 次 Earn × 10

最後驗證:

text
balance = 1000
total_earned = 1000
transaction count = 100

這個測試可以驗證 PointService 的連續操作沒有出現計算錯誤。 但是後來會發現: 這其實不是真正的 Concurrent Test。 因為這段程式是同一個 Process 依序執行:

text
Request 1
  ↓
完成

Request 2
  ↓
完成

Request 3
  ↓
完成

...

Request 100

而不是:

text
Request 1 ─┐
Request 2 ─┤
Request 3 ─┤
Request 4 ─┤
...        ├── 同時競爭
Request 100┘

這是測試點數系統時非常值得注意的一件事情。 十、Sequential Test 不等於 Concurrency Test 例如:

text
for ($i = 0; $i < 10; $i++) {
    $this->pointService->redeem(
        $this->customer,
        20
    );
}

最後可能得到:

text
成功 5 次
失敗 5 次
balance = 0

這個結果是正確的。 但它只能證明: PointService 在連續執行下可以正確處理餘額。 它不能完全證明: Redis Lock 與 Database Row Lock 在真正的多 Process / 多 Request 競爭下有效。 真正的 concurrency test 必須讓多個 execution context 同時競爭同一個 Customer。 十一、真正值得測的是這個場景 假設會員有:

text
balance = 100

同時送出:

text
10 個 redeem request
每個扣 20

預期結果:

text
成功:5
失敗:5

最終 balance:
0

total_redeemed:
100

Redeem transaction:
5 筆

這個測試真正要證明的是:

text
10 個 Request
      ↓
同時競爭
      ↓
Redis Distributed Lock
      ↓
DB Transaction
      ↓
SELECT ... FOR UPDATE
      ↓
每次取得正確最新餘額
      ↓
不會出現負數

這才是點數系統真正重要的並發測試。 十二、目前測試已經涵蓋哪些能力? 目前的測試已經涵蓋不少重要場景: 正常操作

text
Earn
Redeem

餘額不足

text
balance = 20
redeem = 30

→ 拒絕
→ balance 不變
→ 不建立 Redeem transaction

Transaction Rollback

text
更新 Account
↓
建立 Transaction
↓
Exception
↓
Rollback

Redis Lock

text
Acquire
↓
Release
↓
再次 Acquire

PointAccount 自動建立

text
Customer
↓
沒有 PointAccount
↓
第一次 Earn
↓
建立 Account

Idempotency

text
第一次 Request
↓
建立交易

相同 Idempotency-Key
↓
返回既有交易
↓
不重複建立

這些都是點數系統應該具備的基本測試。 十三、但測試還需要區分「已驗證」與「尚未驗證」 這是我認為在工程上非常重要的習慣。 不能因為測試名稱叫:

text
PointTransactionConcurrencyTest

就直接認定: 「並發已經測試過了。」 真正應該看測試做了什麼。 目前的:

text
for (...) {
    $this->pointService->earn(...);
}

是 Sequential Execution。 因此比較精確的說法應該是: 目前已完成點數交易的原子性、交易回滾、Lock 基礎行為、餘額驗證與連續操作測試;真正的跨 Process / 多 Request 並發測試仍需要額外驗證。 這比單純宣稱「已完成並發測試」更加準確。 十四、點數系統的核心不是「把 balance 加減」 一個成熟的點數系統,真正需要思考的是:

text
Point System
                      │
       ┌──────────────┼──────────────┐
       ↓              ↓              ↓
   Data Model     Consistency     Reliability
       │              │              │
 PointAccount    Transaction      Idempotency
 PointTransaction  Row Lock       Retry
       │              │
       └──────────────┘
              ↓
       Auditability

其中 PointTransaction 不只是紀錄而已。 每一筆交易都保存:

text
balance_before
amount
balance_after
type
customer_id
point_account_id
tenant_id
description
created_by
reference

因此可以回答: 這次交易前有多少點? 操作了多少點? 操作後剩多少? 這對日後查帳、問題追蹤與外部系統整合都很重要。 十五、最後的架構思考 目前這套設計的核心概念可以整理成:

text
External System
      │
      │ REST API
      ↓
Authentication
      │
Tenant Resolution
      │
      ↓
Point Transaction API
      │
      ├── Idempotency
      │
      ↓
PointService
      │
      ↓
Redis Distributed Lock
      │
      ↓
DB Transaction
      │
      ↓
SELECT ... FOR UPDATE
      │
      ├── PointAccount
      │
      └── PointTransaction

每一層解決不同問題:

text
| 機制                | 解決問題           |
| ----------------- | -------------- |
| JWT               | API 身份驗證       |
| Tenant            | 租戶隔離           |
| Idempotency       | 防止同一請求重複執行     |
| Redis Lock        | 分散式操作競爭        |
| DB Transaction    | 保證資料庫操作 Atomic |
| lockForUpdate()   | 保護資料列並發更新      |
| Unique Constraint | 防止重複建立帳戶       |
| PointTransaction  | 保留點數操作歷史       |

這也是我認為點數系統開發最值得注意的地方: 不要只看「這個 API 能不能成功」,而要看「同一時間、重試、例外、資料庫失敗時,系統會不會留下錯誤狀態」。 結語 點數系統的難度從來不在:

text
$balance += $amount;

真正困難的是:

text
多個 Request 同時進來怎麼辦?

Request 執行一半失敗怎麼辦?

Client Retry 怎麼辦?

兩個 Application Instance 同時操作怎麼辦?

PointAccount 第一次建立時發生競爭怎麼辦?

如何確認交易前後的餘額?

如何追蹤每一次點數變化?

因此,設計點數系統時,我會把重點放在: Atomicity、Concurrency、Idempotency、Tenant Isolation、Auditability。 而測試也必須誠實區分: Sequential Test 能證明什麼,真正 Concurrent Test 又能證明什麼。 只有把這些問題都驗證清楚,點數系統才不只是「功能可以跑」,而是開始具備面對實際 API 流量與外部系統整合的可靠性。

TDD、DDD、SDD:從測試、領域設計到規格驅動開發,建立可維護的軟體工程流程

TDD、DDD、SDD:從測試、領域設計到規格驅動開發,建立可維護的軟體工程流程

1. 開場:為什麼現在又要談 TDD、DDD、SDD?

AI 現在能快速產出大量程式碼。一個 CRUD API、一個 Service、甚至一整套 Feature Test,幾分鐘就能生成。問題是:這些程式碼「能跑」與「正確」是兩回事。

專案越大,修改一個功能就越容易牽動其他地方。工程師常常直接進入 coding,卻沒有先釐清問題邊界。需求、規格、測試、程式碼之間容易脫節——有人腦中的業務規則、有 Jira 上的模糊描述、有 AI 自己猜出來的欄位與 abstraction、有通過但沒有真正驗證業務行為的測試。

結果就是:表面看起來有測試覆蓋、有架構分層,實際上改一個權限規則就要翻半個系統,或者 AI 產出的程式「看起來合理」卻違反既有業務約束。

TDD、DDD、SDD 並不是三個互相競爭的方法,也不是什麼銀彈。它們分別處理不同層次的問題:

  • SDD 回答:「我們到底要做什麼?」
  • DDD 回答:「我們應該如何理解與設計這個問題?」
  • TDD 回答:「我們如何確認實作真的符合預期?」

這不是三種嚴格按照先後順序執行的方法論,而是三個不同關注面的工程實踐。實務上它們會以 feedback loop 的方式互相影響,而不是瀑布式 pipeline。

AI Coding 時代讓這條鏈變得更關鍵,因為 coding 的成本大幅下降,真正昂貴的變成了「釐清問題、建模、驗證」。

2. TDD:先建立「可驗證性」

Test-Driven Development 的核心循環是 Red → Green → Refactor。它真正解決的不是「提高 coverage 數字」,而是建立可執行的行為規格。測試是在描述「系統應該如何回應」,而不是事後補上的檢查清單。

以 Laravel 實務為例。需求:「建立一個 API,只有當使用者具有某個權限時才能刪除資料。」

以下權限相關範例假設專案使用 Spatie Laravel Permission 或相容的 Permission Model。Laravel 原生並沒有 givePermissionTo() 方法。若使用其他權限套件,請替換成對應 API。

先寫測試(Feature / API Test):

PHP
// tests/Feature/EnterpriseDeleteTest.php
public function test_user_with_delete_permission_can_delete_enterprise(): void
{
    $user = User::factory()->create();
    $user->givePermissionTo('enterprise.delete');
    $enterprise = Enterprise::factory()->create();

    $response = $this->actingAs($user)
        ->deleteJson("/api/enterprises/{$enterprise->id}");

    $response->assertNoContent();
    $this->assertDatabaseMissing('enterprises', ['id' => $enterprise->id]);
}

public function test_user_without_delete_permission_cannot_delete_enterprise(): void
{
    $user = User::factory()->create(); // 沒有 permission
    $enterprise = Enterprise::factory()->create();

    $response = $this->actingAs($user)
        ->deleteJson("/api/enterprises/{$enterprise->id}");

    $response->assertForbidden();
    $this->assertDatabaseHas('enterprises', ['id' => $enterprise->id]);
}

先跑測試 → 失敗(Red)。再寫最小實作讓它通過(Green),然後再重構(Refactor)。不要一開始就寫完整 Service、DTO、Exception 階層。

在企業 Laravel 專案中,測試分工大致如下:

  • Unit Test:純邏輯、Value Object、Domain Service(幾乎不碰 DB、HTTP)。
  • Feature / API Test:從 HTTP 入口驗證完整行為與權限,在 Laravel 後端專案中特別適合作為可執行規格的一部分。
  • Integration Test:涉及多個外部系統或複雜 transaction 時使用。

實際上 Unit / Feature / Integration 的邊界會依專案而異。重點不是名稱,而是「這組測試是否能有效保護我們真正在意的行為」。不要把所有東西都塞進 Unit Test,也不要為了 coverage 而測 trivial getter。

TDD 的價值在於:之後任何人(包括 AI)改程式時,都有一組可執行的行為約束。測試失敗就代表行為被改變了。

3. DDD:先搞清楚「我們到底在解決什麼問題」

Domain-Driven Design 最常被誤解的地方,是把它等同於「把 Model 改成 Entity + 建一堆 Repository + Service」。那不是 DDD,那是 Pattern 堆砌。

DDD 的核心是:讓程式模型反映業務領域與業務規則。Ubiquitous Language 要在團隊與程式碼中一致。Bounded Context 幫你劃分「哪些東西屬於同一個問題空間」。

拿企業官網後台的「投資人/公司/分類/多語系內容管理」當例子。

真正的 Domain Rule 可能是:

  • 一個公司在某個語系下只能有一個「主要投資人」描述。
  • 刪除分類時,如果底下還有已發布內容,必須先處理關聯或禁止刪除。
  • 多語系內容的「發布狀態」是跨語系協調的,而不是單純每個翻譯獨立。

這些規則值得放進 Domain Model(Entity、Value Object、Aggregate 的行為方法,或 Domain Service)。反過來說,單純的 CRUD 欄位編輯、列表篩選、圖片上傳,通常不需要完整 Aggregate 或 Repository 抽象。

Laravel 實務建議:

  • Eloquent Model 可以直接承載部分 Domain 行為(如果規則不複雜)。
  • 真正複雜的規則再抽到 Domain Service 或 Application Service。
  • Repository 只有在你需要真正隔離 persistence 細節、或有多種儲存策略時才值得。
  • 不是每個 Model 都要變成 Entity,也不是每個操作都要 Application Service。

「不是所有 CRUD 都值得 DDD 化。」這句話要反覆提醒自己。DDD 的成本在於認知負擔與程式碼結構複雜度。只有當業務複雜度高到「直接寫在 Controller / Model 會很快失控」時,才值得投入。

4. SDD:規格驅動開發

本文所說的 SDD,是指 Specification-Driven Development / Spec-Driven Development

需要先說明:SDD 並不像 TDD 或 DDD 那樣有高度標準化、單一且被廣泛接受的業界定義。不同團隊可能把 SDD 理解成不同流程與產物。因此本文明確定義為:

先建立明確、可驗證、可供人與 AI 共同理解的規格,再進入實作。

它通常包含:

  • Functional Requirement
  • Acceptance Criteria
  • Business Rules
  • API Contract / Data Contract
  • Permission Rules
  • Edge Cases
  • Error Handling
  • 對應的 Test Cases

SDD 的價值不是「多寫文件」,而是降低這條鏈上的資訊損失:

人腦中的需求 → AI / 工程師理解 → 程式碼

沒有明確規格時,AI 最容易開始「猜」:自己加欄位、自己加 Service、自己決定錯誤碼、自己假設權限模型。工程師也容易直接開始寫,寫到一半才發現需求沒講清楚。

規格要可驗證。最好能直接對應到測試案例或 Acceptance Criteria。模糊的「要支援刪除」不如「只有具備 enterprise.delete 權限的使用者可以刪除;若有關聯資料則禁止刪除並回傳特定錯誤」。

5. TDD、DDD、SDD 三者到底有什麼差別?

方法核心問題關注層次主要產物解決的風險
TDD做出來的東西對不對?ImplementationTestsRegression / Incorrect Behavior
DDD我們應該如何建模?DomainDomain ModelBusiness Complexity
SDD我們到底要做什麼?RequirementSpecificationAmbiguous Requirements

簡化來說:

  • SDD 關注「我們到底要做什麼」
  • DDD 關注「我們應該如何理解與建模這個問題」
  • TDD 關注「我們如何確認實作真的符合預期」

但這不是絕對的線性規則。實務上它們會互相回饋,任何一環發現問題,都可能回到其他環節。

6. 三者如何組合?(Feedback Loop,而非瀑布)

實務流程大致如下:

text
需求

SDD(釐清 Business Rules / Acceptance Criteria / Edge Cases)

DDD(建立 Domain Model,決定哪些規則放哪裡)

TDD(寫可驗證的行為測試)

Implementation(最小必要實作)

Refactor

Integration / Acceptance Test

實際專案幾乎從來不是完美線性:

  • TDD 寫到一半發現需求有洞 → 回到 SDD
  • 實作時發現 Domain Model 不合理 → 回到 DDD
  • DDD 過程中發現業務規則根本沒講清楚 → 回到 SDD
  • 測試發現規格與實際業務矛盾 → 回到 Requirement

真正的流程是 feedback loop,而不是瀑布。文件再漂亮,如果無法驅動測試與實作,價值有限。

7. AI Coding 時代,為什麼 SDD + DDD + TDD 特別重要?

這是全文重點。

AI 很擅長:

  • 產生 CRUD
  • 補程式碼
  • 依照既有 Pattern 重構
  • 寫測試骨架
  • 根據既有程式修改

但 AI 不應該被當成「知道需求的 Senior Engineer」。它最容易犯的錯誤包括:

  • 猜需求、自己增加欄位或行為
  • 引入不必要的 abstraction(多一層 Service、DTO、Repository)
  • 忽略既有架構與邊界
  • 只修表面問題
  • 寫出可以執行但不符合業務規則的程式

AI 並不是不會寫程式,而是可以很有效率地實作「錯誤的需求」。

因此:

  • SDD 約束 AI 要做什麼:明確的 Acceptance Criteria、API Contract、Permission Rule、Edge Cases。AI 不該自由發揮。
  • DDD 約束 AI 應該如何理解業務:Domain Model、Ubiquitous Language、Bounded Context。AI 提出的建議要對齊既有領域語言。
  • TDD 約束 AI 不能隨便改變既有行為:測試是行為的護欄。AI 改完程式後必須通過既有測試,新增行為也要有對應測試。

AI + TDD:讓 AI 產生或補齊測試,但測試案例必須反映真正需求,而不是 AI 自己想像的 happy path。
AI + DDD:AI 可以提出 Domain Model 建議,但最終建模決策由工程師負責,避免 AI 為了「看起來專業」而過度抽象。
AI + SDD:最關鍵。沒有規格時,AI 產出的程式碼品質上限很低。有明確規格時,AI 變成強大的實作加速器。

AI 降低了 coding cost,卻沒有降低 requirement clarification、domain understanding 和 verification 的成本。這才是現代後端工程真正昂貴的地方。

8. 一個完整 Laravel 實戰案例

需求:「後台使用者可以刪除某個企業資料,但只有具備對應 Delete Permission 的使用者才能刪除;如果資料具有關聯資料,必須依照業務規則處理。」

SDD

  • Functional Requirement:提供 DELETE /api/enterprises/{id}
  • Permission Rule:使用者必須具備 enterprise.delete 權限。
  • Business Rule:若該企業下仍有已發布的關聯內容(例如投資人報告),禁止刪除,並回傳 422 與明確業務錯誤訊息。
  • Acceptance Criteria:
    • 有權限且無阻礙關聯 → 204 No Content,資料被刪除。
    • 無權限 → 403 Forbidden。
    • 資料不存在 → 404 Not Found。
    • 有阻礙關聯 → 422 Unprocessable Entity,附帶明確訊息。
  • Edge Cases:關聯內容已 soft-deleted、權限中途被撤銷、並發刪除請求等。

DDD

  • Entity:Enterprise(可承載部分業務行為)。
  • Domain Rule:刪除前是否允許,屬於業務規則。實務上可放在 Model 的行為方法中。
  • 實作上的取捨:
PHP
// app/Models/Enterprise.php
public function canBeDeleted(): bool
{
    // 這是 pragmatic 的做法:把「查詢關聯狀態」與「業務判斷」放在一起。
    // 若未來規則變得更複雜(跨 Bounded Context、需要多種資料來源),
    // 再考慮抽出 Domain Service 或把條件透過參數注入,以減少對 Eloquent 的直接依賴。
    return !$this->publishedReports()->exists();
}
  • Application Layer:協調權限檢查 + Domain 行為 + Persistence。
  • Permission Boundary:權限檢查放在 Policy,不要塞進 Domain Entity。
  • 不需要的東西:不需要為了這個功能再建完整 Repository 介面、Command Bus、一堆 DTO,除非既有架構已經這樣做。

重點再次強調:
「能用現有 Laravel Pattern(Policy + Model 行為方法)清楚表達規則,就不要為了追求純正 DDD 而額外引入抽象。」

TDD

PHP
public function test_authorized_user_can_delete_enterprise_without_blocking_relations(): void { /* ... */ }
public function test_unauthorized_user_gets_forbidden(): void { /* ... */ }
public function test_non_existent_enterprise_returns_404(): void { /* ... */ }
public function test_enterprise_with_blocking_relations_cannot_be_deleted(): void { /* ... */ }

Implementation(保持 KISS)

使用 Laravel 常見實務:Policy + Controller + Eloquent 行為方法。

PHP
// app/Policies/EnterprisePolicy.php
public function delete(User $user, Enterprise $enterprise): bool
{
    return $user->can('enterprise.delete');
}

// Controller
public function destroy(Enterprise $enterprise)
{
    $this->authorize('delete', $enterprise);

    if (!$enterprise->canBeDeleted()) {
        return response()->json([
            'message' => 'Cannot delete enterprise with published related reports.',
        ], 422);
    }

    $enterprise->delete();

    return response()->noContent();
}

對應的錯誤回應契約建議保持一致:

  • 403:無權限
  • 404:資源不存在
  • 422:違反業務規則(有阻礙關聯)

為什麼這樣設計?因為這個功能的複雜度還沒到需要完整 Aggregate Root + Domain Service + Application Service 的程度。能用 Policy + Model 行為解決,就不要為了 Pattern 而 Pattern。如果之後規則變得非常複雜(多種刪除策略、事件溯源、跨 Bounded Context),再演化。

9. 什麼情況不需要 TDD / DDD / SDD?

  • 非常簡單的 CRUD、內部一次性 script、原型驗證:不需要完整 DDD,也不一定值得建立完整測試架構。
  • 純 UI 調整、沒有複雜業務規則的前端頁面:完整 SDD 可能過重。
  • 探索性程式或 throw-away code:過度規格與測試是浪費。

但對以下情況,應該提高規格、建模與測試的要求:

  • 金流、權限、訂單、多租戶
  • 複雜工作流程、高風險資料操作
  • 複雜業務規則
  • AI Agent 會執行的重要操作

原則是:風險與複雜度越高,SDD + DDD + TDD 的投入越值得。

10. 常見誤區

  1. TDD = 測試覆蓋率越高越好
    修正:關注行為覆蓋與回歸防護,而不是數字。Trivial 測試沒有價值。
  2. DDD = Repository Pattern
    修正:Repository 只是可能的工具。核心是 Domain Model 與業務規則表達。
  3. DDD = 每個 Model 都要 Entity
    修正:只有真正有身份與生命週期的概念才需要。很多東西就是 Data。
  4. Service 越多代表架構越好
    修正:Service 過多常常是缺少 Domain 行為或過度分層的徵兆。
  5. SDD = 寫更多文件
    修正:目標是可驗證、可驅動實作與測試的規格,而不是文件堆積。
  6. AI 寫測試就等於有 TDD
    修正:AI 產生的測試常常只測 happy path 或自己想像的行為。測試必須反映真正需求。
  7. 有規格就代表需求一定正確
    修正:規格本身也需要驗證與迭代。錯誤的規格會被高效實作出來。
  8. 測試通過就代表商業需求正確
    修正:測試只驗證「符合既有規格」。規格錯了,測試也會一起錯。
  9. 所有專案都應該使用完整 DDD
    修正:多數 CRUD 不值得。業務複雜度才是觸發條件。
  10. AI 產生的程式碼只要能跑就可以
    修正:能跑只是最低標準。還要符合業務規則、既有架構、可維護性與測試約束。

11. 最後提出一套適合現代後端工程師的工作方式

簡潔可落地的流程:

  1. Understand — 先理解問題與未知之處。AI 幫忙整理資訊、列出問題,但不能自行猜需求。
  2. Specify — 產出可驗證的 Acceptance Criteria、Business Rules、Edge Cases、API Contract。AI 協助轉寫與補全,工程師負責確認。
  3. Model — 決定 Domain 邊界與關鍵規則放哪裡。AI 可提出建議,工程師做最終決策。
  4. Test — 寫可執行的行為測試。AI 可產生測試骨架,但案例必須對齊規格。
  5. Implement — 最小必要修改。AI 寫實作,但範圍受規格與測試約束。
  6. Verify — 跑測試、看 diff、檢查 regression。AI 協助執行與初步分析。
  7. Refactor — 在測試保護下整理。AI 可協助,但不能自行擴大修改範圍。

AI 在每個階段都是加速器與建議者,而不是決策者。工程師對需求正確性、領域模型、行為驗證負最終責任。

12. 結論

真正成熟的工程流程,不是讓工程師寫更多程式碼,而是在寫程式碼之前,把問題、規則與驗證方式變得足夠清楚。

AI 讓 Coding 的成本下降,因此真正昂貴的反而變成 Requirement、Domain Understanding、Verification。TDD、DDD、SDD 不是讓你變得更「正統」,而是幫你在 AI 加速的時代,仍然能把「正確」與「可維護」守住。


TDD / DDD / SDD + AI 一頁式心智模型

text
┌─────────────────────────────────────────────────────────────┐
│                    Problem Space                             │
│  需求模糊 / 業務複雜 / 行為易回歸 / AI 容易亂猜               │
└──────────────────────────┬──────────────────────────────────┘

           ┌───────────────┼───────────────┐
           ▼               ▼               ▼
     ┌─────────┐     ┌─────────┐     ┌─────────┐
     │   SDD   │     │   DDD   │     │   TDD   │
     │ 要做什麼 │     │ 如何理解 │     │ 如何驗證 │
     │ Spec    │     │ Model   │     │ Tests   │
     └────┬────┘     └────┬────┘     └────┬────┘
          │               │               │
          └───────────────┼───────────────┘

                 Feedback Loop
          (規格 ↔ 模型 ↔ 測試 ↔ 實作)


              ┌───────────────────────┐
              │   AI as Accelerator   │
              │  - 受規格約束          │
              │  - 受模型語言約束      │
              │  - 受測試護欄約束      │
              │  - 工程師做最終決策    │
              └───────────────────────┘


              可維護、可驗證、可演化的系統

用這套思路,而不是追求完美 Pattern 數量,會更接近實務中真正能長期維護的後端系統。

2026年9月6日 星期日

2026 年,怎麼真正用 AI 加速開發工作

 

現在是 2026 年 9 月。AI 寫 code 已經不是新鮮事,幾乎每個開發者都會用。
但真正拉開差距的,不是「會不會用 AI」,而是怎麼把 AI 嵌進工作流,讓它穩定加速,而不是製造更多返工

這篇文章整理我實際在 Laravel 專案中使用 AI(以 Trae 為例)的經驗,核心是 SDD(Spec-Driven Development)+ 可控實作


一、先建立正確心態

AI 是加速器,不是代理人。

  • 你負責:判斷、取捨、架構決策、風險把關、最終品質
  • AI 負責:重複勞動、初稿產出、搜尋整理、小範圍實作、格式與檢查

把它當成「能力很強、但需要明確指令的 Junior」,而不是可以全權外包的 Senior。


二、核心方法:Spec-Driven Development(SDD)

SDD 的精神很簡單:

先把「要做什麼」寫清楚(Spec / Plan),確認無誤後,再讓 AI 去實作。

而不是直接喊「幫我做登入」,然後看 AI 自由發揮。

推薦工作流

text
需求澄清
  ↓
產出 Spec / Plan(可用 AI 輔助)
  ↓
人工確認 Spec(這步不能省)
  ↓
AI 依 Spec 最小實作(用 Skill 約束)
  ↓
Review + 測試驗證

沒有經過確認的 Spec,AI 實作越快,後面修正成本通常越高。
「先對齊,再動手」是 SDD 的核心。


三、Rules 與 Skills 的差別

項目Rules(規則)Skills(技能)
本質行為約束、常駐規範特定任務的操作手冊
載入時機幾乎一直生效符合條件才觸發
適合內容禁止事項、安全底線、命名慣例完整工作流(依 Spec 實作、Code Review 等)
Token 成本每次對話都可能帶上按需載入,較省
  • Rules = 公司制度(永遠要遵守)
  • Skills = 標準作業程序 SOP(做特定工作才拿出來)

建議:

  • Rules 只放真正不能破的底線
  • Skills 放「依 Spec 實作」這類完整流程

四、配合 SDD 的實作 Skill 範例

當 Spec 已確認後,用以下精神約束 AI:

text
You are a Senior Laravel Developer. Implement only from confirmed Spec / Plan.

Rules:
1. Confirm Spec first. If Spec ≠ actual code, stop and report — do not expand scope.
2. Read relevant existing code before modifying.
3. Make the smallest correct change. Prefer existing Model / Service / Policy / Helper / Schema.
4. KISS + Minimal Change. No unnecessary refactoring or new abstraction.
5. Do not change DB schema, API contracts, or permission architecture unless explicitly in the Spec.

After implementation:
- Syntax check
- Relevant tests
- DB / Permission verification if applicable
- Regression check

Output:
- Changed Files
- Changes
- Verification
- Test Result
- Remaining Risk

這個 Skill 的目的不是讓 AI 更聰明,而是讓它嚴格依照已確認的 Spec 做事,避免 scope creep。


五、真正能加速的習慣

  1. 先產出並確認 Spec,再進入實作
  2. 強制最小變更(不要重構、不要加新 abstraction)
  3. 要求固定輸出格式,方便快速 Review
  4. 把有效的 Spec 模板與 Skill 沉澱下來
  5. 保留人類檢查點(權限、金錢、資料安全相關一定要人工過)

六、常見踩坑

  • 沒有 Spec 就直接叫 AI 寫 → 結果不可控
  • Spec 寫得模糊,卻期待 AI 一次做對
  • 讓 AI 一次做太大功能 → scope 失控
  • 從不 Review 就合併 → 技術債快速累積
  • 規則寫太長太雜 → token 貴,重點被稀釋

七、總結

2026 年用 AI 加速開發,核心可以收斂成兩句話:

  1. 用 SDD:先把 Spec 寫清楚並確認,再實作。
  2. 用 AI 加速「已經確定要做的事」,而不是用 AI 決定「該做什麼事」。

工具已經夠強,真正拉開差距的是工作流是否可控:
先對齊 Spec → 用 Skills 約束實作 → 用 Rules 守住底線 → 保留人工檢查點。

AI 不會取代會用 AI 的開發者,但會快速淘汰那些「只會喊 AI、卻無法控管結果」的人。


需要我再幫你調成更口語、或加上實際 Spec 範例的版本嗎?

2026年9月5日 星期六

Laravel Boost:從 Code Generator 進化成真正理解專案的 AI Agent

Laravel Boost 與 Laravel MCP 技術分享
讓 AI 真正進入你的 Laravel 專案,而不是只會猜

以前使用 AI 寫 Laravel,我最常遇到的問題不是 AI 不會寫 PHP,而是:

AI 根本不知道我的專案。

它不知道 Laravel 版本、不知道目前用了哪些套件、不知道資料庫 Schema、不知道既有的架構規則,也不知道某個問題其實已經有現成的 Service、Policy 或 Helper。

於是很容易出現這種情況:

AI:建議新增一個 Service。
實際上:專案裡已經有同功能的 Service。

或者:

AI:這是 Filament 3 的寫法。
實際上:專案使用的是 Filament 4。

甚至:

AI:新增一個 Permission。
實際上:專案早就有完整的 Gate + Policy + Shield + 自訂權限 Resolver。

這也是我開始關注 Laravel Boost 的原因。

Laravel Boost 是 Laravel 官方團隊提供的 AI 開發工具,核心目的就是讓 AI Coding Agent 取得 Laravel 專案真正需要的 Context、Tools、Guidelines 與 Skills。官方目前將它定位為:

Laravel-focused MCP server for augmenting your AI powered local development experience.


一、Laravel Boost 到底是什麼?

如果要用一句話解釋:

Laravel Boost 就是 Laravel 與 AI Coding Agent 之間的一座橋。

傳統 AI Coding 大概是:

text
開發者
   ↓
AI
   ↓
看你提供的程式碼
   ↓
猜測專案架構
   ↓
產生程式碼

加入 Laravel Boost 之後:

text
AI Coding Agent
                       │
                 Laravel Boost
                       │
        ┌──────────────┼──────────────┐
        ↓              ↓              ↓
     MCP Tools      Guidelines      Skills
        │              │              │
        └──────────────┼──────────────┘
                       ↓
                Laravel Application
                       │
          ┌────────────┼────────────┐
          ↓            ↓            ↓
        Code         MySQL        Logs

AI 不再只能依賴模型記憶,它可以透過 Boost:

  • 了解 Laravel / PHP 版本
  • 了解安裝了哪些 Laravel 生態套件
  • 查看 Eloquent Models
  • 查看 Database Schema
  • 執行 Database Query
  • 查看 Laravel Log
  • 查看 Browser Logs
  • 取得最後一次錯誤
  • 搜尋 Laravel / 套件官方文件

目前官方列出的 Boost MCP 工具包含 Application Info、Database Schema、Database Query、Browser Logs、Last Error、Read Log Entries、Search Docs 等。

這個差異非常大。


二、Laravel Boost 和 Laravel MCP 不一樣

這也是我覺得最容易搞混的地方。

Laravel 現在有兩個相關套件:

  • laravel/mcp
  • laravel/boost

但用途完全不同。

Laravel MCP
主要是讓你建立自己的 MCP Server

text
你的 Laravel Application
        ↓
自訂 MCP Server
        ↓
AI / 外部系統

你可以自己設計 Tool、Resource、Prompt,把業務能力暴露給任何 MCP Client。

Laravel Boost
則是讓 AI Coding Agent 更了解你的 Laravel 專案

text
TRAE / Cursor / Claude Code
            ↓
       Laravel Boost
            ↓
     你的 Laravel 專案

所以如果你的目的只是:

「我要讓 TRAE / Cursor 更懂我的 Laravel 專案,幫我寫得更準。」

那麼你真正需要研究的是 Laravel Boost

而 Laravel Boost 本身就是建立在 Laravel MCP 之上的——它提供的那些開發工具,全部都是 MCP Tools。


三、安裝 Laravel Boost

安裝非常簡單:

Bash
composer require laravel/boost --dev
php artisan boost:install

Boost 會偵測你的開發環境與 AI Agent,並產生對應的 MCP、Guidelines、Skills 等資源。

安裝之後,你會開始看到類似:

  • .mcp.json
  • .ai/
  • 不同 Agent 所需要的設定檔

這些東西的目的不是取代你的 Laravel 程式碼,而是:

把你的 Laravel 專案變成 AI 更容易理解的開發環境。

核心啟動指令:

Bash
php artisan boost:mcp

如果 IDE 需要手動註冊 MCP,官方提供的設定方式是:

JSON
{
    "mcpServers": {
        "laravel-boost": {
            "command": "php",
            "args": ["artisan", "boost:mcp"]
        }
    }
}

四、Boost 最重要的 MCP Tools

1. Application Info

AI 先了解你的環境。

假設專案是 PHP 8.2 + Laravel 11 + Filament 4 + Livewire + Spatie Media Library。
Boost 可以直接提供這些資訊,AI 不用再靠猜。

2. Database Schema

這個對企業 Laravel 專案非常重要。

很多 Bug 根本不是 PHP 語法問題,而是 Model ≠ Database。
AI 可以直接確認 table、column、type、relationship,再開始改 Code。

3. Database Query

AI 可以直接驗證資料。

以前要手動開 Tinker 查詢再貼給 AI,現在 Agent 可以自己查,工作流程變成真正的 Debugging。

4. Logs(Last Error、Read Log Entries、Browser Logs)

這是我認為 Boost 最實用的功能之一。

從「請貼錯誤訊息」變成 AI 自己去看 Log,Debug 效率明顯提升。

5. Search Docs

避免 AI 使用錯版本 API。

Boost 的 Documentation API 有超過 17,000 筆 Laravel 生態系資訊,並根據專案實際安裝的套件版本提供對應文件(支援 Laravel 10~13、Filament 2~5、Livewire 1~4 等)。


五、Boost 不只是 MCP

如果只把 Laravel Boost 理解成「一個 Laravel MCP Server」,其實低估它了。

Boost 現在還提供:

  • Guidelines:AI 啟動時就應該知道的通用規則(Laravel、Livewire、Pest、Tailwind 等)
  • Skills:需要時才載入的專業知識(例如 Livewire Development、Pest Testing)
  • Project Rules:真正適合企業專案的關鍵

Guidelines 是廣泛、基礎的規範;Skills 則是針對特定工作領域、需要時才啟用。

Project Rules 才是教 AI「你的公司系統怎麼寫」的地方。

例如:

  • 所有 Permission 使用既有命名規則
  • 不要自行新增 Repository
  • 修改權限之前必須檢查 Policy / Gate / Shield / Permission Resolver
  • 優先使用既有 Service
  • 不要重複建立 Helper
  • 修改資料庫之前必須先確認 migration

Boost 使用 .ai/rules/ 與 index.md,透過 glob 對應檔案範圍,讓 Agent 只讀取相關規則,而不是一次塞全部 Context。

甚至可以直接跟 Agent 說「記住:所有金額都使用 integer cents 儲存」,Boost 會透過 record-rule 工具把規則寫入 Project Rules。


六、對我來說,Boost 最大的價值

很多人看到 AI Coding 工具,第一個想法是「可以幫我寫 Code,所以會變快」。

但實際上,我認為更大的提升是:

減少 AI 與開發者之間反覆傳遞 Context 的時間。

沒有 Boost:

text
你:這是 Model。
AI:請給 migration。
你:這是 migration。
AI:請給 Service。
你:這是 Service。
AI:請給錯誤 Log。
你:這是 Log。

有 Boost:

text
你:找出這個問題並修正。
AI → Application Info → Database Schema → Code → Logs → Documentation → 修改

真正被加速的其實是 Context Gathering


七、我自己的 AI 開發流程

如果是 Laravel 專案,我會要求 AI 遵循:

text
Think → Inspect → Understand → Plan → Modify → Verify

而不是:

text
Prompt → Generate Code

具體流程:

  1. 先理解需求
  2. 查看現有實作
  3. 查看 Database Schema
  4. 查看相關 Permission / Policy
  5. 查官方文件
  6. 提出最小修改方案
  7. 修改
  8. 查看 Log / Error
  9. 測試
  10. 檢查 Git Diff

Laravel Boost 剛好可以把其中很多 Context Gathering 的工作交給 Agent。


八、安全性提醒

Boost 能讓 AI 查 Database、執行 Query、讀 Logs,這不代表應該讓 AI 在 Production 隨便操作。

我會建議:

  • Development → 完整 Boost
  • Production → 嚴格限制

尤其是 UPDATE、DELETE、敏感資料、Payment、Authentication 都應該非常謹慎。

AI 工具越強,權限控管反而越重要。

另外,Boost 不是取代你的架構。我會在 Project Rules 明確寫:

  • 不要過度設計
  • 優先使用既有架構
  • 修改前先搜尋現有實作
  • 如果已有相同功能,禁止重新建立
  • 只修改完成需求所必要的檔案
  • 不要進行與需求無關的重構

九、結語:AI Coding 的下一步不是更大的模型,而是更好的 Context

使用 AI 一段時間後,我越來越覺得:

AI Coding 的瓶頸不一定是模型能力,而是 Context。

模型再強,如果它不知道你的 Laravel 版本、資料庫、套件、架構、權限、Log、Coding Convention,它還是可能寫出一段「看起來正確,但不適合你的專案」的 Code。

Laravel Boost 解決的,正是這個問題。

它把 Laravel + MCP + Documentation + Guidelines + Skills + Project Rules 組合起來,最後形成:

text
AI Agent
                       │
                  Laravel Boost
                       │
       ┌───────────────┼────────────────┐
       ↓               ↓                ↓
      MCP         Guidelines          Skills
       │               │                │
       └───────────────┼────────────────┘
                       ↓
                Laravel Application
                       │
        ┌──────────────┼───────────────┐
        ↓              ↓               ↓
      Code           Database         Logs

對我來說,Laravel Boost 最有價值的地方不是「讓 AI 幫我多寫幾行 Code」,而是:

讓 AI 從一個只會產生程式碼的工具,變成真正可以在 Laravel 專案裡工作的開發 Agent。

而這也讓 AI 開發的思維開始從:

text
Prompt → Code

慢慢變成:

text
Context → Understand → Plan → Code → Verify

這才是我認為 Laravel Boost 真正值得導入 Laravel 專案的原因。

2026年9月1日 星期二

AI-Assisted Software Engineering 從 SDD、AI Workflow 到 Architecture Governance

 

AI-Assisted Software Engineering

從 SDD、AI Workflow 到 Architecture Governance

AI 負責速度,工程師負責方向;Spec 負責約束,Architecture 負責品質。


一、背景:AI Coding 解決了什麼,又帶來了什麼問題?

生成式 AI 出現之後,軟體開發效率有了非常明顯的提升。

現在透過 AI,可以快速完成:

  • Controller

  • Service

  • Model

  • Migration

  • API

  • Vue Component

  • Filament Resource

  • Validation

  • Test

  • Refactoring

  • Debugging

以前可能需要幾個小時完成的工作,現在幾十分鐘甚至幾分鐘就能完成。

但當 AI 開始大量參與實際專案開發後,我發現一個很重要的問題:

AI 最大的風險不是寫不出 Code,而是寫太多「看起來合理」的 Code。

例如:

  • 已經存在的 Service,AI 又建立一個新的

  • 原本應該放在 Service 的邏輯被塞進 Controller

  • 為了修一個問題順便重構整個模組

  • 新增一套與既有 API 不一致的 Response Format

  • 修改功能時忽略 Permission

  • 為了符合 AI 自己熟悉的 Pattern,改變原本專案架構

  • 解決 A 問題後,引入 B、C、D 問題

  • 每次修改都合理,但長期造成 Architecture Fragmentation

所以我開始重新思考:

如果 AI 已經可以高速產生 Code,那工程師真正需要控制的是什麼?

我的答案是:

Specification、Workflow、Architecture。

因此,我逐漸建立了一套自己的 AI 輔助軟體工程流程:

                    AI-Assisted
                 Software Engineering
                         │
        ┌────────────────┼────────────────┐
        ↓                ↓                ↓
       SDD          AI Workflow     Architecture
        │                │                │
     定義問題          約束 AI           驗證結果
        │                │                │
        └────────────────┼────────────────┘
                         ↓
                  Controlled AI
                   Development

二、核心理念:我不把 AI 當 Senior Developer

我的 AI 開發方式有一個非常明確的定位:

我把 AI 當成一個速度非常快的 Junior Engineer。

AI 可以負責:

  • 搜尋

  • 分析

  • 撰寫 Code

  • Debug

  • Refactoring

  • Test

  • 文件整理

但不應該自行決定:

  • 核心 Architecture

  • Business Rule

  • Database Model

  • 大型 Refactoring

  • 技術選型

  • 需求邊界

原因很簡單。

AI 很擅長回答:

「這段 Code 怎麼寫?」

但工程師真正需要回答的是:

「這段 Code 應該放在哪裡?」

以及:

「我們真的需要這段 Code 嗎?」

這兩個問題,本質上是不同的。


三、SDD:Spec Before Code

我將第一個核心概念定義為:

Spec Before Code

也就是在讓 AI 修改 Code 之前,先把問題定義清楚。

傳統 AI Coding 通常是:

需求
 ↓
Prompt
 ↓
AI 寫 Code
 ↓
發現問題
 ↓
再叫 AI 修改
 ↓
繼續累積修改

而我的流程是:

需求
 ↓
Specification
 ↓
Existing Architecture Analysis
 ↓
Call Chain
 ↓
Data Flow
 ↓
Constraints
 ↓
Implementation Plan
 ↓
Implementation
 ↓
Verification

四、我的 SDD Template

我會讓 Specification 至少包含以下區塊。

1. Goal

這次需求真正要解決什麼問題?

不是只描述「我要新增一個功能」,而是要說明:

為什麼需要這個功能?


2. Scope

明確定義這次修改的範圍。

例如:

  • Backend

  • API

  • Database

  • Frontend

  • Permission

  • Cache

  • Queue

同時標示哪些模組受到影響。


3. Non-goals

這是我認為非常重要、而且很容易被忽略的一部分。

除了告訴 AI:

「這次要做什麼。」

也必須告訴 AI:

「這次不要做什麼。」

例如:

## Non-goals

本次需求不包含:

- 不重構現有 Service 架構
- 不修改既有 API Response Format
- 不新增 Repository Layer
- 不修改既有 Permission Naming
- 不處理無關的 Technical Debt
- 不進行 Frontend Component 重構
- 不修改其他既有模組

這可以有效降低 AI 的過度設計。

因為 AI 很容易看到一個問題後順手說:

「既然這裡可以改善,我一起重構。」

但在實際專案中:

「可以改善」不代表「現在應該改善」。


五、Existing Architecture:先理解,再修改

AI 最常見的問題之一,是直接假設架構。

例如:

「這個專案應該使用 Repository Pattern。」

然後 AI 開始新增:

Controller
 ↓
Service
 ↓
Repository
 ↓
Model

但實際專案可能根本沒有 Repository Layer。

這時候 AI 雖然寫出來的 Code 沒有錯,但它已經開始改變 Architecture。

所以我要求 AI:

先找現有架構,不要自己假設架構。

例如 Laravel 專案:

Route
 ↓
Controller
 ↓
Service
 ↓
Model
 ↓
Database

如果既有架構就是這樣,就優先延續既有模式。


六、Call Chain:不要猜,直接追

在大型專案中,我非常重視 Call Chain。

例如一個 API:

POST /api/quest/select
        ↓
QuestController
        ↓
checkEligibility()
        ↓
QuestService
        ↓
Business Rule
        ↓
Database

如果直接看到 Controller 就開始修改,很容易判斷錯誤。

因此我會要求 AI:

1. 找 Route
2. 找 Controller
3. 找實際呼叫的方法
4. 追 Service
5. 找 Model
6. 找 Database
7. 找相關 Permission
8. 找 Frontend 呼叫位置
9. 找 Test

先回答:

「這個功能實際上是怎麼運作的?」

再開始 Coding。


七、Data Flow:確認資料怎麼流動

除了 Call Chain,我也會確認 Data Flow。

例如:

Frontend
   ↓
Request
   ↓
Validation
   ↓
Controller
   ↓
Service
   ↓
Model
   ↓
Database
   ↓
Resource / Transformer
   ↓
API Response
   ↓
Frontend

這可以避免只修改 Backend,卻忘記:

  • API Response

  • Frontend Payload

  • Validation

  • Transformer

  • Database

  • Cache

等相關位置。

因此 SDD 不只是「需求文件」,它其實也是:

AI 修改系統前的 Context Boundary。


八、Constraints:告訴 AI 哪些事情不能做

AI 如果沒有約束,很容易產生過度設計。

所以我會明確設定:

Constraints

- 優先使用既有 Service
- 不建立重複功能
- 不進行無關重構
- 不修改既有 API Contract
- 不改變既有 Business Rule
- 保持既有 Permission Naming
- 修改範圍保持最小
- 如果現有架構不足,先提出方案,不直接重構

這裡的核心原則是:

AI 可以提出方案,但不能默認擴大 Scope。


九、AI Workflow:把開發流程標準化

建立 SDD 後,我再將 AI 的工作分成幾個階段。

Phase 1:Understand

AI 只負責理解。

Search
 ↓
Read
 ↓
Trace
 ↓
Understand

這個階段不修改 Code。


Phase 2:Analyze

分析:

Current Architecture
        ↓
Problem
        ↓
Root Cause
        ↓
Impact
        ↓
Possible Solutions

重點是找 Root Cause,而不是只處理表面症狀。


Phase 3:Propose

AI 提出方案。

例如:

Option A

修改既有 Service。

Option B

建立新的 Service。

Option C

重構整個模組。

然後由工程師判斷哪個方案適合目前 Architecture。


Phase 4:Implement

方案確認後,才讓 AI 實作。

此時 AI 就可以發揮最大的效率:

  • Generate Code

  • Generate Test

  • Modify Files

  • Refactor

  • Debug


Phase 5:Verify

完成後再次檢查:

Functional
 ↓
Regression
 ↓
API
 ↓
Database
 ↓
Permission
 ↓
Performance

最後才算完成。


十、最小修改原則

AI 開發中,我非常重視:

Minimal Change

例如今天只是修正一個 Business Rule。

理想情況:

Controller
 ↓
Existing Service
 ↓
修正 Business Rule

而不是:

Controller
 ↓
重構 Service
 ↓
新增 Repository
 ↓
修改 Model
 ↓
修改 API
 ↓
重寫 Frontend

即使後者「架構看起來更漂亮」,也不代表它是正確的工程決策。

因為每增加一個修改點,就增加一個 Regression Risk。

所以我的原則是:

先解決問題,再考慮重構。


十一、Architecture Check:AI 寫完不代表完成

我認為 AI Coding 最大的缺口之一,就是:

AI 可以確認 Code 能跑,但不一定能確認 Architecture 是否健康。

因此我會另外做 Architecture Check。


十二、Level 1:Daily Architecture Checklist

每次 AI Coding 完成後,可以快速檢查:

#Check問題
1Scope是否只修改需求必要範圍?
2Duplication是否新增了既有功能的第二套實作?
3ResponsibilityController / Service / Model 職責是否清楚?
4Call Chain是否確認實際呼叫鏈後才修改?
5Data FlowRequest → API → DB → Response 是否一致?
6Permission新功能是否同步檢查權限?
7Performance是否引入 N+1、重複 Query 或大量資料載入?
8Regression是否確認既有功能沒有受到影響?

這個 Checklist 的目的不是取代 Code Review。

而是建立一個最低限度的:

Architecture Quality Gate


十三、Level 2:Module Architecture Review

當修改的是重要模組,就不只做快速 Check。

會完整追:

API
 ↓
Controller
 ↓
Service
 ↓
Model
 ↓
Database

再確認:

Frontend
Permission
Cache
Queue
Test

例如一個新的 API,不只是確認 Controller 是否正確,而是確認:

Request
 ↓
Validation
 ↓
Business Logic
 ↓
Database
 ↓
Transformer
 ↓
Response
 ↓
Frontend

整條鏈是否一致。


十四、Level 3:Full Architecture Audit

大型專案或累積大量 AI 修改後,可以進行完整架構健檢。

我會將它分成:

                Architecture Audit
                       │
       ┌───────────────┼───────────────┐
       ↓               ↓               ↓
      API              DB              Auth
       │               │               │
       ↓               ↓               ↓
   Frontend       Performance       Permission
       │               │               │
       └───────────────┼───────────────┘
                       ↓
                 Technical Debt

最後不是單純列出「有問題」。

而是進一步分類:

Critical

可能影響資料正確性、安全性或核心功能。

High

可能造成重大維護成本或效能問題。

Medium

可以改善,但短期不一定需要處理。

Low

Code Quality 或未來可優化項目。

這可以避免 Architecture Review 最後變成:

「看到什麼就全部重構。」


十五、實際案例一:AI 建立重複 Service

Problem

需求是新增一個翻譯匯入功能。

AI 分析後提出:

建立新的 TranslationService

程式本身沒有明顯錯誤。

甚至測試也可以通過。

但進行 Architecture Check 後發現:

Existing TranslationService
        +
New TranslationService
        ↓
相同責任開始分散

問題不在 Code 能不能執行。

問題在:

系統開始出現兩個相同責任的入口。


修正

重新追蹤既有架構後,發現原本的 Translation Service 已經負責相關流程。

因此最後改成:

需求
 ↓
尋找 Existing Service
 ↓
確認既有責任
 ↓
擴充 Existing Service
 ↓
維持單一責任入口

這個案例讓我更加確定:

AI 產生的方案不一定是錯的,但不一定適合既有 Architecture。


十六、實際案例二:修問題前先追 Call Chain

另一類常見問題是:

表面看到的錯誤位置,不一定是真正的問題位置。

例如 API:

POST /api/xxx
 ↓
Controller
 ↓
checkEligibility()
 ↓
Service
 ↓
Business Rule

如果直接看到 Controller 就修改,很容易把 Business Logic 放到錯誤的位置。

因此我會先追完整 Call Chain。

最後可能發現:

Controller
    ↓
只負責 Request / Response

Service
    ↓
真正負責 Business Rule

所以正確的修改方式不是:

「Controller 有問題 → 修改 Controller」

而是:

「找到真正的責任邊界 → 在正確的 Layer 修改。」

這也是我在 AI Coding 中非常重視的觀念。


十七、這套流程帶來的改變

建立這套方法後,我認為 AI Coding 的工作模式產生了一個很大的變化。

以前:

工程師
 ↓
寫 Code

現在:

工程師
 ↓
定義問題
 ↓
定義 Architecture
 ↓
制定 Constraints
 ↓
指揮 AI
 ↓
Review
 ↓
Verification

工程師花在「手寫 Code」的比例下降。

但花在:

  • Requirement Analysis

  • Architecture

  • Debugging

  • Code Review

  • System Design

  • Quality Control

的比例反而提高。

我認為這並不是工程師價值下降。

反而是工程師的工作開始往更高層次移動。


十八、我對 AI Engineering 的理解

我認為 AI 時代的工程能力,可以分成三層。

第一層:AI Coding

知道怎麼讓 AI 寫 Code。

Prompt
 ↓
Code

這是最基本的能力。


第二層:AI-Assisted Development

知道怎麼把 AI 放進日常開發流程。

Requirement
 ↓
AI Analysis
 ↓
Implementation
 ↓
Test
 ↓
Review

效率會比單純 Coding 高很多。


第三層:AI Engineering

建立一套可以控制 AI 的工程方法。

Specification
       ↓
AI Workflow
       ↓
Implementation
       ↓
Verification
       ↓
Architecture Governance

這時候 AI 已經不是單純的 Coding Tool。

而是:

Software Engineering 的一部分。


十九、我的三層方法論

最後,我把整套方法濃縮成三個核心。

① SDD — Define

回答:

我們到底要做什麼?

透過:

  • Goal

  • Scope

  • Non-goals

  • Existing Architecture

  • Call Chain

  • Data Flow

  • Constraints

  • Acceptance Criteria

定義問題。


② AI Workflow — Control

回答:

AI 應該怎麼做?

透過:

Understand
 ↓
Analyze
 ↓
Propose
 ↓
Implement
 ↓
Verify

控制 AI 的行為。


③ Architecture Check — Govern

回答:

AI 做完之後,系統還健康嗎?

透過:

Daily Check
 ↓
Module Review
 ↓
Full Architecture Audit

持續控制 Architecture Quality。


二十、結語

我認為 AI Coding 真正帶來的改變,不是:

「工程師不用寫 Code 了。」

而是:

「工程師需要把更多注意力放在 Code 以外的工程問題。」

AI 可以讓 Code 產生速度提高十倍。

但如果沒有 Specification、Workflow 與 Architecture Governance,

那麼 Technical Debt 也可能累積得更快。

所以我現在對 AI 輔助開發的核心理念是:

Think Before Code.

Spec Before AI.

Minimal Change Before Refactoring.

Architecture Before Scale.

AI 負責速度。

SDD 負責方向。

Workflow 負責控制。

Architecture Check 負責品質。

而工程師真正的價值,是把這四件事情串起來,讓 AI 不只是「會寫 Code」,而是能夠在既有軟體工程規範下,持續產生正確、可維護、可演進的系統

Laravel 點數系統的並發一致性:從 Redis Lock 到真正的 Concurrency Test

  Laravel 點數系統的並發一致性:從 Redis Lock 到真正的 Concurrency Test 您可以透過以下  GitHub  連結檢閱本專案的原始碼: https://github.com/BpsEason/loyalty-api.git 在點數系統裡,「加點...