iT邦幫忙

0

(7) 研發看 MES:客戶資料只是 CRUD?從聯絡人開始看一對多資料設計

mes
  • 分享至 

  • xImage
  •  

研發看 MES:客戶資料只是 CRUD?從聯絡人開始看一對多資料設計

前面幾篇,我們已經完成:

Machine
   ↓
Process
   ↓
Route

而且系統的複雜度也慢慢增加。
Machine 的時候,我們主要處理:
CRUD、Validation、Disable。

到了 Process,開始出現:
資料之間的關聯。

到了 Route,又多了:
Process + SEQ,以及流程控制。

今天我們稍微離開生產流程,來建立另外一個之後工單會使用到的 Master Data:
Customer(客戶)。

乍看之下 Customer 好像又回到了最簡單的 CRUD。
但如果一個客戶可以有多個聯絡人,事情就會開始變得不太一樣。

先從最簡單的 Customer 開始

如果今天只需要記錄客戶基本資料,Table 其實很單純。
例如:

CUSTOMER
-------------------------
CUSTOMER_UUID
COMPANY_CODE
COMPANY_NAME
ADDRESS
ENABLE
CREATE_TIME
UPDATE_TIME

建立一個客戶:

COMPANY_CODE : C001
COMPANY_NAME : 客戶 A
ADDRESS      : 桃園市...
ENABLE       : True

API 也跟前面的 Machine 非常像:

POST /customers
GET /customers
GET /customers/{customer_uuid}
PUT /customers/{customer_uuid}

如果文章寫到這裡就結束,那其實沒什麼好講的。
因為本質上就是:

收到資料
   ↓
Validation
   ↓
INSERT CUSTOMER

先前的 Machine 已經做過一次了。

所以今天真正想看的不是 Customer 本身。
而是:
Customer 底下的 Contact。

一個客戶不一定只有一個聯絡人

假設今天建立:

客戶 A

實際跟這間公司合作時,可能不會永遠只找同一個人。

例如:

客戶 A
│
├── 王先生
│   └── 生產相關窗口
│
├── 陳小姐
│   └── 品質相關窗口
│
└── 林先生
    └── 出貨相關窗口

這時候問題就來了。

我們要怎麼把這三個聯絡人存進 Customer?

全部塞在 CUSTOMER Table 可以嗎?

最直覺的方法可能是:

CUSTOMER
-------------------------
CUSTOMER_UUID
COMPANY_CODE
COMPANY_NAME

CONTACT_NAME_1
CONTACT_MOBILE_1

CONTACT_NAME_2
CONTACT_MOBILE_2

CONTACT_NAME_3
CONTACT_MOBILE_3

如果一個客戶最多三個聯絡人,好像可以。
但是很快就會遇到一個問題:

如果某個客戶有第四個聯絡人呢?

那就繼續增加:

CONTACT_NAME_4
CONTACT_MOBILE_4

以後如果有人有十個:

CONTACT_NAME_10
CONTACT_MOBILE_10

顯然會越來越難維護。
所以這裡跟前面的 Route 有點像。
當資料的數量不固定時,不應該一直增加 Column。
我們可以把它拆成另外一張 Table。

Customer 與 Contact 拆開

這時候資料結構可以變成:

CUSTOMER
    │
    ├── CONTACT
    ├── CONTACT
    └── CONTACT

例如:

CUSTOMER
-------------------------
CUSTOMER_UUID
COMPANY_CODE
COMPANY_NAME
ADDRESS
ENABLE

另外建立:

CUSTOMER_CONTACT
-------------------------
CONTACT_UUID
CUSTOMER_UUID
CONTACT_NAME
MOBILE
EMAIL
SEQ

資料可能長成:

CUSTOMER

C001    客戶 A

然後:

CUSTOMER_CONTACT

CUSTOMER    CONTACT     MOBILE
--------------------------------
C001        王先生      09xxxxxxxx
C001        陳小姐      09xxxxxxxx
C001        林先生      09xxxxxxxx

這樣不管一個客戶有多少個聯絡人 Customer Table 本身都不用改。

這就是 One-to-Many

現在 Customer 與 Contact 之間的關係是:

Customer 1
    │
    ├── Contact A
    ├── Contact B
    └── Contact C

也就是:
一個 Customer,可以擁有多個 Contact。

這種資料關係通常稱為:
One-to-Many(一對多)。

前一篇 Process 與 Machine,我們碰到的是資料之間的 Relation。
今天則開始碰到另外一種很常見的資料結構:

Header
   │
   └── Detail

在這個例子裡:

Customer = Header

Contact = Detail

前端傳進來的資料也開始變了

如果只有 Customer,Request 很簡單:

{
    "company_code": "C001",
    "company_name": "客戶 A",
    "address": "桃園市..."
}

現在多了 Contact 之後,可能變成:

{
    "company_code": "C001",
    "company_name": "客戶 A",
    "address": "桃園市...",
    "contacts": [
        {
            "contact_name": "王先生",
            "mobile": "09xxxxxxxx",
            "email": "user1@example.com"
        },
        {
            "contact_name": "陳小姐",
            "mobile": "09xxxxxxxx",
            "email": "user2@example.com"
        }
    ]
}

這種結構開始跟之前不太一樣。
Request 裡面除了 Customer 本身,還包了一組:

contacts[]

也就是:
Nested Data。

Create 還算簡單

建立 Customer 的流程可能是:

收到 Request
    ↓
建立 Customer
    ↓
取得 CUSTOMER_UUID
    ↓
建立 Contact 1
    ↓
建立 Contact 2
    ↓
Commit

概念上的程式可能像:

customer = Customer(
    company_code=data["company_code"],
    company_name=data["company_name"],
    address=data["address"]
)

db.add(customer)
db.flush()

for contact in data["contacts"]:

    customer_contact = CustomerContact(
        customer_uuid=customer.customer_uuid,
        contact_name=contact["contact_name"],
        mobile=contact["mobile"],
        email=contact["email"]
    )

    db.add(customer_contact)

db.commit()

跟上一篇建立 Route 有一點像。
先建立:

Customer

再利用 Customer UUID 建立:

Customer Contact

而且最好放在同一個 Transaction。

如果其中一筆 Contact 建立失敗:

Customer        → 成功
Contact 1       → 成功
Contact 2       → 失敗

我們通常不希望 Database 最後留下:

Customer
└── Contact 1

這種只完成一半的狀態。

所以失敗時應該:

ROLLBACK

讓整次操作一起失敗。

真正麻煩的是 Update

Create 其實還滿直覺的。

真正比較有意思的是:
修改 Customer。

假設資料庫原本是:

客戶 A
│
├── 王先生
└── 陳小姐

使用者打開編輯頁面之後:
保留王先生。
刪除陳小姐。
新增林先生。

修改後變成:

客戶 A
│
├── 王先生
└── 林先生

從使用者的角度來看:

改完,按「儲存」。

就結束了。

但從後端的角度來看,我們收到新資料之後,其實要知道三件事情。

王先生 → 原本就存在
陳小姐 → 原本存在,現在不見了
林先生 → 原本不存在

也就是:

王先生 → KEEP / UPDATE

陳小姐 → DELETE / DISABLE

林先生 → INSERT

這時候 Update 就不再只是:

UPDATE CUSTOMER

而是綜合以下功能:

更新 Customer
比較 Contact 新舊資料
新增 Contact
修改 Contact
刪除 / 停用 Contact

後端怎麼知道誰是誰?

這時候 CONTACT_UUID 就開始變得重要。
假設前端送回:

{
    "contacts": [
        {
            "contact_uuid": "UUID-001",
            "contact_name": "王先生"
        },
        {
            "contact_uuid": null,
            "contact_name": "林先生"
        }
    ]
}

Database 原本有:

UUID-001 → 王先生
UUID-002 → 陳小姐

我們就可以比較。

前端傳回:

UUID-001

Database 也有:

UUID-001

所以:

UUID-001 → 保留 / 更新

前端沒有傳回:

UUID-002

代表:

UUID-002 → 被移除

而:

林先生

沒有 UUID。

代表:

林先生 → 新增

最後就得到:

原本
----------------
UUID-001 王先生
UUID-002 陳小姐


修改後
----------------
UUID-001 王先生
NEW      林先生

這就是處理 Header / Detail Update 時很常遇到的情況。

最簡單的方法:全部刪掉再新增?

看到這裡可能會想:

有必要這麼麻煩嗎?

其實還有一個非常簡單的方法。

每次 Update:

DELETE 原本所有 Contact
          ↓
INSERT 前端傳來的所有 Contact

例如:

原本:

王先生
陳小姐

        ↓ DELETE ALL

修改後重新 INSERT:

王先生
林先生

程式確實會簡單很多。
而且在某些單純的設定資料裡,這種做法也不是完全不能用。
但要注意一件事情:
原本資料的 Identity 會消失。

例如王先生明明沒有變:

原本:
UUID-001 → 王先生

重新建立後:
UUID-999 → 王先生

對使用者來說還是同一個王先生。

對 Database 來說:
已經是完全不同的一筆資料。

如果未來有其他資料引用:

CONTACT_UUID = UUID-001

這時候問題就出現了。

所以到底要:
全部刪掉重建

還是:
比較差異後進行 Insert / Update / Delete

沒有永遠唯一的答案。
要看這筆 Detail 未來會不會被其他資料引用,以及我們需不需要保留它的 Identity 與歷史。

從研發角度看:新增簡單,更新才是問題

Customer 做到這裡,其實可以看到一個很常見的開發問題。

建立資料的時候很單純:

新增 Customer
    ↓
新增 Contact

有幾個聯絡人,就新增幾筆資料。
但到了修改,就沒有這麼單純了。

假設原本是:

客戶 A
│
├── 王先生
└── 陳小姐

使用者修改之後變成:

客戶 A
│
├── 王先生
└── 林先生

從使用者的角度來看,只是:

修改資料 → 按下儲存

但從系統的角度來看,其實要做的是:

王先生 → 保留 / 更新
陳小姐 → 刪除 / 停用
林先生 → 新增

所以「更新」不一定只是執行一次:

UPDATE

而是要先比較:

原本的資料
    ↓
使用者修改後的資料
    ↓
找出差異

最後再決定哪些資料需要:

INSERT
UPDATE
DELETE / DISABLE

這也是為什麼我們會替每一筆 Contact 保留自己的 UUID。

因為系統需要知道:

這筆資料是原本就存在的,還是這次才新增的?
看起來只是很普通的「客戶資料修改」,背後其實已經不只是單純修改幾個欄位。

隨著系統裡的資料越來越複雜,這種:

比較修改前後的差異,再決定資料要新增、修改還是移除。
也會是開發 MES 時經常遇到的問題。

接下來我們要開始建立另一個非常重要的 Master Data:
Product(產品)。

因為到了後面的 Work Order,我們至少需要回答:

這張工單是替哪個客戶,生產哪一個產品?

而 Product 做完之後,我們距離真正建立 Work Order,也就越來越近了。
下一篇:
「產品是由基礎資料組合而成的。」

認識我們
聯繫我們

章節列表


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

尚未有邦友留言

立即登入留言