Solid:一套開發者友好的關聯資料開發框架

買賣虛擬貨幣
雖然語義網社羣正在領域內努力奮鬥,但我們仍未能吸引那些一線開發者:前端開發人員。為什麼這具有諷刺意味?因為我們這些語義網愛好者其實並沒法專注於一線開發; 我們的技術正在為專業的後端系統提供生產力,而不是去實現直接面向普通使用者的應用程式。在分散式 Web 應用程式的生態 —— SoLiD 中,關聯資料和語義網技術發揮著至關重要的作用。在過去的一年裡,我堅定地致力於為 SoLiD 做貢獻,我意識到設計一個開發者友好的開發體驗對於它的成功至關重要。在與前端開發者交流後,我建立了幾個 JavaScript 庫,這些庫可以幫助開發者輕鬆地與複雜的關聯資料進行互動,而無需瞭解 RDF。 這篇文章介紹了 SoLiD 的 React 元件以及 LDFlex 查詢語言,以及在它們的設計過程中吸取的經驗教訓。在 2000 年左右,一種新的網路分工以越來越複雜和專業化的趨勢悄然出現:homo developerensis frontendicus,通常被稱為前端開發者。他們通常有兩個與其他開發者不同的地方:1. 與喜歡與機器互動的後端開發人員(backendicus)相比,前端開發人員傾向於與普通人進行更多互動(也就是進行人機互動設計)。雖然許多後端開發人員會用他們對 Perl 的深刻理解挑戰你,並嘗試用 Java Spring XML 配置檔案給潛在的合作伙伴留下深刻印象,但前端開發者常常不認為後端開發人員是偉大的開發者,因為他們的工作比較無趣。 2. 前端開發者構建人們想要和喜歡的東西,後端開發人員使前端開發者能夠輕鬆完成這項工作。隨著分散式系統的出現,語義網技術可以在解決資料互操作性方面發揮關鍵作用。 特別是,表示知識的關聯資料非常適合在分散式網路中儲存和程式設計。但是,我們的技術棧其實不是特別讓開發者適應。在語義網和 RDF 社羣建立之後,前端開發者在社羣中展現出了超出常人的活躍度,但我們語義網愛好者犯了很大的錯誤,因為我們忽略了前端開發者的存在 —— 即使他們的活躍度遠遠超過我們。我們依然一直堅持 Java 的正規化,如 RDF / XML 和 Turtle,並拼命地試圖說服前端世界是他們錯了,我們是對的,他們應該關注我們。事實證明確實是我們錯了。

你猜怎麼著?如果我們不遵循前端開發的規則,那麼我們將成為過時的人。我們的錯誤在於我們沒有關注 Web 的發展方向。如果我們不改變方向,使 Web 重新去中心化的使命可能最終缺乏足夠的資料整合而導致失敗。

今年早些時候,我在 GraphQL Day 非常清楚地看到了前端開發者的重要性。不知不覺地,這種查詢語言已經成功地聚集起塞滿一個房間的人,這一天裡有很多有趣的人在使用 GraphQL 查詢各種資料,並在此基礎上構建漂亮的應用程式。GraphQL 被(錯誤地)稱讚為 REST 架構的替代品,他們大聲斥責其他解決方案的複雜性,例如語義網的查詢語言 SPARQL。而具有諷刺意味的是,我瞭解了未來將會大大複雜化並重塑 GraphQL 的計劃,這是為了讓 GraphQL 的能力增強到能夠涵蓋 SPARQL 多年來積累的基礎技術的地步。

但是,因此而責怪 GraphQL 或前端開發社羣是非常錯誤的。多年來,我們已經使用 Linked Data,RDF 和 SPARQL 獲得了金牌,而我們始終未能覆蓋那些能夠將其帶給終端使用者的前端開發者。這是我們的失敗,和其他人無關。

我堅信我們應該將語義網帶到 Web 上。 我們需要為前端開發人員提供工具和庫。這就是為什麼自從加入語義網社羣以來,我花了很多時間從頭開始為瀏覽器建立 JavaScript 庫,只有這樣我們才可以在 Web 上實現語義網。如果我不堅持這樣做,我會更快地推進,但是我建造的東西永遠不會到達終端使用者。

到目前為止,我還沒有為前端開發人員撰寫過文章介紹這個:我的庫提供了一個關鍵資料的底層結構。它們暴露了 RDF 三元組 —— 這是一個獨特的技術分支,而不是開發人員熟悉的 JSON 樹。大多數開發人員不想要 RDF,他們是對的。我們應該為分散式 Web 應用程式提供良好的開發體驗,這些開發工具應該遮蔽 RDF 的底層複雜性但能提供關聯資料的優勢。

為什麼要使用關聯資料(Linked Data)?

去中心化 Web 應用擁有多個後端

第一個關鍵的問題是分散式 Web 應用是否需要關聯資料。為什麼不像其他 Web API 一樣,伺服器傳送客戶端可以輕鬆解析的自定義 JSON?SoLiD 中的分散式想法是應用程式沒有自己的資料儲存,而是將資料儲存在使用者選擇的位置。因此,應用程式需要更加靈活,以便與不同的後端相容。多個應用程式可能同時使用多個後端。例如,社交媒體應用中的資料可以儲存在不同的位置。

舉個例子,如果你想對一篇文章點贊,那麼需要以下兩點:

· 你需要一種方法將你的「點贊」和文章做關聯。
· 你的喜歡需要具有普遍含義(具有共識的語義),因此不同的應用可以使用它。

目前的各個 Web API 使用的自定義 JSON 解決這些問題並不容易。

關聯資料使 Web 應用獨立於特定的後端

關聯資料(Linked Data)使用連結解決這個問題。在關聯資料中,“點贊”和“文章”都將有一個連結,比如:

· 文章可能是:https://articles.org/posts/1234
· 點贊可能是:https://you.example/likes/2019/02#like-on-post-1234

所以“點贊”和“文章”的連線方式是:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "actor": "https://you.example/profile#you",
  "type": "Like",
  "object": "https://articles.org/posts/1234", // 文章地址
  "published": "2018-12-28T10:00:00Z",
  "id": "#like-on-post-1234" // 點贊
}

由於像 type, object, actor 類似的單詞在不同的應用中有不同的含義,不同的開發者可能對於它們的涵義會產生不同的理解,為避免這種歧義,關聯資料將始終使用 連結(links) 來建立一個有共識通用含義。上面的程式碼片段中有一個 @context 欄位,這個欄位使得 actor 指代 https://www.w3.org/ns/activitystreams#actor。這也叫 JSON-LD (JSON Linked Data) 語境/上下文(Context)。

複雜嗎?但透過抽象層進行抽象後,你就不用瞭解上述的任何內容了!

下面兩章會詳細探討 React 和關聯資料表示式的使用方法,主要針對 JavaScript 開發者,如果你不感興趣可以跳過。

SoLiD React 元件

選擇一個語言和框架

在設計開發者友好的開發框架時,第一個問題是要定位的語言和框架,JavaScript 和 React 在 2018 年的 JavaScript 生態中成為明顯的贏家。對於一般的框架我並不是特別興奮,因為它們的受歡迎程度上升和下降得如此之快,以至於沒有一個被認為是安全的賭注(你還記得 jQuery 嗎)。儘管從未學過 React,但我發現很容易微調程式碼來相容 React,所以我感到很好奇並開始探索。然後我開始編寫自己的元件來簡化一些重複的 SoLiD 身份認證程式碼。當我發現高階元件(HoC)時,我完全迷上了構建 React 元件庫的禪道。

然而,我和 SoLiD 團隊都沒有和 React 完全綁死,我們還會留意其他當前和即將到來的框架。重要的是,許多經驗教訓(以及一些在開發過程中生成的庫)可以直接應用於其他框架。

可以在 GitHub 和 NPM 上找到 SoLiD 的 React 元件。

登入和退出

SoLiD 的第一類 React 元件提供身份認證功能。雖然不是特定於關聯資料,但身份認證對於在分散式網路中獲取私有資料至關重要。與 Facebook 和其他社交網路相比,SoLiD 網路中不使用實體按鈕登入。相反,人們使用自己的資料視窗登入,該資料視窗可以駐留在 Web 上的任何位置。因此,一致的登入體驗至關重要,但我注意到,連線我們現有的身份認證庫可以輕鬆地使用十幾行程式碼完成。透過將這些行減少到單個元件,開發人員可以重用經過良好測試的解決方案。以下是結果程式碼的片段:

<LoggedOut>
  <p><LoginButton popup="popup.html" /></p>
  <p>你已退出,這是一個不受保護的區域</p>
</LoggedOut>
<LoggedIn>
  <p>你已登入,可以看到受保護的特殊內容</p>
</LoggedIn>

有趣的是,SoLiD React 庫不僅僅是提供元件:它也使開發人員能夠輕鬆構建自己的 SoLiD 元件。 例如,上面的 <LoggedIn> 元件有一個簡單的實現:它不是必須自己呼叫身份認證庫,而是包含在 withWebId Helper 中。 此幫助程式將 webID 屬性傳遞給 <LoggedIn> 元件,該元件包含已登入使用者的標識。所有 <LoggedIn> 需要做的是檢查是否已設定其 webID 屬性,並且僅在這種情況下,呈現其內容。開發人員構建自己的涉及身份認證的 SoLiD 元件可以簡單地重用 withWebId 高階元件,而不必擔心它是如何工作的。

展示關聯資料

第二類 React 元件提供了硬核的功能:輕鬆訪問關聯資料。在以前,即使是非常簡單的任務,例如顯示登入使用者的名字,也需要幾行很不直觀的程式碼,並要求開發人員理解 RDF 的細節,這是有難度的。

在 React 中,用一個元件代替了這些有複雜度的程式碼:

<p>
  Welcome, <Value src="user.name" />
</p>

<Value> 元件顯示透過 src 屬性標識的一段 Linked Data 的值。與身份認證一樣,這是透過名為 evaluateExpressions 的高階元件實現的,這樣開發人員可以輕鬆建立自己的 Linked Data 元件。你需要做的就是使用 evaluateExpressions 包裝你的元件,並指出哪些屬性可以包含Linked Data 表示式(在本例中為 src)。然後將這些表示式計算為值,並將這些值傳遞給你的元件。

比如,假如我們定義了一個 <Span> 元件:

const Span = evaluateExpressions(({ src }) => (src ? <span>{src}</span> : <em>pending</em>));

那麼我們可以傳遞 src 屬性給他:

<p>
  Your first name is <Span src="user.firstName" />.
</p>

這個 src 屬性將被 evaluateExpressions 轉換為實際值,這樣渲染的值將變為:

<p>Your first name is Ruben.</p>

該庫包含一些非常方便的元件,可以透過各種方式顯示關聯資料:

<LoggedIn>
  <p>
    Welcome, <Value src="user.firstName" />
  </p>
  <Image src="user.image" defaultSrc="profile.svg" />
  <ul>
    <li>
      <Link href="user.inbox">Your inbox</Link>
    </li>
    <li>
      <Link href="user.homepage">Your homepage</Link>
    </li>
  </ul>
  <h2>Your friends</h2>
  <List src="user.friends.firstName" />
</LoggedIn>

如果您將上述程式碼與使用關聯資料的基於 RDF 和三元組的方式進行比較,你會注意到它簡化了很多事情。著不僅適用於前端開發人員,也適用於想要構建關聯資料 Web 應用的所有人。例如,考慮使用 jQuery 和 RDFlib.js 或使用 React 元件實現的 SoLiD 個人資料檢視器。前者需要理解 RDF 和本體,而後者僅需理解 React 和 Linked Data 表示式。此外,React 實現的身份認證和資料元件經過嚴格測試,因此生成的應用程式具有更強的質量保證。

比較一下,這是以前繁雜的程式碼:

const store = $RDF.graph();
const fetcher = new $RDF.Fetcher(store);
const fullName = store.any($RDF.sym(user), FOAF('name'));
$('#fullName').text(fullName && fullName.value);

我想,以這種方式書寫:

<Value src="user.name" />

絕對更有趣、更健壯。如果一件事複雜度很高,那麼人們很可能本能的抗拒。因此,這將導致 SoLiD 永遠不會和終端普通使用者產生聯絡,或者在最壞的情況下,根本不會有 SoLiD 應用。這更加突出了設計開發者友好的關聯資料開發體驗是多麼重要。

LDFlex:透過 JavaScript 查詢 Web 網路

簡單資料需要簡單的表示式

你可能已經注意到,上面 React 元件易於使用的地方是它可以方便地檢索互聯資料。使用一種名為 LDFlex 的查詢語言 —— 我為此建立了該語言。LDFlex 是 JavaScript 的領域專用語言(DSL),這意味著它的所有表示式最終都會被轉化為 JavaScript 物件。

LDFlex 是我提出的一種解決方案,用於滿足開發者在構建應用時快速獲取資料的需求。比如獲取使用者名稱或主頁等內容時,開發者無需再寫很多行程式碼,也不必使用硬編碼一堆底層操作。LDFlex 透過簡潔的表示式滿足這些需求,如果它透過瀏覽器中的 solid.data 暴露到全域性,就可以這麼做:

const name = await solid.data.user.firstName;
const email = await solid.data.user.email;
for await (const friend of solid.data.user.friends.firstName)
  console.log(friend);

上面幾行程式碼內部到底做了什麼?雖然它看起來像遍歷本地物件,但每次我們等待 LDFlex 表示式時,我們實際上都在查詢 Web。以下是 LDFlex 表示式 solid.data.user.friends.firstName 背後發生的事情:

· 獲取當前使用者的 WebID URL。
· 將 friends 和 firstName 解析為其唯一識別符號。
· 將表示式轉化成一個 SPARQL 查詢(例子)。
· 透過 http 獲取根節點的文件(在本例中為使用者的 WebID)。
· 對文件執行 SPARQL 查詢並返回結果。

每當需要獲取資料,這些步驟(或其變體)以前都需要開發者去親自實現。雖然可以把這些步驟抽象成函式,但是目前大部分開發者已經習慣了直接從物件中取資料。而且這樣的程式碼長度還比把 GraphQL 查詢注入到 React 元件要短得多;事實上,表示式可以簡單地寫為內聯屬性。

除了使用者資料,你還可以在 Web 上查詢任何互聯資料資源:

solid.data['https://ruben.verborgh.org/profile/#me'].firstName;
solid.data['https://ruben.verborgh.org/profile/#me'].homepage;
solid.data['https://ruben.verborgh.org/profile/#me'].friends.firstName;
solid.data['https://ruben.verborgh.org/profile/#me'].blog.schema_blogPost.label;

這些表示式可以單獨使用,也可以作為SoLiD React 元件的 src 屬性中的值使用(為簡潔起見,省略了 solid.data)。這些表示式不光只能用在 React 裡。例如,如果你想用 Angular 或 Vue.js 構建,LDFlex 也會派上用場。

讓“開發體驗”好一點

我見過的幾個比較舊的庫,將 Linked Data 資源進行特定的物件導向包裝。你給他們一個檔案的 url,他們會為你提供一個人物,照片或任何其他特定領域概念的 JSON 物件。這種方法有幾個缺點:

1. 這些庫只能用於特定一類事物。如果你正在處理不同型別的資料,則無法使用它們。這很不對勁,因為互聯資料可以為任何東西建模。
2. 他們假設物件具有一組特定的屬性。這是一個最大的限制,因為互聯資料中的資料結構是任意的。
3. 他們透過將互聯資料扁平化為本地物件來刪除連結。但是,該物件不可能包含所有資料,因為關聯資料遍佈整個 Web。
4. 換句話說,透過將互聯資料降級為普通的 json 物件,我們失去了互聯資料的優點和靈活性,並且只繼承了缺點。發生這種情況是因為 JSON 物件是樹,而 Linked Data 是圖(Graph)。因此,關聯資料的純物件導向的抽象在設計上就是失敗的。

在設計 LDFlex 時,我一直在尋找一種抽象方式,能夠釋放互聯資料的力量和「感覺」,同時仍然讓開發者熟悉。這就是為什麼 LDFlex 表示式感覺像本地 JSON 物件,而實際上不是。仔細體會下面這種形式的表示式:

solid.data['https://ruben.verborgh.org/profile/#me'].label;

你可以使用任何其他互聯資料資源替換我的 WebID 網址,它仍然有效。因此 solid.data 構成一個具有無限屬性的物件,這更接近於互聯資料的真實本質。

當我們使用 JavaScript await關鍵字時,會發生從本地表示式到遠端資料來源的神奇切換:

//執行到下面這一行還什麼也沒做
const expression = solid.data.user.friends.name;
// ...但下面這行將從Web獲取資料
const name = await expression;

實現原理:JavaScript Proxy 和 JSON-LD

LDFlex 透過 JavaScript 代理物件工作,它提供攔截任意屬性的機制。使用 Proxy,我們可以確保任意複雜的路徑(如 my.random.path.expression)會被解析為有意義的值,即使 my 物件實際上沒有任何這些屬性。

回想一下,對於 Linked Data,術語有全球範圍的共識,因此它們可以跨越不同的後端而依然具有相同的含義。因此,LDFlex 的核心任務是將簡單的術語翻譯成具有共識的 URL 術語。例如,solid.data 上的路徑 user.friends.firstName 將透過以下方式解析:

1. user 變為:https://you.example/profile#you (當前使用者的 WebID)
2. friends 變成:http://xmlns.com/foaf/0.1/knows
3. firstName 成為:http://xmlns.com/foaf/0.1/givenName

至關重要的是,這些轉換並沒有硬編碼到 LDFlex 本身。從術語到 URL 的轉換可以透過 JSON-LD context 自由配置。因此,LDFlex 將這種使用 @context 標記 JSON 物件的機制應用於 Web 上無限的互聯資料。這種靈活性是透過多個庫實現的:

LDFlex 核心庫包含解析和查詢機制,但是並沒有具體實現。它知道如何解析路徑並生成 SPARQL 查詢,但你仍然需要使用 JSON-LD context 和查詢引擎進行配置。

Comunica for LDFlex 可以讓 Comunica 查詢引擎與 LDFlex 表示式一起使用。LDFlex 核心庫將給 Comunica 傳遞一個 SPARQL 查詢以供執行。

LDFlex for SoLiD 是 LDFlex 的一套配置,它提供 user object 和包含 SoLiD 專用術語的 JSON-LD context。因此,此配置定義了 user,friends 和 firstName 對 SoLiD 應用程式的含義。

它們合在一起,共同提供了一種使用體驗 —— 擁有無限屬性的本地物件,可以訪問整個 Web 上的互聯資料。LDFlex 核心庫實現了 await 和 for await 支援,就這點魔法。當 await 用於 LDFlex 表示式時,表示式被視為 Promise,內部實現就是呼叫 then 方法。LDFlex 將到查詢執行的第一個結果透過 then 方法向外傳遞。同樣,for await 被包裝成對 Symbol.asyncIterator 的方法呼叫。

請到 SoLiD LDFlex playground 探索 LDFlex。你可以在 SoLiD LDFlex 文件及其 JSON-LD context 中找到關於這種表示式的靈感。

未來就是對 Web 的讀寫

SoLiD 旨在透過互聯資料實現讀寫 Web。作為 Inrupt 公司的技術倡導者,我在最佳化開發體驗方面來支援這一目標。在我之前的部落格文章中,我指出了查詢在去中心化應用程式的重要性,因為應用程式不會(也不應該)知道如何檢索資料。 LDFlex 透過簡單的查詢表示式實現了這一點。雖然 LDFlex 不是所有查詢需求的解決方案,它覆蓋了許多場景下快速查詢的案例,寫起來比其他查詢語言更快。

在未來,我們肯定希望探索更強大的語言,如 GraphQL。我故意不提 SPARQL,因為 GraphQL 的開發者工具要好得多,將 GraphQL 通用化,而不是從頭開始構建 SPARQL 工具可能更有意義。

LDFlex 的下一個飛躍顯然是寫入:使新增或更改資料像讀取資料一樣容易。由於互聯資料的靈活性,寫入資料帶來了一些挑戰,例如在哪儲存資料,如何儲存資料。編寫互聯資料並不意味著編寫三元組,正如以下例子所示(你可以去親自嘗試一下!):

// 關注我
solid.data['https://ruben.verborgh.org/profile/#me'].follow();
// 為我的所有部落格文章點贊
solid.data['https://ruben.verborgh.org/profile/#me'].blog.schema_blogPost.like();
// 給 Facebook 拍磚
solid.data['https://facebook.com/'].dislike();

當點贊變得像呼叫like()方法一樣簡單時,這種呼叫方式對開發者友好,因此考慮優先提供。

發展去中心化應用的開發者體驗

在他 2018 年的技術回顧中,AndréStaltz 表示,雖然在治理和自由方面得分很高,但去中心化的專案仍然需要注重使用者體驗(UX)。在這篇部落格文章中,我認為去中心化開發的社羣應該首先關注開發者體驗(DX),因為前端開發人員是那些接觸終端使用者並塑造他們體驗的人。我們應該相信,他們創造卓越的使用者體驗的才能遠遠超過我們。

因此,問題在於我們怎樣才能最好地為前端開發人員鋪平道路。Dan Brickley 和 Libby Miller 在撰寫時寫道:

人們認為 RDF 是一個痛苦之源,因為它很複雜。事實上,情況更糟,RDF 非常簡單,但是它讓你可以處理非常複雜的現實資料和接觸那些令人痛苦的現實世界的問題。

但是,這並不意味著每個人都需要接觸到 RDF。除了去中心化程式設計的可怕複雜性之外,RDF 引入了一種不同的思維方式,我們不應該將它強加在前端開發人員身上。相反,我們應該釋放前端開發者們已有的大量知識,融入他們的框架和工具。

我們對前端開發者唯一的要求就是理解互聯資料。試圖將互聯資料包裝在簡單的 JSON 物件中會喪失去中心化生態系統的優勢。不要低估了前端開發人員走出舒適區域的意願。根據我的經驗,互聯資料激發了以前從未見過它的開發人員,因為他們突然可以訪問整個 Web 資料,而不只是從一個後端。它開闢了巨大的機會,因為他們不再依靠收穫資料來建立一些不錯的東西。

這些開發人員不會受到過去語義 Web 錯誤的影響,例如我們過度依賴 XML 和本體。我們不會嘗試重新啟動語義 Web,也不會再迫使開發人員進入我們的世界。這是關於互聯資料作為構建去中心化應用程式的解決方案。 Schema.org 的成功表明這些解決方案還有發展空間,我看到了在 SoLiD 世界中邁出第一步的年輕開發人員的熱情。這就是未來,而不是過去。

重要的是,React 和 LDFlex 庫不僅為前端開發人員提供了構建終端使用者應用程式的工具。它們還包含了一些基本元件用來建立新的庫和工具。我們的目標應該是培養這些生態系統,而不是自己編寫一切。

透過支援前端開發人員,我們開闢了一條創新高速路,使去中心化的 Web 能夠更快,更好地覆蓋終端使用者。而且,我們可以使用相同的庫和工具來加速我們自己的開發。因此,我們從一大批新人中獲利,同時能夠更好地利用我們自己的人才。幫助前端開發人員就是幫助使用者,並最終實現自我。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;