使用Liquid實現簡單的數據交換

在平時的開發工作中,接口對接是一件無可避免的事情。雖然在“前後端分離”的大趨勢下,後端的角色逐漸轉換爲數據接口的提供者,然而在實際的應用場景中,我們面對的往往是各種不同的“數據”,譬如企業應用中普遍使用的企業服務總線(ESB),這類服務要求服務接入者必須使用WebService來作爲數據交換格式;再譬如電子數據交換(EDI)這種特定行業中使用的數據交換格式,從可讀性上甚至還不如基於XML的WebService…而更爲普遍的則可能是需要使用Word、Excel、CSV來作爲數據交換的媒介。順着這個思路繼續發散下去,進入我們失業的或許還有各種數據庫,譬如MySQL和MongoDB;各種大數據平臺,譬如Hadoop和Spark;各種消息隊列,譬如RabbitMQ和Kafka等等。

注意到,這裏反覆提到的一個概念是數據交換(Data Switching),它是指在多個數據終端設備間,爲任意兩個終端設備建立數據通信臨時互聯通路的過程。自從阿里提出“中臺”的概念以來,越來越多的公司開始跟風“中臺”概念,並隨之衍生出譬如組織中臺、數據中臺、業務中臺、內容中臺等等的概念。今天這篇博客,我並不打算故弄玄虛地扯這些概念,我的落腳點是接口級別的數據交換,主要通過Liquid這款模板引擎來實現。它對應我在這篇博客開頭提到的場景:一個對外提供RESful風格API的系統,如何快速地和一個WebService實現對接。總而言之,希望能對這篇博客對大家有所啓發吧!

關於Liquid

首先,我們來介紹Liquid,通過它的官方網站,我們應該它是一門模板語言。對於模板語言,我們應該是非常熟悉啦,JavaScript裏的HandlebarsEjs就是非常著名的模板語言。如大家所見,這個博客就是用Ejs模板渲染出來的。而到了三大前端框架並駕齊驅的時代,模版語法依然被保留了下來,比如Vue{% raw %}{{model.userName}}{% endraw %}標記常常用來做文本插值。所以,如果要認真追溯起來的話,也許這些框架都或多或少的收到了Liquid的影響,因爲它的基本語法如下:

//使用page實例的title屬性插值
{{ page.title}}

假設page是一個對象,它的title屬性值爲:Introduction,此時,渲染後的結果即爲:Introduction。是不是感覺非常簡單呢? 我們繼續往下看。除了基本的“插值”語法以外,我們可以用{% raw %}{% tag %}{% endraw %}這種結構(Liquid稱之爲Tag):

//聲稱變量author並賦值
{% sssign author = '貓先森' %}
//條件語句
{% if author == '貓先森' %}
帥哥,你好
{% endif %}
//循環語句
{% for post in posts %}
{{post.date}}-{{post.title}}
{% endfor %}

這裏僅僅展示了一部分Liquid的特性,但對於我們瞭解一門“語言”已經足夠了,因爲對於一門編程語言來說,只要學會順序、條件和循環三種結構足矣。言下之意呢,像常規elseelseifbreakcontinueLiquid都是支持的,這樣子是不是更有編程語言的感覺了呢?除此之外,它還支持像tablerow這樣的Tag,主要用來渲染HTML裏的表格。

也許有人想說,這玩意兒有什麼用呢?抱歉啊,這玩意兒還真有用。像發送郵件、發送短信這種一般都需要寫個字符串模板的,簡單的大家可以用String.Format()或者$來搞定,可一旦遇上循環的場景,這種基於字符串替換的方式就有點力不從心了。不開玩笑地說,在代碼裏用StringBuilder拼接HTML的方式,實在是太傻逼了。如果用Liquid寫可能就是:

親愛的{{ model.UserID }}:
   您好!您有以下設備即將超過校驗有效期,請及時採取有效行動。
   {% for equipment in model.Equipments %}
   {{ equipment.EquipmentID }}
   {% endfor %}
   
{{ model.SendBy }}

顯然,這個代碼比拼接字符串要優雅很多。博主曾經在一個前端頁面看到過大量的HTML拼接操作,果然是jQuery操作DOM一時爽,jQuery操作DOM一直爽,可明明前端就有HandlebarsEjs這樣的模板語言。最近一位同事寫前端頁面的經歷不由得讓我感慨,眼睛覺得簡單的事情,爲什麼總是要求手去做呢?直接操作DOM帶來的弊端就是,業務邏輯永遠和DOM糾纏在一起,那些沒有人敢改的JavaScript代碼,那些未經模塊化全局引入的JavaScript代碼,雖然馬上就要2020年了,寫下這些句子的時候還是感到魔幻,可能這就是所謂的魔幻現實主義吧。

OK, 我們把思緒拉回到Liquid。除了使用各種Tag實現流程控制以外,Liquid中還提供了過濾器(Filter)的概念,過濾器主要是配合{% raw %}{{ variable | filter }}{% endraw %}語法來使用的。比如說,數據層返回了一個負數,而展示層希望展示正數,在不確定這個數值是否被別人使用的情況下,貿然去修改數據層的返回值是件危險的事情。此時,我們可以:

//對綁定的變量或者值取絕對值
{{ -17 | abs}}
//保留小數位
{{ 183.357 | round: 2 }}
//日期/時間格式
{{ article.created_date | data: %b %d, %Y}}

類似小數點位數、日期/時間格式等問題,均可以在Liquid中找到相應的過濾器。需要說明的是,Liquid使用Ruby進行開發的。也許在讀到這篇博客前,大家都沒有聽說過Liquid,那麼至少聽說過Jekyll這個著名的靜態博客生成器吧。實際上,在我寫這篇博客的時候,我剛剛瞭解到一件事情,Jekyll就是基於Liquid而開發的,想到當初搭建這個博客時被Ruby勸退的回憶,我大概想不到有一天會再次接觸它吧,不得不說,人生還真是奇妙啊!

一個簡單的想法

好了,關於Liquid的介紹我們先瞭解到這裏。寫到這裏,再回頭去看我們一開始的問題,即:怎麼把上游的數據(Model)轉化爲下游的數據(Template)。這裏暫且拋開它到底是XML、JSON還是EDI這種細節性的問題,我想我們大概會有一個簡單的想法,如果把需要傳輸給對方的接口報文做成模板,然後通過Liquid語法完成數據的綁定,那麼數據映射這一層的工作就可以減輕不少,畢竟寫A.XXX=B.XXX這種賦值語句是沒什麼前途的啦,而AutoMapper則需要提前寫好Map並註冊,經過一番權衡,我們來驗證一下我們的想法吧!

這段時間一直在和金蝶K3Cloud接口做對接,坦白說我覺得金蝶的接口設計得非常糟糕,從它那個奇葩的FNumber字段就能看出來,而且它試圖用一個接口做完所有事情的做法恕我不敢苟同,在我看來它違反了單一職責原則。因爲要對接的接口數量多、字段多,我首先根據字段對應關係製作了一份Liquid模板,並根據業務上的需要,用主表(Main) + 明細表(Details)的方式來定義數據,這意味着我接下來只需要根據業務實現不同的數據源即可:

基於Liquid的JSON報文模板

好了,現在我們使用Liquid的.NET版本DotLiquid來負責模板的解析和渲染,這個庫可以直接通過Nuget安裝,可以注意到這個代碼非常的簡單:

string RenderTpl(string filePath, dynamic model)
{
  var content = File.ReadAllText(filePath);
  var template = Template.Parse(content);
  var output = template.Render(Hash.FromAnonymousObject(model));
  return output;
}

實際上渲染後的文本就是對方需要的接口報文了,此時,該怎麼樣就怎麼樣處理,只需要把這個報文發送給對方就可以了。唯一需要花時間的就是對字段、寫綁定,相比寫實體類的方式效率要高更多。這種方式的話,我個人覺得更適合分工合作,如果需要數據加字段,那在數據層(Model)裏增加就好了,而像改字段映射關係、字段默認值都可以由別人來完成。我一直相信,開發並不是幫別人做越多事情越好,而是可以提供一種能力讓別人去做更多的事情,這就是我們常常聽到的“賦能”。繼續延伸下去的話,傳統的MVC其實和Liquid是一個道理,都是根據數據去生成視圖,無非是我們這裏的"視圖"變成了數據報文。

本文小結

通過日常工作中的接口對接這一典型場景,我們引出了“數據交換”的概念,而最低層級的數據交換實際上是接口報文的交換。爲此,我們介紹了Liquid模板引擎,它提供的語法可以讓我們完成一系列的綁定,順着這個思路,博主爲大家展示了這種想法的可行性。Liquid是一個非常成熟的模板引擎,無論是編寫郵件、短信的文本模板,還是輕量級的文本表達式實現,都是一個非常不錯的選擇。即使是做一個ApiCaller,一定要做一個有頭腦的ApiCaller。好了,以上就是這篇博客的全部內容啦,歡迎大家留言,謝謝大家。

發佈了247 篇原創文章 · 獲贊 913 · 訪問量 206萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章