138 lines
4.6 KiB
Markdown
138 lines
4.6 KiB
Markdown
---
|
||
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;
|
||
});
|
||
```
|