微服務(wù)是一個(gè)自包含的獨(dú)立單元,跟其他的微服務(wù)共同組成一個(gè)大型應(yīng)用。通過把應(yīng)用拆分成小單元,每個(gè)單元都能獨(dú)立部署和擴(kuò)展,也能由不同的團(tuán)隊(duì)用不同的編程語言開發(fā),還能獨(dú)立測(cè)試。
micro 是一個(gè)很小的(大約100行代碼)模塊,它讓我們用 Node.js 寫微服務(wù)變得輕松有趣。它很容易使用,而且非???。無論你之前是否用過 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”,無論請(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è)頁面,你會(huì)看到:

做點(diǎn)有用的
前面做的有點(diǎn)枯燥,我們來做點(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 核心庫的url 模塊(無需另外安裝)解析它。
引入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,用來保存所有訪問記錄:
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ì)看到:

這基本上是我在幾個(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ù)庫,只是放在內(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,它允許我們通過 promise 的方式使用level 。如果不太理解,不用擔(dān)心,很快你就能知道它是什么了!
先安裝這些模塊:
npm install level then-levelup
為了創(chuàng)建數(shù)據(jù)庫,我們先引入level,然后指定數(shù)據(jù)庫的存儲(chǔ)位置,存儲(chǔ)內(nèi)容為JSON格式。我們用 then-levelup 導(dǎo)出的方法 promisify包住這個(gè)數(shù)據(jù)庫,然后導(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ù)庫我們需要的兩個(gè)方法是,db.put(key, value) 用來保存數(shù)據(jù)(等效于visits[pathname] = x),db.get(key)用來獲取數(shù)據(jù)(等效于const x = visits[pathname])。
首先,我們想知道在數(shù)據(jù)庫里是否存在該路徑的訪問記錄。通過 db.get(pathname) 來實(shí)現(xiàn),并用await 關(guān)鍵字等待完成:
module.exports = async function (request, response) {
const { pathname } = url.parse(request.url)
const currentVisits = await db.get(pathname)
}
如果不加上
await,currentVisits就被賦值為一個(gè) Promise,函數(shù)會(huì)繼續(xù)執(zhí)行,我們也就得不到數(shù)據(jù)庫返回的值了——這不是我們想要的結(jié)果!
與之前相反,如果當(dāng)前沒有訪問記錄,db.get 會(huì)拋出一個(gè) “NotFoundError” 異常。我們要用 try/catch 塊來捕獲,并用 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)在,頁面的訪問記錄已經(jīng)持久化保存到 vists.db 文件里了,服務(wù)重啟也不受影響。試著重啟服務(wù),打開幾次 localhost:3000/foo ,然后再重啟服務(wù),再訪問同一個(gè)頁面。你會(huì)發(fā)現(xiàn)之前的訪問次數(shù)還在,盡管已經(jīng)重啟服務(wù)了。
恭喜你,在10分鐘內(nèi)就建立了一個(gè)頁面計(jì)數(shù)器! ??
這就是 Node.js 中小型的、集中的模塊的強(qiáng)大功能。無需折騰基礎(chǔ)組件,我們只要專注于應(yīng)用開發(fā)。