Files
star-erp/.agents/skills/cross-module-communication/SKILL.md

138 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: 跨模組調用與通訊規範 (Cross-Module Communication)
description: 規範 Laravel Modular Monolith 架構下不同業務模組中如何彼此調用資料與邏輯包含禁止項目、Interface 實作、與 Service 綁定規則。
---
# 跨模組調用與通訊規範 (Cross-Module Communication)
為了確保專案的「模組化單體架構 (Modular Monolith)」的獨立性與可維護性,當遇到**需要跨越不同業務模組存取資料或調用功能**的情境時,請嚴格遵守以下規範。
## 🚫 絕對禁止的行為 (Strict Prohibitions)
* **禁止跨模組 Eloquent 關聯(例外除外)**
* **禁止跨模組直接引入 (use) Model**
* **禁止跨模組直接實例化 (new) Service**
---
## 🌟 允許的全域例外 (Global Exceptions)
雖然我們嚴格禁止跨模組直接相依,但為了開發效率與框架機制的完整性,**`Core` 模組下的特定基礎設施模型 (Infrastructure Models) 被視為全域例外**。
其他業務模組 **可以** 透過 Eloquent (`belongsTo` / `hasMany`) 直接關聯以下 Model
1. **`App\Modules\Core\Models\User`**
2. **`App\Modules\Core\Models\Role`**
3. **`App\Modules\Core\Models\Tenant`**
> **⚠️ 注意**:這項例外是單向的。`Core` 模組內的業務邏輯(如 `DashboardController`**絕對不能**反過來直接 `use` 外部業務模組的 Model仍必須透過外部模組的 Service Interface 來索取資料。
---
## ✅ 正確的跨模組調用流程:合約與依賴反轉
所有的跨模組資料交換與功能調用,必須透過**介面化通訊 (Contracts)** 進行。
### Step 1: 在被調用的模組定義合約 (Interface)
如果 `Inventory` 模組需要提供功能給外部使用,請在 `app/Modules/Inventory/Contracts/` 建立 Interface 檔案。
```php
namespace App\Modules\Inventory\Contracts;
use Illuminate\Support\Collection;
interface InventoryServiceInterface
{
public function getActiveWarehouses(): Collection;
}
```
### Step 2: 實作介面並在自己模組的 ServiceProvider 註冊
`Inventory` 模組自己的 Service 來實作上述介面。
```php
namespace App\Modules\Inventory\Services;
use App\Modules\Inventory\Contracts\InventoryServiceInterface;
use App\Modules\Inventory\Models\Warehouse;
use Illuminate\Support\Collection;
class InventoryService implements InventoryServiceInterface
{
public function getActiveWarehouses(): Collection
{
return Warehouse::where('is_active', true)
->select(['id', 'name', 'code'])
->get();
}
}
```
然後進入 `app/Modules/Inventory/InventoryServiceProvider.php` 完成綁定:
```php
namespace App\Modules\Inventory;
use Illuminate\Support\ServiceProvider;
use App\Modules\Inventory\Contracts\InventoryServiceInterface;
use App\Modules\Inventory\Services\InventoryService;
class InventoryServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(InventoryServiceInterface::class, InventoryService::class);
}
}
```
### Step 3: 調用方透過依賴注入 (DI) 使用服務
`Procurement` 模組需要取得倉庫資料時,必須透過**建構子注入**或**方法注入**取得 `InventoryServiceInterface`
```php
namespace App\Modules\Procurement\Controllers;
use App\Http\Controllers\Controller;
use App\Modules\Inventory\Contracts\InventoryServiceInterface;
use Inertia\Inertia;
class PurchaseOrderController extends Controller
{
public function __construct(
protected InventoryServiceInterface $inventoryService
) {}
public function create()
{
$warehouses = $this->inventoryService->getActiveWarehouses();
return Inertia::render('Procurement/PurchaseOrder/Create', [
'warehouses' => $warehouses
]);
}
}
```
---
## ⚠️ 跨模組資料回傳的注意事項 (Data Hydration)
* **回傳純粹資料**:建議在 Service 中用 `with()` 載入好關聯,或者直接轉為原生的 Array 或有具體結構的 DTO避免依賴 Lazy Loading。
* **手動組合 (Manual Hydration)**:若某個頁面需要合併兩個模組的資料,必須在 Controller 層級呼叫兩個不同的 Service Interface 後,手動合併。
### 範例:手動合併資料
```php
// 正確示範:在各自模組取資料,並手動組裝
$orders = $this->orderService->getOrders();
$userIds = $orders->pluck('user_id')->unique()->toArray();
$users = $this->coreUserService->getUsersByIds($userIds)->keyBy('id');
$mergedData = $orders->map(function ($order) use ($users) {
// 將使用者資料手動附加上去
$order->user_name = $users->get($order->user_id)->name ?? 'Unknown';
return $order;
});
```