想像我們有一個深不見底的樹狀結構,而且同一時間有多個使用者同時在使用它,這導致在查詢過程中有比較小的機率,可能出現資料一致性問題,例如以下情況
A - A1 - A11 - ...
- A2 - ...
- A3 - ...
有時候因為開發成本以及效能考量,我們暫時無法解決「某些節點有時候會找不到的問題」,但至少我們可以監控這種現象出現的頻率,有了監控以後,當某個節點出現永久的損壞,我們就有機會提早發現並修復它,也可以追蹤造成它損壞的原因。
複習一下 D13 - 樹狀搜尋問題 非同步版 實作篇,會發現我們其實已經做過類似的事情
const getNameById = (target:string, parentId?:string) => async(id:string):Promise<string|undefined> => {
const result = await getNode(id)
if(result._tag==="NodeNotFoundError") {
console.error(`get id ${id} failed, skip this node`) // 1. 最初版進行 "監控" 的點
return undefined
}
if(result._tag==="Node" && result.text===target) return result.text
const children = await Promise.all(result.childrenIds.map(getNameById(target,id)))
return children.find(text=>text!==undefined)
}
然而這樣的設計會違反「開放封閉原則」,因為「監控」是和「節點模組」不相關的,我們應該基於節點模組去擴展出監控功能,而不是直接把監控的邏輯嵌入到程式碼裡面,例如以下這樣
interface MyNode {
_tag: "Node";
id: string;
text: string;
childrenIds: readonly string[];
}
interface NodeNotFoundError {
_tag: "NodeNotFoundError";
id: string;
}
const getNode: (
id: string,
onNodeNotFoundError: OnNodeNotFoundError
) => Promise<MyNode | NodeNotFoundError> = async (id) => {
const map: Record<string, MyNode> = {
a: { _tag: "Node", id: "a", text: "a", childrenIds: ["a1", "a2", "a3"] },
a1: { _tag: "Node", id: "a1", text: "a", childrenIds: ["a11"] },
a11: { _tag: "Node", id: "a11", text: "a", childrenIds: [] },
a2: { _tag: "Node", id: "a2", text: "a", childrenIds: [] },
a3: { _tag: "Node", id: "a3", text: "a", childrenIds: [] },
};
if (map[id]) return map[id];
else {
return { _tag: "NodeNotFoundError", id };
}
};
type OnNodeNotFoundError = (error: NodeNotFoundError) => void; // 1. 定義一個錯誤處理的鉤子
const getNameById =
(
target: string,
onNodeNotFoundError: OnNodeNotFoundError // 2. 在執行階段才當作參數傳入
) =>
async (id: string): Promise<string | undefined> => {
const result = await getNode(id, onNodeNotFoundError); // 3. 再次把鉤子當作餐數傳入
if (result._tag === "NodeNotFoundError") return undefined;
if (result._tag === "Node" && result.text === target) return result.text;
const children = await Promise.all(
result.childrenIds.map(getNameById(target, onNodeNotFoundError))
);
return children.find((text) => text !== undefined);
};
這樣做總算成功解開了節點和監控兩大模組的耦合,然而這樣做還是會有一些問題!
預計明天利用颱風假好好討論可能造成的問題以及解決方法