構(gòu)建你的第一個(gè) Node.js 微服務(wù)

微服務(wù)是一個(gè)自包含的獨(dú)立單元惑艇,跟其他的微服務(wù)共同組成一個(gè)大型應(yīng)用夷野。通過(guò)把應(yīng)用拆分成小單元懊蒸,每個(gè)單元都能獨(dú)立部署和擴(kuò)展,也能由不同的團(tuán)隊(duì)用不同的編程語(yǔ)言開發(fā)悯搔,還能獨(dú)立測(cè)試骑丸。

micro 是一個(gè)很小的(大約100行代碼)模塊,它讓我們用 Node.js 寫微服務(wù)變得輕松有趣鳖孤。它很容易使用者娱,而且非常快苏揣。無(wú)論你之前是否用過(guò) Node.js黄鳍,看完這篇文章你就能寫自己的微服務(wù)了!

上手準(zhǔn)備

上手操作僅需兩個(gè)小步驟平匈,首先需要安裝 micro:

npm install -g micro

這里選擇全局安裝是為了確保我們能使用micro 命令框沟。如果你知道如何使用 npm scripts,你可以隨意使用它們增炭。

第二步就是新建一個(gè)存放微服務(wù)的文件 index.js

touch index.js

初始步驟

這個(gè) index.js 文件需要導(dǎo)出一個(gè)函數(shù)忍燥, micro 會(huì)把連接的請(qǐng)求和響應(yīng)對(duì)象傳給它:

module.exports = function (request, response) {
  // 微服務(wù)邏輯代碼
}

我們用到 micro的最主要的方法是send,用它可以向客戶端發(fā)送響應(yīng)隙姿。我們先require 它梅垄,并發(fā)送一個(gè)簡(jiǎn)單的“Hello World”,無(wú)論請(qǐng)求是什么:

const { send } = require('micro')

module.exports = function (request, response) {
  send(response, 200, 'Hello World! ??')
}

send 第一個(gè)參數(shù)是要發(fā)送的響應(yīng)输玷,第二個(gè)參數(shù)是 HTTP 狀態(tài)碼队丝,第三個(gè)參數(shù)是響應(yīng)內(nèi)容(可以是JOSN)。

啟動(dòng)微服務(wù)只需要一個(gè)命令:

$ micro index.js

 Ready! Listening on http://0.0.0.0:3000

用瀏覽器打開這個(gè)頁(yè)面欲鹏,你會(huì)看到:


構(gòu)建你的第一個(gè) Node.js 微服務(wù)

做點(diǎn)有用的

前面做的有點(diǎn)枯燥机久,我們來(lái)做點(diǎn)有用的東西!我們想做個(gè)能記錄指定路徑被請(qǐng)求的次數(shù)的微服務(wù)赔嚎。也就是當(dāng) /foo 第一次被請(qǐng)求時(shí)膘盖,返回1胧弛,再一次請(qǐng)求時(shí)返回2,等等侠畔。

我們首先需要知道請(qǐng)求 URL 的pathname结缚。從request.url獲得 URL,然后用 Node.js 核心庫(kù)的url 模塊(無(wú)需另外安裝)解析它践图。

引入url模塊并用它從 URL 解析獲得 pathname

const { send } = require('micro')
const url = require('url')

module.exports = function (request, response) {
  const { pathname } = url.parse(request.url)
  console.log(pathname)
  send(response, 200, 'Hello World! ??')
}

重啟微服務(wù)(按 CTRL+C掺冠,然后再次輸入micro index.js)試試看。
請(qǐng)求localhost:3000/foo 會(huì)在控制臺(tái)輸出 /foo码党,請(qǐng)求localhost:3000/bar 輸出 /bar

有了 pathname斥黑,最后一步就是保存這個(gè)路徑被請(qǐng)求的次數(shù)了揖盘。
創(chuàng)建一個(gè)全局對(duì)象visits,用來(lái)保存所有訪問記錄:

const { send } = require('micro')
const url = require('url')

const visits = {}

module.exports = function (request, response) {
  const { pathname } = url.parse(request.url)
  send(response, 200, 'Hello World! ??')
}

每次請(qǐng)求到達(dá)的時(shí)候檢查visits[pathname]是否存在锌奴。如果存在兽狭,就把訪問次數(shù)遞增并返回結(jié)果給客戶端。否則就把它設(shè)置為1并把它返回給客戶端鹿蜀。

const { send } = require('micro')
const url = require('url')

const visits = {}

module.exports = function (request, response) {
  const { pathname } = url.parse(request.url)

  if (visits[pathname]) {
    visits[pathname] = visits[pathname] + 1
  } else {
    visits[pathname] = 1
  }

  send(response, 200, `This page has ${visits[pathname]} visits!`)
}

再次重啟服務(wù)箕慧,在瀏覽器打開localhost:3000/foo并刷新幾次。你會(huì)看到:

記錄訪問次數(shù)

這基本上是我在幾個(gè)小時(shí)內(nèi)構(gòu)建 micro-analytics 的方法茴恰。核心概念是一樣的颠焦,只是多了一些功能。一旦弄清楚在做的東西往枣,實(shí)現(xiàn)的代碼還是挺簡(jiǎn)單的伐庭。

持久化數(shù)據(jù)

你可能也注意到了,每當(dāng)我們重啟服務(wù)的時(shí)候分冈,數(shù)據(jù)都被刪除了圾另。我們并沒有把訪問數(shù)據(jù)保存到數(shù)據(jù)庫(kù),只是放在內(nèi)存里雕沉。讓我們解決這個(gè)問題集乔!

我們將使用 level 持久化數(shù)據(jù),它是一個(gè)基于文件的鍵值存儲(chǔ)器坡椒。 micro內(nèi)置對(duì) async/await 的支持扰路,這讓異步代碼更加優(yōu)美。問題在于肠牲, level 是基于回調(diào)函數(shù)而不是 Promise的幼衰。??

像往常一樣,npm 里有我們需要的模塊缀雳。 Forbes Lindesay 開發(fā)了 then-levelup渡嚣,它允許我們通過(guò) promise 的方式使用level 。如果不太理解,不用擔(dān)心识椰,很快你就能知道它是什么了绝葡!

先安裝這些模塊:

npm install level then-levelup

為了創(chuàng)建數(shù)據(jù)庫(kù),我們先引入level腹鹉,然后指定數(shù)據(jù)庫(kù)的存儲(chǔ)位置藏畅,存儲(chǔ)內(nèi)容為JSON格式。我們用 then-levelup 導(dǎo)出的方法 promisify包住這個(gè)數(shù)據(jù)庫(kù)功咒,然后導(dǎo)出一個(gè)async 函數(shù)而不是普通函數(shù)愉阎,以便能使用 await 關(guān)鍵字:

const { send } = require('micro')
const url = require('url')
const level = require('level')
const promisify = require('then-levelup')

const db = promisify(level('visits.db', {
  valueEncoding: 'json'
}))

module.exports = async function (request, response) {
  /* ... */
}

對(duì)于數(shù)據(jù)庫(kù)我們需要的兩個(gè)方法是,db.put(key, value) 用來(lái)保存數(shù)據(jù)(等效于visits[pathname] = x)力奋,db.get(key)用來(lái)獲取數(shù)據(jù)(等效于const x = visits[pathname])榜旦。

首先,我們想知道在數(shù)據(jù)庫(kù)里是否存在該路徑的訪問記錄景殷。通過(guò) db.get(pathname) 來(lái)實(shí)現(xiàn)溅呢,并用await 關(guān)鍵字等待完成:

module.exports = async function (request, response) {
  const { pathname } = url.parse(request.url)

  const currentVisits = await db.get(pathname)
}

如果不加上 awaitcurrentVisits 就被賦值為一個(gè) Promise猿挚,函數(shù)會(huì)繼續(xù)執(zhí)行咐旧,我們也就得不到數(shù)據(jù)庫(kù)返回的值了——這不是我們想要的結(jié)果!

與之前相反绩蜻,如果當(dāng)前沒有訪問記錄铣墨,db.get 會(huì)拋出一個(gè) “NotFoundError” 異常。我們要用 try/catch 塊來(lái)捕獲辜羊,并用 db.put 設(shè)置初始值為1

/* ... */

module.exports = async function (request, response) {
  const { pathname } = url.parse(request.url)

  try {
    const currentVisits = await db.get(pathname)
  } catch (error) {
    if (error.notFound) await db.put(pathname, 1)
  }
}

繼續(xù)完成它踏兜,如果已經(jīng)有訪問記錄,我們需要增加訪問次數(shù)并發(fā)送響應(yīng):

/* ... */

module.exports = async function (request, response) {
  const { pathname } = url.parse(request.url)

  try {
    const currentVisits = await db.get(pathname)
    await db.put(pathname, currentVisits + 1)
  } catch (error) {
    if (error.notFound) await db.put(pathname, 1)
  }

  send(response, 200, `This page has ${await db.get(pathname)} visits!`)
}

這就是我們要做的所有事情八秃!現(xiàn)在碱妆,頁(yè)面的訪問記錄已經(jīng)持久化保存到 vists.db 文件里了,服務(wù)重啟也不受影響昔驱。試著重啟服務(wù)疹尾,打開幾次 localhost:3000/foo ,然后再重啟服務(wù)骤肛,再訪問同一個(gè)頁(yè)面纳本。你會(huì)發(fā)現(xiàn)之前的訪問次數(shù)還在,盡管已經(jīng)重啟服務(wù)了腋颠。

恭喜你繁成,在10分鐘內(nèi)就建立了一個(gè)頁(yè)面計(jì)數(shù)器! ??

這就是 Node.js 中小型的淑玫、集中的模塊的強(qiáng)大功能巾腕。無(wú)需折騰基礎(chǔ)組件面睛,我們只要專注于應(yīng)用開發(fā)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尊搬,一起剝皮案震驚了整個(gè)濱河市叁鉴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佛寿,老刑警劉巖幌墓,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異冀泻,居然都是意外死亡常侣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門腔长,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)袭祟,“玉大人,你說(shuō)我怎么就攤上這事捞附。” “怎么了您没?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵鸟召,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我氨鹏,道長(zhǎng)欧募,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任仆抵,我火速辦了婚禮跟继,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘镣丑。我一直安慰自己舔糖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布莺匠。 她就那樣靜靜地躺著金吗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趣竣。 梳的紋絲不亂的頭發(fā)上摇庙,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音遥缕,去河邊找鬼卫袒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛单匣,可吹牛的內(nèi)容都是我干的夕凝。 我是一名探鬼主播宝穗,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼迹冤!你這毒婦竟也來(lái)了讽营?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泡徙,失蹤者是張志新(化名)和其女友劉穎橱鹏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堪藐,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莉兰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了礁竞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糖荒。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖模捂,靈堂內(nèi)的尸體忽然破棺而出捶朵,到底是詐尸還是另有隱情,我是刑警寧澤狂男,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布综看,位于F島的核電站,受9級(jí)特大地震影響岖食,放射性物質(zhì)發(fā)生泄漏红碑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一泡垃、第九天 我趴在偏房一處隱蔽的房頂上張望析珊。 院中可真熱鬧,春花似錦蔑穴、人聲如沸忠寻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锡溯。三九已至,卻和暖如春哑姚,著一層夾襖步出監(jiān)牢的瞬間祭饭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工叙量, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倡蝙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓绞佩,卻偏偏與公主長(zhǎng)得像寺鸥,于是被迫代替她去往敵國(guó)和親猪钮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容