使用Liquid实现简单的数据交换
在平時的開發工作中,接口對接是一件無可避免的事情。雖然在“前后端分離”的大趨勢下,后端的角色逐漸轉換為數據接口的提供者,然而在實際的應用場景中,我們面對的往往是各種不同的“數據”,譬如企業應用中普遍使用的企業服務總線(ESB),這類服務要求服務接入者必須使用WebService來作為數據交換格式;再譬如電子數據交換(EDI)這種特定行業中使用的數據交換格式,從可讀性上甚至還不如基于XML的WebService…而更為普遍的則可能是需要使用Word、Excel、CSV來作為數據交換的媒介。順著這個思路繼續發散下去,進入我們失業的或許還有各種數據庫,譬如MySQL和MongoDB;各種大數據平臺,譬如Hadoop和Spark;各種消息隊列,譬如RabbitMQ和Kafka等等。
注意到,這里反復提到的一個概念是數據交換(Data Switching),它是指在多個數據終端設備間,為任意兩個終端設備建立數據通信臨時互聯通路的過程。自從阿里提出“中臺”的概念以來,越來越多的公司開始跟風“中臺”概念,并隨之衍生出譬如組織中臺、數據中臺、業務中臺、內容中臺等等的概念。今天這篇博客,我并不打算故弄玄虛地扯這些概念,我的落腳點是接口級別的數據交換,主要通過Liquid這款模板引擎來實現。它對應我在這篇博客開頭提到的場景:一個對外提供RESful風格API的系統,如何快速地和一個WebService實現對接。總而言之,希望能對這篇博客對大家有所啟發吧!
關于Liquid
首先,我們來介紹Liquid,通過它的官方網站,我們應該它是一門模板語言。對于模板語言,我們應該是非常熟悉啦,JavaScript里的Handlebars和Ejs就是非常著名的模板語言。如大家所見,這個博客就是用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的特性,但對于我們了解一門“語言”已經足夠了,因為對于一門編程語言來說,只要學會順序、條件和循環三種結構足矣。言下之意呢,像常規else、elseif、break和continue,Liquid都是支持的,這樣子是不是更有編程語言的感覺了呢?除此之外,它還支持像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一直爽,可明明前端就有Handlebars和Ejs這樣的模板語言。最近一位同事寫前端頁面的經歷不由得讓我感慨,眼睛覺得簡單的事情,為什么總是要求手去做呢?直接操作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的.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。好了,以上就是這篇博客的全部內容啦,歡迎大家留言,謝謝大家。
總結
以上是生活随笔為你收集整理的使用Liquid实现简单的数据交换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于vba word的一些用法
- 下一篇: 2008年全国计算机软考程序员考试大纲