Deno從零到架構級系列(一)——開篇

飛狐發表於2020-08-11

image

大家好,小弟飛狐。好久沒來思否了,再來帶來了的一定是乾貨。從Deno開始,飛狐帶來的絕對到目前為止前所未有的Deno系列。話不多說,用技術說話。

你學不動的 Deno 來了

還記得 Github 上那個讓人學不動的 Deno 麼?就在2020年5月13日,Deno1.0正式釋出。作為新晉網紅執行時,Deno真的會替代 Node 嗎?這個問題可以追溯到2018年,從Node之父 Ryan Dahl的演講說起,Ryan在演講中談及對Node有十大不滿之處,並且在演講的最後公佈了Deno專案。我在這裡只列三大新特性。

  • 首先,Deno 作為一個JavaScript/TypeScript 執行時,底層基於效能超高的 Rust 編寫,在效能和儲存安全上有先天的優勢。
  • 其次,Deno 擁有完整的標準庫,不再有 NPM 或 node_modules 資料夾,允許從任何地方匯入所需模組。
  • 另外,Deno 整合 TypeScript,不再像以前一樣藉助工具編譯,而是通過內部轉換。不過似乎又要剝離。

綜上所述,你會發現,Deno真的是青出於藍而勝於藍。是否替代,只是時間問題而已。我也看到很多人在做deno和node的效能比較,但在目前我認為做這兩者的效能比較完全沒有必要。

image

安裝

我們一開始甭管deno底層用的go還是rust,為啥要從go換成rust、或者是deno的技術架構是咋樣?這些目前都不要關心,我們就把deno當成一個新的執行時,只做執行時。從零到一,通過搭建一套腳手架,再慢慢去深入。Deno可以在Mac、Linux、Windows三大系統上執行。Deno也不需要其他依賴。通過如下方式安裝:

  • Shell (Mac, Linux): curl -fsSL https://deno.land/x/install/install.sh | sh
  • PowerShell (Windows): iwr https://deno.land/x/install/install.ps1 -useb | iex
  • Homebrew (Mac): brew install deno
  • Chocolatey (Windows): choco install deno
  • Scoop (Windows): scoop install deno

第一個例子也是來自官方,每一行我都加了解釋。如下:

// 建立檔案 /server.ts
// 不需要像node一樣去npm,這裡直接引入
import { serve } from "https://deno.land/std@0.63.0/http/server.ts";
// 構建服務,設定埠
const s = serve({ port: 8000 });
// 這裡是直接返回
for await (const req of s) {
  req.respond({ body: "Hello World\\n" });
}

整個程式碼非常簡單,語法也是ts,和node非常像。有node基礎的同學直接入手。

執行命令:deno run --allow-net ./server.ts,

然後在瀏覽器開啟http://localhost:8000,就可以看到hello world了,如下圖:

image

框架之選

眾所周知,node的框架比較成熟,國外的有nest.js、國內的有egg.js。而目前deno的生態其實並不成熟,框架也都是模仿其他框架,多的就不介紹了,這裡我給大家推薦兩個框架。如下:

  • oak
  • alosaur

推薦oak的原因很簡單,就是我們整個腳手架搭建都是基於oak的。oak模仿的是node的框架koa,從名字也可以看出來。 而推薦alosaur的原因就一點,可學性很強。有興趣可以去看這個框架的原始碼,非常多值得借鑑的地方。飛狐教大家搭建腳手架,雖然不用這個框架,但很多底層實現也是借鑑的這個框架,比如註解路由。好啦,框架就介紹到這裡啦,後面我們再慢慢深入。

話不多說,我們先來個oak例子:

// 引入oak框架
import { Application } from "https://deno.land/x/oak/mod.ts";
// 初始化
const app = new Application();
// 跟koa一樣,執行上下文
app.use((ctx) => { ctx.response.body = "Hello World!"; });
// 監聽埠
await app.listen({ port: 8000 });

在執行的時候,可能會報錯,如下:

image

是不是有點受挫。其實大可不必,這個問題是deno版本迭代時std版本未更新至最新引起的。

注意,deno是新玩意兒,有不少坑,官方也在頻繁迭代解決這些坑。 所以,理解萬歲。最簡單的解決辦法只需要把oak的版本升級成最新就好了,如下面的例子。

路由

路由部分oak跟koa不一樣的是,oak直接提供路由,只需引入即可。如下:

// 升級到oak的最新版本
import { Application, Router } from "https://deno.land/x/oak@v6.0.1/mod.ts";
// 這是官方的例子
const books = new Map<string, any>();
books.set("1", {
  id: "1",
  title: "聽飛狐聊deno",
  author: "飛狐",
});
// 建立路由
const router = new Router();
// 路由,和koa-router的用法一樣
router
  .get("/", (context) => {
    context.response.body = "Hello world!";
  })
  .get("/book", (context) => {
    context.response.body = Array.from(books.values());
  })
  .get("/book/:id", (context) => {
    if (context.params && context.params.id && books.has(context.params.id)) {
      context.response.body = books.get(context.params.id);
    }
  });
const app = new Application();
// 應用路由
app.use(router.routes());
// 允許路中介軟體引入
app.use(router.allowedMethods());
await app.listen({ port: 8000 });

切記,一定要用最新的版本。同樣的,輸入命令執行。如下圖(postman測試介面):
image

拆分路由

MVC模式大家都不陌生了,我們這裡僅僅做個簡單的拆分,把路由和控制層獨立出去。 建立一個controller資料夾,在該資料夾下建立一個bookController檔案,如下:

// 建立bookController.ts,
// 我們把資料先移到這來再說
const books = new Map<string, any>();

books.set("1", {
  id: "1",
  title: "聽飛狐聊deno",
  author: "飛狐",
});
// 這裡直接返回一個物件,把路由對映的方法也搬到這裡
export default {
  getbook: ((context: any) => {
    context.response.body = Array.from(books.values());
  }),
  getbookById: ((context: any) => {
    if (context.params && context.params.id && books.has(context.params.id)) {
      context.response.body = books.get(context.params.id);
    }
  })
}

再在根目錄下建立router.ts,程式碼如下:

import { Router } from 'https://deno.land/x/oak@v6.0.1/mod.ts';
// 引入控制層的檔案
import bookController from './controller/bookController.ts'

const router = new Router();
router
  .get("/", (context) => {
    context.response.body = "Hello world!";
  })
  .get("/book", bookController.getbook)
  .get("/book/:id", bookController.getbookById)

export default router

原來的入口檔案server.ts,就變得十分簡潔了,如下:

import { Application } from 'https://deno.land/x/oak@v6.0.1/mod.ts';
import router from './router.ts'

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: 8000 });

這樣就很簡潔了,也利於擴充套件。比如:

  • 後續用到業務邏輯的時候,我們可以再新增service層
  • 後續用到中介軟體的時候,我們可以再新增middleware層
  • 後續擴充套件異常處理等等

在這個基礎上,在後面的篇章裡我們再繼續深入。今天的內容其實已經完成了。這裡再介紹一下,為啥說這個系列是架構級思想呢?因為我們在寫程式碼的時候,就會基於一些特定的場景考慮,比如微服務等。就像golang裡的go-micro,會整合grpc、etcd、gin等等一樣。有興趣我也可以寫一套golang系列分享給大家,小弟我有太多想和大家夥兒分享的東東了,比如,TensorFlow.js、Julia量化交易等。呃~好像跑偏了,還是拉回來先送大家一個結尾彩蛋,為下一篇打基礎。

彩蛋(改造入口檔案)

從現在起,後面的部分我們要用類class的方式來寫程式碼了,先從入口檔案開始,改造如下:

// server.ts
import { Application } from 'https://deno.land/x/oak@v6.0.1/mod.ts';
import router from './router.ts'

const app = new Application();
class Server {
  constructor () {
    this.init()
  }

  async init () {
    app.use(router.routes());
    app.use(router.allowedMethods());
    this.listen()
  }

  async listen () {
    await app.listen({ port: 8000 });
  }
}

new Server()

這裡我不用註釋了,單獨講解一下這個地方,主要三點:

  • 1.建立一個server類,建立一個初始化函式init()
  • 2.在建構函式裡呼叫初始化函式,把路由,中介軟體,監聽函式,一切需要初始化執行的函式都拎進去
  • 3.最後執行這個類 這樣做的好處,一個標準化,一個是利於擴充套件。

下回預告

回顧一下,這篇內容很淺顯,主要是安裝deno,簡單實用oak框架,拆分路由而已。 下回我們直接聊typescript裝飾器模式,註解,並且實現註解路由。控制層也按照類class的寫法,就跟Java的springmvc一樣,如下圖:

image

從上圖可以看到,這樣根本就不需要router檔案啦。不管是node、deno還是golang,飛狐真的很不喜歡一個單獨的router檔案去維護路由,太麻煩。所以每次搭架子的時候,我一定是先把路由給搞定了,免得麻煩。現在理解為啥下回直接開幹註解路由了吧,deno的註解路由還是很一波三折的,因為完全照搬node會有些坑。期待吧,嘿嘿?~

相關文章