天天看點

如何應用資料模型?

如何應用資料模型?

作者 | 風水

來源 | 阿裡技術公衆号

一 前言

Vmo 是我在 18 年釋出的一個工具庫,用于快速建立資料模型,當時我寫了一篇文章《Vmo 前端資料模型設計》得到過一段時間的關注,當時我從事三維裝修相關的項目。在圖形學的背景基礎及海量複雜的資料的情況下,自然而然在前端則會衍生出一種資料處理、解析、消費的技術方案,也種下了我對資料模型概念的種子。

簡單舉個例子:需要解析一個三維裝修的房子的資料會有哪些呢?

  • 房子(House),樓層(Layer),房間(Room),牆體(Wall),牆面(WallSpace),牆角(Corner),吊頂(Ceiling),踢腳線(Skirting),地(Floor,帶厚度),地面(FloorSpace),門(Door),窗(Window)。
  • 以及會延伸出來大量的變體,比如飄窗,直角飄窗,弧形窗,牆洞,樓梯等等。

在解析這些資料中存在非常多的互相關聯和計算,比如 房間需要和牆面,牆面需要和牆體關聯,牆體和最多 2 個房間關聯,牆角和多個房間關聯,牆角和多個牆體關聯等等

面對這樣海量、複雜的資料,如果隻靠着一個 API 請求的結果消費顯然是非常不可取的方案,先不說這些資料能不能正确的解析出來,就說這些資料如何維護,儲存時如何收集到所有資料反向序列化給後端都是些頭疼的問題。

當然這些問題在當時我們抽象的各個資料模型中得到了解決,如果想了解具體細節可以檢視我之前的文章。

今天我想講的是,在我加入阿裡後,一直在思考的關于資料模型的兩個問題:

  • 是不是資料模型這種事情對于正常項目沒有使用場景或者價值呢?正常的,像一些資料查詢,或者填寫一些資料送出。這種需求裡面有必要使用什麼抽象類,什麼資料模型嗎?
  • 為什麼在前端圈子裡面,很少有看到這方面的内容,現在前端圈子裡大多都是在走向函數化,Composition等等,是不是這條路子走的有問題?

在尋覓了 2 年後,主導 Lazada 商家端的商品釋出頁面重構時,仿佛找到了一些答案。

二 商品模型

首先在新增一個商品的過程中,實際上是使用者在以用戶端的形式制作一組商品資料,正常的前端視角來看就是送出一份“JSON”。

而編輯就是通過 API 拉取這份“JSON”解析到 Form 表單中,讓使用者進行編輯後,再将這份“JSON”送出。

那麼粗略的将資料抽象為模型将會成為這樣:

如何應用資料模型?

Well,到目前為止,我們做的事情都感覺像是在脫褲子放屁,多此一舉。哈哈哈,各位看官暫且勿噴,稍安勿躁 。

那麼為什麼需要把這些資料抽象為一個類呢?我拿一下幾個 Case 來說明:

1 請求資料 & 單元測試

很多時候,前端把對資料的請求和處理是寫在元件中的,更優一點可能會封裝在某個聚類裡面,或者某個 Hook 裡面,調用時輕巧的拿到狀态和資料。

像商品這樣的資料請求方式會存在多種:草稿中擷取,編輯中擷取,某個類目中擷取(不同類目下,商品屬性不同)。

每種擷取方式請求的接口和參數組合方式可能不同,但最後前端消費的産物卻是相同的。按照政策模式來說,對于一個商品模型的擷取隻是使用了不同的政策,但産物卻是一緻的,消費端無論調用何種方式,擷取到的結果都是可靠的 Product 模型類。

有經驗的前端都知道,很多時候,在一個項目在一輪輪的疊代後,我們的接口資料往往會存在部分資料需要前端做一定處理或者轉換。

面對這樣的資料處理,如果放在一個元件或者 Hook 中,是不太合适的,在做單元測試或者資料消費的時候都可能會給我們帶來一些阻力。

在我看來,調試一個資料問題最好的辦法,就是寫一個單元測試,對單元測試預期的結果進行調試,往往比我們在浏覽器中 Mock 一份資料調試資料更高效,對将來的穩定性也更有幫助。

安全感,資料消費起來,一個類和一份 JSON 給開發者帶來的安全感和爽感是完全不同的。消費過資料模型 或者 次一點 消費過Interface的小夥伴,我相信對這一點是非常認同的。

哈哈,說到這裡有些小夥伴可能要問了,你說的這個我們用Interface也能達到同樣的效果呀。好,咱們繼續...

2 計算性消費資料

什麼叫計算性消費資料的,說的簡單點,就比如:

class Person1 {
  fistName = "Wang";
  lastName = "Yee";

  get fullName() {
    return `${this.lastName} ${this.fistName}`; // Yee Wang
  }

  get fullNameCN() {
    return `${this.fistName} ${this.lastName}`; // Wang Yee
  }
}           

上面這個例子非常經典且清晰,中繼資料中可能隻是些基本資料,但是很多時候前端需要根據不同場景來進行中繼資料組裝,以往這些資料往往會被封裝為各個方法,或者被當做 template 寫在元件中,散落在各個角落,每當用到這份資料時可能又會重新按照場景組裝一遍。往往這種時候就會存在 需求缺失,比如某情況下需要将之前所有消費到 fullName 的地方改為小寫。

拿到商品釋出來說,計算性消費資料到底有哪些應用場景呢?

在此之前,我想先解釋一下SKU這個資料模型,它其中最核心的中繼資料是:

如何應用資料模型?
如何應用資料模型?

按照上圖這個表格中所示,可以看到該商品共有 6 個 SKU,第一個 SKU 所對應的SKU模型資料應為:

如何應用資料模型?
class SKU {
  value = new Map([
    [
      new SKUProperty({ id: 1, label: "Color Family" }),
      new SKUValue({ id: 101, label: "Red" }),
    ],
    [
      new SKUProperty({ id: 2, label: "Size" }),
      new SKUValue({ id: 201, label: "33" }),
    ],
  ]);
  price: string;
}           

像這樣一個 SKU Model,它所具備的中繼資料已經可以清晰描述目前 SKU,而且可以通過 SKU 的擴充方法做到很多有用的資料,比如:

  • getProperties() 擷取該 SKU 有所有屬性,如:Color Family,Size。
  • getValues() 擷取該 SKU 所有Value,如:Red,33。
  • isEqual(anotherSKU: SKU): boolean 比較一個 SKU 是否和目前 SKU 完全相同,這在後續的資料合并中非常有用。
  • getValueByPropertyId(id: string) 通過 PropertyId,擷取一個 SKUValue。

相比與隻是一個 Object 對象來說,資料模型能夠帶來非常多的資料處理和資料擴充能力,當某種情況下需要消費由該資料産生的計算性消費資料時,可以很輕易的進行擴充使用,對于資料結構也有更好的預期和掌控力。

結合對該資料模型的單元測試,就可以清晰快速的開發資料層,當資料層可靠後,在視圖層消費就會變得行雲流水,得心應手了。

舉個單元測試的例子:

it("alias sku equal", () => {
  const data = [
    {
      text: "300MB",
      value: 2988,
      name: "p-1",
    },
    {
      text: "Blue",
      value: 2888,
      alias: "Blue1",
      name: "p-2",
    },
  ];
  const sku = SKU.fromData(data);
  expect(
    sku.isEqual(
      SKU.fromData([
        {
          text: "300MB",
          value: 2988,
          name: "p-1",
        },
        {
          text: "Blue",
          value: 2888,
          alias: "Blue2",
          name: "p-2",
        },
      ])
    )
  ).toBeFalsy();
});           

這種SKU,是一種類型較為特殊的SKU,它其中會存在 alias 字段,當有這種字段時,在做SKU比對時,不但要對 SKUProperty,SKUValue 的ID做比對,還需要對 alias 字段做比對。

是以按照上面的單測來看,結果應該是 false,因為這兩份資料中的alias是不同的。沒辦法,這是一個業務需求。

如果在視圖層做資料比對時,使用的是純資料進行比對,很有可能漏掉這部分邏輯,這就會導緻項目變得捉襟見肘,拆東牆補西牆。

反正,在消費層遇到很多的需要對資料處理或判斷時,大可以将這部分能力交給資料模型來處理,由資料模型來保證資料的穩定性。

3 資料關系

使用資料模型,還可以幫你清晰管理資料關系,比如商品和SKU之間,SKU和 SKUProperty,SKUValue 之間的關系。

我舉個具體案例:

如何應用資料模型?

這是一個商品編輯時組 笛卡爾積(Cartesian product) 的過程,當我們的SKU屬性被使用者添加或者修改時,将會觸發笛卡爾積的重新計算出最新的排列組合結果。

比如當使用者新增一個尺碼為35時,笛卡爾積将會多出兩項組合結果。同理,如果當次元增加一列時,比如添加材質次元,将會産生更多SKU結果。

以往,前端開發者總會将這部分計算過程封裝成為一個數學方法,放在utils中随時調用,這看起沒什麼問題。

如果将這個過程看做是,一個 SKUCollection 資料模型的建構過程的話,一切就會将變得順理成章:

test('sku calculate whether valid', () => {
  const skuCellection = SKUCollection.fromData({
    'p-3xxxx': [
      {
        text: '300MB',
        value: 2,
      },
      {
        text: '128GB',
        value: 3,
      },
    ],
    'p-4xxxx': [
      {
        text: 'Blue',
        value: 3,
      },
      {
        text: 'Red',
        value: 15,
      },
      {
        text: 'Green',
        value: 1,
      },
    ],
  });

  expect(
    skuCellection.value
  ).toEqual(
    // 6 SKU Model
  ); 
});           

有了這樣一個資料模型結構後,就可以清晰的通過資料模型來調用其相關的資料和計算性資料。

另外,不同的資料模型雖然互相依賴,但對資料解析和計算性資料缺互相獨立,可以做到獨立使用和單元測試。

如何應用資料模型?

三 異常模型

商品釋出本質上是一個較為複雜的表單送出頁面。由于字段多,互動複雜等原因,在産品設計過程中,就已經将很多字段先拆分為不同子產品,來減輕使用者心理負擔。

比如會存在:基礎資訊,商品屬性,詳描,運費等。

在填寫過程中,會存在部分 前端校驗 + 後端校驗 的場景。

在資料送出或者其他資料寫入過程中,後端同時會處理字段校驗,當後端發現某個字段填寫錯誤時,服務端将傳回錯誤資訊及錯誤字段資訊。

為了更好的互動體驗,前端将會根據傳回擷取到字段資訊,定位到對應的字段位置,顯示錯誤資訊并報紅,另外還需要根據目前字段判斷其所歸屬的子產品進行報錯。

如何應用資料模型?

還有一種情況是:服務端的第一層校驗通過,調用其他商品上遊鍊路時抛出異常,此時上遊鍊路可能已經丢失字段資訊,面對這樣的異常資料,前端需要展示在表單頂部,并且提供traceId,以便追蹤定位異常。

如何應用資料模型?

這樣的異常資料,通常處理都需要和後端反複确認不同Case的表現情況,有些異常甚至很難出現一次,我們在疊代過程中往往會因為一些元件變動或者邏輯變動丢失這部分資料消費能力。

就商品釋出來說,顯而易見的"儲存"的動作是一個需要處理異常的情況,是以我們會在送出的地方寫上很多後端傳回異常時的處理邏輯。

當有一天,有另外一個疊代需要寫入操作時,同樣也會産生異常的情況,這些的異常情況再次處理時又會有很多資料轉換和錯誤顯示的邏輯。

如果收到這份後端傳回資料,将他轉換為異常資料模型,然後交由視圖層消費,這樣會讓所有異常模型下需要處理的邏輯複用避免互動邏輯丢失。

當然,視圖層如何更巧妙的消費該資料模型又是另外一個有意思的設計,此處暫且不表,後面我還會寫一篇專門介紹商品釋出的視圖層狀态管理設計。

四 總結

在商品釋出中,除了上述提到的幾個資料模型以外,其實還建構了一些其他類型的資料模型,如:運費模型,商品品質分模型,類目推薦模型等... 然後由這些多個子模型共同組合成為一個商品的模型。

這樣的資料模型在消費起來,開發者其實不會太過關心究竟需要請求什麼API,傳回的資料究竟是什麼樣的,他們的傳回是否要處理、轉換、相容等問題。

同時,這樣高品質的資料模型其實不依賴于視圖層的架構,它可以被抽離作為一個獨立的包來管理維護,然後在其他頁面引入使用,比如商品域可能遇到的:商品管理,商品選擇,運費編輯,商品品質分預覽等等...

回到開頭,我提到的問題:

首先肯定的是,在我所使用的過程中,資料模型确實非常清晰,有力,牢固的解決了我所面到的業務問題,是以它是有價值的。

至于和正常的需求,到底應該用什麼好呢?哈哈,這個問題有個比較無賴的回答,小孩子才考慮什麼要什麼不要,成年人什麼都要,沒有什麼技術是非黑即白的。

Vite 就隻能在 Vue 的項目裡面使用嗎?

什麼合适用什麼,簡單的資料查詢展示不需要這麼精細的資料處理,當然可以直接拿來即用咯,解決業務問題的方法就是好方法!

至于Composition API,其實在商品釋出的重構過程中,基本絕大多數都是使用這種設計思路來實作的,這樣的設計确實能讓我們清晰的分辨每個方法是幹什麼的,是否會影響互動,以及這樣的互動是在做什麼,每個互動都在一個位置維護和處理,後面我會單獨寫一篇介紹。

實踐過程中發現,資料模型和Composition API并不沖突,一個是用來處理資料層,一個是用來處理視圖層,它們相輔相成結合一些訂閱模式的設計,就會讓整個項目的劃分異常清晰,我十分建議大家在以後遇到單點項目較為複雜時能夠使用這一套思路來解決業務問題!

繼續閱讀