跳到主要內容

TypeScript 中的資料建模

Deno KV 目前處於測試階段

Deno KV 和相關的雲端原語 API,例如佇列和 cron,目前為實驗性質,且可能會變更。儘管我們盡力確保資料耐用性,但資料遺失仍有可能發生,特別是在 Deno 更新時。

使用 KV 的 Deno 程式在啟動程式時需要 --unstable 旗標,如下所示

deno run -A --unstable my_kv_code.ts

在 TypeScript 應用程式中,通常需要建立強類型、文件編寫良好的物件來包含應用程式運作的資料。使用 介面類別,您可以描述程式中物件的形狀和行為。

不過,如果您使用 Deno KV,則需要多做一些工作才能保留和擷取強類型物件。在本指南中,我們將介紹處理強類型物件進出 Deno KV 的策略。

使用介面和類型斷言

在 Deno KV 中儲存和擷取應用程式資料時,您可能想要先使用 TypeScript 介面來描述資料的形狀。以下是描述部落格系統中一些主要元件的物件模型

model.ts
export interface Author {
username: string;
fullName: string;
}

export interface Post {
slug: string;
title: string;
body: string;
author: Author;
createdAt: Date;
updatedAt: Date;
}

此物件模型描述部落格文章和相關作者。

使用 Deno KV,您可以使用這些 TypeScript 介面,例如 資料傳輸物件 (DTO),這是強類型包裝器,可用於傳送至或從 Deno KV 接收的未類型化物件。

無需任何額外工作,您便可以輕鬆地將其中一個 DTO 的內容儲存在 Deno KV 中。

import { Author } from "./model.ts";

const kv = await Deno.openKv();

const a: Author = {
username: "acdoyle",
fullName: "Arthur Conan Doyle",
};

await kv.set(["authors", a.username], a);

然而,從 Deno KV 擷取這個相同的物件時,預設不會有與其關聯的型別資訊。不過,如果你知道為金鑰儲存的物件形狀,你可以使用 型別斷言 來告知 TypeScript 編譯器關於物件的形狀。

import { Author } from "./model.ts";

const kv = await Deno.openKv();

const r = await kv.get(["authors", "acdoyle"]);
const ac = r.value as Author;

console.log(ac.fullName);

你也可以為 get 指定一個選用的 型別參數

import { Author } from "./model.ts";

const kv = await Deno.openKv();

const r = await kv.get<Author>(["authors", "acdoyle"]);

console.log(r.value.fullName);

對於較簡單的資料結構,這個技巧可能就夠用了。但通常,你會想要或需要在建立或存取你的網域物件時套用一些商業邏輯。當有這個需求時,你可以開發一組純粹函式,它們可以在你的 DTO 上運作。

使用服務層封裝商業邏輯

當你的應用程式的持久性需求變得更複雜時,例如當你需要建立 次要索引 來透過不同的金鑰查詢你的資料,或維護物件之間的關聯時,你會想要建立一組函式,放在你的 DTO 上方,以確保傳遞的資料是有效的(而不仅仅是正確的型別)。

從我們上面的商業物件,Post 物件夠複雜,很可能需要一小段程式碼來儲存和擷取物件的執行個體。以下是兩個函式的範例,它們包裝了底層的 Deno KV API,並為 Post 介面傳回強型別的物件執行個體。

值得注意的是,我們需要儲存 Author 物件的識別碼,以便我們稍後可以從 KV 擷取作者資訊。

import { Author, Post } from "./model.ts";

const kv = await Deno.openKv();

interface RawPost extends Post {
authorUsername: string;
}

export async function savePost(p: Post): Promise<Post> {
const postData: RawPost = Object.assign({}, p, {
authorUsername: p.author.username,
});

await kv.set(["posts", p.slug], postData);
return p;
}

export async function getPost(slug: string): Promise<Post> {
const postResponse = await kv.get(["posts", slug]);
const rawPost = postResponse.value as RawPost;
const authorResponse = await kv.get(["authors", rawPost.authorUsername]);

const author = authorResponse.value as Author;
const post = Object.assign({}, postResponse.value, {
author,
}) as Post;

return post;
}

這個薄層使用 RawPost 介面,它延伸實際的 Post 介面,以包含一些額外的資料,用於參照另一個索引中的資料(關聯的 Author 物件)。

savePostgetPost 函式取代直接的 Deno KV getset 操作,以便它們可以適當地序列化和「補充」模型物件,並為我們提供適當的類型和關聯。