3D Plugin / 多設備單元邊界分析
1. 文件定位
本文件是第三階段 3D「平台化擴充」的第一份邊界分析,用來釐清未來外部 Plugin DLL、多設備單元、資源鎖、命令佇列、設定版本與回滾之間的責任分工。
本文件只做分析與確認準備,不代表已同意新增 Plugin Loader、載入外部 DLL、修改 IDeviceAdapter public contract、導入外部 plugin framework、建立背景服務、修改設定檔格式、接真實硬體或把案場客製邏輯寫入 Core。
2. 3D 目標
3D 的目標不是立即讓系統動態載入外部 DLL,而是先把平台化擴充的邊界固定好,避免後續把設備單元、硬體 driver、資源排程、任務佇列、設定版本與案場客製流程混在同一層。
| 目標 | 說明 |
| 固定多設備單元模型 | 釐清 ControlUnitId、StationId、DeviceId、Adapter instance 與實體設備之間的關係。 |
| 固定 Plugin 邊界 | 決定未來外部 DLL 應提供什麼資訊、如何失敗隔離、如何映射到既有 Adapter contract。 |
| 固定 Resource Lock 邊界 | 避免多任務、多設備單元或多命令同時搶同一實體資源。 |
| 固定 Command Queue 邊界 | 讓命令排程與任務生命週期可追蹤,不在 Controller、Host 或 Adapter 內各自排隊。 |
| 固定設定版本與回滾方向 | 未來 JSON / plugin / 設備設定變更需可審核、追蹤與回復。 |
| 建立停止線 | 未確認前不載入 DLL、不改 public contract、不導入套件、不改啟動方式。 |
3. 既有前置能力
| 前置能力 | 狀態 | 對 3D 的意義 |
IDeviceAdapter | 已存在 | 未來 plugin adapter 應先映射回既有 Adapter contract,不讓 Core 認識 DLL 載入細節。 |
DeviceId | 已存在 | 目前已可由設定、WorkflowNode、DeviceCommand 與 Log 串接設備識別。 |
DeviceAdapterDispatcher | 已存在 | 已具備依 DeviceId 分派 adapter 的基礎,但尚未處理多設備單元與資源鎖。 |
| Adapter Connect rollback | 已完成 | 多設備啟動中途失敗時,已成功連線的 adapter 會補償斷線,可作為未來 ControlUnit 啟動失敗策略基礎。 |
TaskEngine / WorkflowEngine | 已存在 | Resource Lock / Command Queue 不應讓 Workflow node 自行散落管理。 |
HS.DeviceControl.Application | 3C 已完成第一版 | 未來可承接任務控制、設備狀態、健康檢查與平台化協調,但 3D 本文件不修改程式。 |
| Log / Trace / TaskStore | 第二階段與 3A 已完成基礎 | 未來 lock、queue、plugin 載入與設備命令需可追蹤,不只回傳 bool。 |
4. 名詞邊界
| 名詞 | 建議語意 | 不應混淆的內容 |
ControlUnitId | 一組可被系統共同管理的設備單元,例如一台調劑台、一組藥櫃、一個控制盒或一組產線 station。 | 不等於單一 adapter,也不一定等於一台 PC。 |
StationId | Control Unit 底下可被操作或辨識的位置,例如站點、格位、通道、藥槽或工作區。 | 不應拿來取代 DeviceId。 |
DeviceId | 系統內對單一設備或 adapter instance 的穩定識別。 | 不應包含連線密碼、IP、Port 等敏感資訊。 |
| Adapter instance | 程式執行時實際負責 Connect、Disconnect、GetStatus、Execute 的物件。 | 不應知道 WebApi route 或 UI 控制項。 |
| Plugin package | 未來可能由外部 DLL、manifest、版本資訊與相依檔案組成的設備擴充包。 | 本階段不載入、不掃描、不執行。 |
| Resource | 實體或邏輯上不能被同時占用的資源,例如設備、COM port、TCP endpoint、機械手臂、門鎖、秤重平台。 | 不只等於 DeviceId,也可能跨多個 device。 |
5. 多設備單元模型邊界
第一版分析建議先把多設備單元視為「設定與執行協調模型」,不要直接改既有 config schema。未來若要實作,需另行確認是否新增 ControlUnitId / StationId 欄位,以及它們如何映射到既有 DeviceId。
| 層級 | 候選責任 | 第一版邊界 |
| Control Unit | 管理一組設備、共享資源與啟停生命週期。 | 先分析,不新增設定欄位。 |
| Station | 表示 Control Unit 內的一個操作位置或槽位。 | 先定義語意,不綁定特定藥局設備。 |
| Device | 既有 adapter 操作單位。 | 沿用 DeviceId,不改 Adapter contract。 |
| Workflow Node | 宣告要操作的 DeviceId 與命令。 | 不在 node 內寫死連線資訊或資源鎖實作。 |
| Task / Command | 任務與命令需可追蹤 lock / queue 狀態。 | 未確認前不新增資料表或 DB 欄位。 |
建議原則:
ControlUnitId用來描述「哪一組設備單元」,DeviceId用來描述「哪一個設備 adapter」。StationId可以輔助描述「哪個位置」或「哪個工作區」,但不應讓 Core 寫入案場客製語意。- 同一個
DeviceId不應同時被兩個任務任意操作,除非後續明確定義可共享模式。 - 跨設備的實體資源,例如同一條輸送機或同一個 COM port,應以 Resource Lock 管理,而不是靠命名慣例暗示。
6. Plugin Loader 邊界
Plugin Loader 未來應是「Infrastructure / Host 端能力」,不應進入 Core。Core 只需要知道 IDeviceAdapter 與標準 Result / Error,不能知道 DLL 路徑、AssemblyLoadContext、manifest、檔案雜湊或外部套件相依。
| 項目 | 邊界說明 |
| Plugin 掃描 | 未來可由 Host / Infrastructure 掃描指定目錄,但本階段不掃描。 |
| DLL 載入 | 未來需有 allowlist、版本、相依套件與失敗隔離策略,但本階段不載入。 |
| Adapter 建立 | Plugin 最終應產生或註冊 IDeviceAdapter 相容物件,不讓 WorkflowEngine 依賴 plugin 型別。 |
| Manifest | 可候選記錄 plugin id、版本、支援 device type、capability 與 checksum,但本階段不定 schema。 |
| 失敗隔離 | 載入失敗、版本不符、建構失敗、Connect 失敗需轉為標準 ErrorInfo / ExecuteResult。 |
| 安全 | 不保存密碼、Token、正式路徑或連線資訊到 repo;不從任意路徑載入 DLL。 |
Plugin Loader 不應負責:
- 不寫 Workflow node 邏輯。
- 不直接修改 task state。
- 不繞過
IDeviceAdapter。 - 不把案場流程寫入 Core。
- 不在未確認前導入外部 plugin framework。
- 不把 DLL 載入失敗吞掉或只回傳
false。
7. Resource Lock 邊界
Resource Lock 的目的,是保護不能同時被多個任務或命令占用的實體資源。它應該是任務執行協調層的能力,不應散落在每個 Adapter 或 Workflow node 內。
| 候選資源 | 鎖定原因 | 第一版建議 |
DeviceId | 同一設備同時下多個命令可能造成狀態錯亂。 | 預設可視為 exclusive resource。 |
| 通訊通道 | 多個設備共用同一 COM port、TCP endpoint 或 gateway。 | 需允許跨 device 的 shared resource key。 |
| 機械動作區 | 馬達、門、輸送帶、秤重平台等實體動作不可重疊。 | 不寫死藥局語意,以 resource key 表示。 |
| Control Unit | 整組設備啟停、維護或急停時需整體鎖定。 | 未來可支援 unit-level lock。 |
| Schema Apply / Preview | Preview 可 read-only;Apply 屬高風險獨占操作。 | 3D 不處理正式 Apply。 |
Resource Lock 第一版候選欄位:
| 欄位 | 用途 |
ResourceKey | 資源穩定識別,例如 device:light-01 或 channel:com3。 |
Mode | Exclusive / SharedRead 等候選模式,需另行確認。 |
OwnerTaskId | 鎖的任務擁有者。 |
OwnerNodeId | 鎖的節點來源。 |
CommandName | 觸發鎖的命令。 |
AcquiredAtUtc | 取得時間。 |
ExpiresAtUtc | 超時保護,避免永久卡住。 |
ReleaseReason | 正常完成、失敗、取消、timeout 或補償釋放。 |
8. Command Queue 邊界
Command Queue 的目的,是讓命令進入可觀察、可取消、可追蹤的排程流程,而不是讓 HTTP request、Host loop、Adapter 或 Thread.Sleep 各自形成隱性佇列。
| 面向 | 邊界說明 |
| Queue 所在層 | 建議放在 Application / ServiceHost 協調層,不放 Core,不放 WebApi controller。 |
| Queue 單位 | 可依 DeviceId、ResourceKey、ControlUnitId 或 Task scope 決定。 |
| 排序策略 | 第一版建議先分析 FIFO;Priority、deadline、preemption 需另行確認。 |
| 狀態 | 至少需能區分 queued、running、completed、failed、cancelled、timeout。 |
| 錯誤 | queue 滿、lock timeout、device offline、adapter exception 需有標準錯誤碼。 |
| DB | 未確認前不新增 queue table,也不改 TaskStore schema。 |
Command Queue 不應負責:
- 不直接操作硬體,仍需透過 Adapter。
- 不把任務狀態改出 StateMachine 規則。
- 不在未確認前導入外部 queue 套件。
- 不把長時間流程塞回單一 WebApi request。
9. 設定版本與回滾邊界
多設備與 plugin 會讓設定變更風險升高,因此後續需要可追蹤的設定版本策略。但本文件不修改 devices.json、workflows.json 或 appsettings.json 格式。
| 項目 | 候選方向 | 本次邊界 |
| 設定版本 | 版本號、異動時間、作者、摘要、hash。 | 只分析,不改 schema。 |
| Plugin 版本 | plugin id、version、capabilities、checksum。 | 只分析,不建立 manifest。 |
| 相容性 | min core/app version、adapter contract version。 | 不改 public contract。 |
| 回滾 | 保留上一版設定與安全回復策略。 | 不建立儲存機制。 |
| 審核 | 正式變更前需人工確認與驗證節點。 | 不導入權限系統。 |
10. 與 3C Application Contract 的關係
3C 已建立 Application contract 第一版,但 3D 不應直接把 plugin 與 queue 塞進既有 contract。比較安全的順序是先完成 3D 邊界決策,再判斷是否需要新增 Application 層 contract。
| 既有 3C 能力 | 3D 可能承接方式 | 是否本次實作 |
DeviceStatusService | 未來可查 Control Unit / Plugin adapter 狀態。 | 否 |
TaskControlService | 未來可把命令排入 queue 或取得 lock 後執行。 | 否 |
HealthService | 未來可回報 plugin、queue、resource lock 健康狀態。 | 否 |
SchemaPreviewService | 與 plugin 無直接關係;仍只 preview、不 apply。 | 否 |
11. 候選程式影響範圍
以下只是後續取得明確授權後可能進入的候選範圍;本次不修改程式 repo。
| 類型 | 候選路徑 / 名稱 | 用途 | 是否已授權 |
| 新增文件 | docs/plugin-multi-device-boundary-analysis.md | 本次 3D 邊界分析 | 已授權進入文件整理 |
| 新增 contract | HS.DeviceControl.Application.Resources/* | Resource Lock request / result / service contract | 尚未 |
| 新增 contract | HS.DeviceControl.Application.Commands/* | Command Queue request / result / service contract | 尚未 |
| 新增模型 | ControlUnitId / StationId DTO | 多設備單元識別 | 尚未 |
| 新增 Plugin contract | HS.DeviceControl.Plugins 或 Application plugin namespace | Plugin metadata / factory 候選 | 尚未 |
| 新增 Infrastructure | Plugin Loader / manifest reader | 掃描與載入外部 DLL | 尚未,且需再次確認 |
| 修改 Adapter contract | IDeviceAdapter | 若需 capability / metadata 擴充 | 尚未,且目前不建議先改 |
| 修改設定 schema | devices / workflows / appsettings | Control Unit、Station、resource key、plugin mapping | 尚未 |
12. 驗證節點候選
| 驗證節點 | 驗證目的 | 前置條件 |
| 多設備單元模型確認 | 確認 ControlUnitId / StationId / DeviceId 不混淆,且不寫案場客製語意。 | 完成 3D 決策確認。 |
| Resource Lock dry-run 驗證 | 確認同一 resource 被第二個任務占用時會被擋下,且逾時可釋放。 | 需另行同意 contract / fake 實作。 |
| Command Queue fake 驗證 | 確認命令可排隊、狀態可查、取消可追蹤,不直接操作硬體。 | 需另行同意 queue contract。 |
| Plugin metadata 驗證 | 確認 manifest / metadata 可讀,但不載入 DLL。 | 需另行同意 metadata schema。 |
| Plugin Loader 隔離驗證 | 確認載入失敗會回標準錯誤且不影響 Host。 | 需另行同意載入外部 DLL,非本階段預設。 |
13. 尚待決策
| 決策 | 影響 |
是否採用 ControlUnitId / StationId 作為標準命名 | 會影響設定格式、DTO、Log、Trace 與 UI 顯示。 |
| Resource Lock 放在 Application 層、ServiceHost 層或 Infrastructure 層 | 會影響 public contract、測試與未來 DB 保存方式。 |
| Command Queue 是否先做 in-memory fake contract | 會影響 TaskControlService 與任務生命週期。 |
| Plugin 第一版是否只做 metadata / manifest,不載入 DLL | 可降低安全與相依套件風險。 |
是否維持 IDeviceAdapter 不變,先用 wrapper / factory 映射 plugin | 會影響 Adapter contract 穩定性。 |
| 是否需要設定版本與回滾文件先行 | 會影響 JSON schema 與管理流程。 |
| 是否將 3D 第一版限制為文件與決策確認 | 可避免過早導入外部 DLL、queue 套件或背景服務。 |
14. 停止線
本文件完成後,以下項目仍不得自動執行:
- 不載入外部 DLL。
- 不掃描 plugin 目錄。
- 不新增 Plugin Loader 程式。
- 不新增或更換 plugin framework、queue framework、DI / Host framework。
- 不修改
IDeviceAdapter、Core、Adapters、Infrastructure public method 簽章。 - 不修改既有 config schema 或啟動方式。
- 不新增 ServiceHost / WebApi 專案。
- 不新增 API route、controller、endpoint 或 middleware。
- 不執行 DB 寫入、DDL、ALTER TABLE 或正式 Apply。
- 不保存密碼、Token、IP、Port、完整 connection string、正式 DLL 路徑或正式環境資訊。
- 不把案場客製流程、藥局專用規則或特定設備邏輯寫入 Core。
15. 建議下一步
建議下一步先整理「3D 決策確認表」,讓使用者逐項確認:
- 是否同意 3D 第一版先做邊界與 contract 草案,不載入外部 DLL。
- 是否同意多設備單元命名先採
ControlUnitId/StationId/DeviceId三層語意。 - 是否同意 Resource Lock 第一版先做 Application 層 contract 候選,不新增 DB table。
- 是否同意 Command Queue 第一版先做 fake / in-memory contract 候選,不導入 queue 套件。
- 是否同意 Plugin 第一版先做 metadata / manifest 邊界,不建立 Loader。
- 是否同意維持
IDeviceAdapterpublic contract 不變,後續用 wrapper / factory 承接 plugin。 - 是否同意未確認前不改 config schema、不改啟動方式、不新增 ServiceHost / WebApi、不保存敏感資訊。