iT邦幫忙

0

(5) 研發看 MES:從機台到製程,資料開始產生關聯了

mes
  • 分享至 

  • xImage
  •  

研發看 MES:從機台到製程,資料開始產生關聯了

上一篇,我們從 MES 裡相對單純的「機台」開始。
建立 Machine Table、完成 CRUD,再加上一些基本的 Validation。
到這裡為止,我們的資料其實還很單純:

Machine

每一台機台都是獨立存在的資料。

但從今天開始,事情要稍微變複雜一點。
因為我們準備建立第二個重要的基礎資料:
製程(Process)。

而這一次,我們不只要建立 Process。
我們還要開始回答一個問題:

「這個製程,可以使用哪些機台?」

資料與資料之間,第一次產生了關聯。

先建立 Process

先不考慮 Machine。
單純看 Process,其實跟上一篇的 Machine 很像。
例如我們可能有:

PROCESS_UUID
PROCESS_CODE
PROCESS_NAME
ENABLE
CREATE_TIME
UPDATE_TIME

建立幾筆資料:

P001    組裝
P002    測試
P003    檢驗

API 也差不多:

POST /processes
GET /processes
GET /processes/{process_uuid}
PUT /processes/{process_uuid}

做到這裡,其實還是在上一篇提到的:
CRUD + Validation
例如:

if process_code_exists(data["process_code"]):
    raise Exception("Process code already exists.")

如果今天 Process 只是這樣,那 Day 05 好像也只是把 Machine 換成 Process 而已。
真正有趣的是下一個需求。

一個製程可以使用哪些機台?

假設工廠現在有三台機台:

MC-001    組裝機 01
MC-002    組裝機 02
MC-003    測試機 01

而我們有兩個製程:

P001    組裝
P002    測試

現場告訴我們:

組裝
├── MC-001
└── MC-002

測試
└── MC-003

也就是說:
P001 可以使用 MC-001、MC-002。
P002 可以使用 MC-003。

這時候 Process 和 Machine 就不再是兩張完全沒有關係的 Table。
我們需要把它們串起來。

最直覺的做法:Machine UUID 放進 Process?

第一次遇到這種需求,很容易想到:

那我在 PROCESS Table 加一個 MACHINE_UUID 不就好了?

變成:

PROCESS
-----------------------
PROCESS_UUID
PROCESS_CODE
PROCESS_NAME
MACHINE_UUID
ENABLE

這樣確實可以表示:

P001 → MC-001

問題是:
如果 P001 可以使用兩台機台呢?

P001
├── MC-001
└── MC-002

一個 MACHINE_UUID 就不夠用了。
那要不要改成:

MACHINE_UUID_1
MACHINE_UUID_2
MACHINE_UUID_3

技術上當然可以做。
但是馬上又會出現問題:

如果未來一個製程可以使用 10 台機台呢?

那我們是不是要一路加:

MACHINE_UUID_4
MACHINE_UUID_5
MACHINE_UUID_6
...

這顯然不是一個很容易維護的設計。

那把 UUID 存成字串呢?

另外一個很直覺的做法可能是:

MACHINE_UUID =
"uuid-001,uuid-002,uuid-003"

看起來很方便。
一個欄位就可以放很多台 Machine。
但是未來如果我要查:

MC-001 現在被哪些製程使用?

事情就開始變麻煩。

而且資料庫原本可以幫我們處理的關聯、約束、JOIN,也會變得比較難使用。
所以面對這種需求,我們通常不會把多個 ID 塞進同一個欄位。
而是建立第三張 Table。

Relation Table:把關係獨立出來

我們可以建立:

PROCESS
MACHINE
PROCESS_MACHINE

其中 PROCESS_MACHINE 專門負責記錄:

哪一個 Process 可以使用哪一台 Machine?

概念上可以長這樣:

PROCESS_MACHINE
--------------------------------
PROCESS_UUID
MACHINE_UUID

假設:

P001 → MC-001
P001 → MC-002
P002 → MC-003

資料就會變成:

PROCESS_UUID    MACHINE_UUID
----------------------------
P001            MC-001
P001            MC-002
P002            MC-003

這時候關係就清楚很多。

為什麼我要拆第三張 Table?

因為我們現在真正要記錄的,不只是 Process,也不是 Machine。
而是:
Process 與 Machine 之間的「關係」。

換個方式看:

Process
   │
   │
   ├── Machine
   ├── Machine
   └── Machine

一個 Process 可以對應多台 Machine。
而如果未來:
同一台 Machine 也可能被不同 Process 使用。

例如:

Process-A ──┐
            ├── Machine-001
Process-B ──┘

那這就會形成典型的:
Many-to-Many(多對多)
這時候中間的 Relation Table 就會變得非常重要。

API 收到的資料也開始不一樣了

上一篇建立 Machine 時,我們收到的資料可能很單純:

{
    "machine_code": "MC-001",
    "machine_name": "組裝機 01"
}

但是建立 Process 時,現在可能會收到:

{
    "process_code": "P001",
    "process_name": "組裝",
    "machine_uuid_list": [
        "machine-uuid-001",
        "machine-uuid-002"
    ]
}

這時候後端就不再只是:

process = Process(**data)

db.add(process)
db.commit()

而是要開始處理兩件事情:

建立 Process
      ↓
建立 Process 與 Machine 的關係

概念上可能像:

process = Process(
    process_code=data["process_code"],
    process_name=data["process_name"]
)

db.add(process)
db.flush()

for machine_uuid in data["machine_uuid_list"]:
    relation = ProcessMachine(
        process_uuid=process.process_uuid,
        machine_uuid=machine_uuid
    )

    db.add(relation)

db.commit()

到了這裡,我們第一次開始感受到:

功能看起來只是「新增製程」,Database 實際上可能寫了不只一張 Table。

db.flush() 是在做什麼?

上面的程式還多了一個上一篇沒有特別提到的東西:

db.flush()

為什麼不是直接 commit()?
因為我們建立 Process 之後,接下來馬上還需要它的 UUID 去建立 PROCESS_MACHINE。
但這整件事情,我們希望它仍然是:
同一次 Transaction。

概念上就是:

BEGIN

建立 PROCESS

取得 PROCESS_UUID

建立 PROCESS_MACHINE
建立 PROCESS_MACHINE
建立 PROCESS_MACHINE

COMMIT

如果中間任何一步失敗:

ROLLBACK

而不是 Process 建成功了,Machine Relation 卻只寫了一半。
這也是當資料開始產生關聯後,研發需要開始注意的另一件事情:
資料一致性。


Machine 不存在怎麼辦?

現在 API 傳進來:

{
    "machine_uuid_list": [
        "machine-001",
        "machine-999"
    ]
}

但是:
machine-999
根本不存在。

那我們應該怎麼辦?
最簡單的想法就是直接寫入,讓 Foreign Key 幫我們擋。

但從系統設計的角度,我通常還是希望在 Service Layer 先確認:

machines = get_machines(data["machine_uuid_list"])

if len(machines) != len(set(data["machine_uuid_list"])):
    raise Exception("Machine not found.")

甚至還要檢查:

if not machine.enable:
    raise Exception("Machine is disabled.")

所以新增 Process 的流程開始變成:

收到資料
   ↓
檢查 Process Code
   ↓
檢查 Machine 是否存在
   ↓
檢查 Machine 是否可使用
   ↓
建立 Process
   ↓
建立 Relation
   ↓
Commit

這已經比上一篇的 Machine CRUD 多了一些東西。

那 Machine 現在還可以刪除嗎?

這時候上一篇留下來的問題也開始有答案了。
假設:

MC-001

已經被:

P001

使用。

關係變成:

P001 → MC-001

現在使用者突然說:

我要刪除 MC-001。

如果我們真的把 Machine Delete 掉:
PROCESS_MACHINE 怎麼辦?

這就是為什麼上一篇提到:
Master Data 很多時候比起 Delete,更適合使用 Disable。

例如:

MC-001
ENABLE = False

它仍然存在。

過去的關係與歷史資料也還在。
但是建立新的設定時,我們不再讓使用者選擇它。
到這裡,上一篇談的設計選擇就真的開始產生影響了。

Update 也開始不只是 UPDATE

假設 P001 原本可以使用:

MC-001
MC-002

現在使用者修改成:

MC-002
MC-003

那我們真正要更新的其實不只是:

UPDATE PROCESS

還包括:

原本:
P001 → MC-001
P001 → MC-002

修改後:
P001 → MC-002
P001 → MC-003

也就是 Relation 本身也需要同步。

這時候 Update 的概念就變成:

更新 Process
+
更新 Process-Machine Relation

所以我現在會把 CRUD 理解成:

CRUD 是操作的入口,不一定代表背後只會執行一個 INSERT、SELECT、UPDATE 或 DELETE。

真正決定複雜度的,通常是後面的 Business Rule 與資料關係。

從研發角度看:系統開始長出「關係」了

上一篇 Machine 很單純:

Machine

今天加入 Process 之後:

Process
   │
   └── Process-Machine
              │
              └── Machine

我們第一次開始處理:
Foreign Key
Relation Table
Many-to-Many
Validation
Transaction
資料一致性

而這些概念,接下來會一直出現在 MES 裡。
因為 MES 本身就是一個充滿關係的系統。
產品跟途程有關。
途程跟製程有關。
工單跟產品、客戶、途程有關。
Lot 又跟工單有關。
當系統越往後開發,這張關係網只會越來越大。

現在我們已經有:

Machine
   ↓
Process

但還是不夠。
因為目前系統只知道:

「工廠有哪些製程。」

它還不知道:

「產品到底要先做哪一道,再做哪一道?」

例如:

組裝
測試
檢驗

到底是:

組裝 → 測試 → 檢驗

還是:

測試 → 組裝 → 檢驗

這就是下一個功能要解決的問題。

下一篇:
「第一次把多個製程串成一條生產流程。」

而這一次除了 Relation 以外,我們還會多出一個新的問題:
順序(Sequence)。

認識我們
聯繫我們

章節列表


*提醒邦友,使用第三方服務/API 時,請務必評估資安風險與隱私保護
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言