掘金技術整理(一)掘金的後端架構

掘金從上線到如今,網站的前端重構了 3 次,後端也陸陸續續修改了整個網站的結構 2 次,可是隨着業務不斷推演複雜,團隊人手增長,有須要一波進一步的優化!javascript

這周,咱們會根據當下掘金的狀況和接下里的主要業務,整理代碼。css

掘金技術整理系列文章:

後端架構梳理

在架構開發以前,咱們首先要梳理現有的網站狀態和需求,而後再作優化html

  1. 後端語言、框架、功能架構狀態
  2. 主要業務
  3. 代碼結構
  4. 一些最佳實踐
  5. 下一步的展望

後端語言、框架、功能架構狀態

後端語言 Node.js

因爲網站的早期開發是我來負責的,雖然我曾經寫過 PHPRuby on RailsPython 的後臺,但由於本身同時寫不少前端代碼所以對 JavaScript 最熟。與此同時,咱們選擇了 LeanCloud 做爲咱們的雲存儲、推送、託管平臺,於是就繼續使用了 Node.js 爲開發框架。前端

  • 當前版本:v4.4.5

框架 Express.js

由於選擇了 LeanCloud 的緣故,其後端你託管要用 Express 加上自己對這個框架的使用還算熟練,便沿用了下來。vue

  • 當前版本:4.13.3

功能架構狀態

上面已經講了咱們選擇了 LeanCloud,具體來說咱們使用了以下功能:java

  • LeanStorage:數據存儲
  • LeanMessage:移動應用推送
  • Leanengine:雲引擎
    • Web 服務器
      • 網頁渲染
      • 簡單的 API 接口
    • 雲函數
      • 數據綁定腳本
      • 定時腳本
  • LeanAnalytics:數據統計工具
  • 當前版本:1.0.0-beta

因爲掘金網站的主頁面是一個純前端應用,其部分業務會與後端數據接口直接關聯。下圖主要展現了咱們後端的總體狀態:node

其中黑色的箭頭表示業務需求,而黃色箭頭表明數據更新需求。webpack


主要業務模塊

Web Server

網站服務器是整個應用最基礎的部分,它處理網頁端的頁面、API 請求,外聯用戶信息和 LeanStorage 數據庫。git

config.js               // 後端配置文件
server.js               // 服務器啓動腳本
app.js                  // 後端業務,被 server.js 引用
cloud.js                // 雲函數定義,被 app.js 引用
webpack.config.js       // webpack 打包配置文件複製代碼

根據不一樣的環境(生產環境、前端開發、後端開發),config.js 定義了各個開發環境須要的配置文件信息。例如在 package.jsonnpm scripts 裏會定義:github

"dev": "cross-env NODE_ENV=devFrontend supervisor -i vue,node_modules server"複製代碼

這樣,npm run dev 就會設置當下的 NODE_ENVdevFrontend,也就是前端開發環境。

接下來 server.js 就會根據當下環境讀取的配置文件開啓服務端業務,如 app.js 定義的後端業務代碼或 cloud.js 裏的雲函數。固然,在不一樣環境下 webpack 也會作不一樣的操做。

app.js

app.js 援引了配置文件後,執行 Express.js 框架,開啓業務代碼。除了基本的 middleware(頁面 template engine jade, cookie, session 等等),其最主要的業務包含:

  • 頁面:routes
  • 用戶:auth
  • 錯誤處理

因爲網站大量使用前端 router,於是在 routes 定義中也要格外當心,掘金的開發方式是這樣的。例如 Styleguide 頁面,多是這樣的定義的:

// app.js 文件
// routes/page.js 裏定義了網頁相關的路由及後端渲染
var page = require('./routes/page');
// app 綁定了 styleguide 頁面的路由,及幾個前端路由統一
app.get('/styleguide',              page.styleguide);
app.get('/styleguide/base',         page.styleguide);
app.get('/styleguide/components',   page.styleguide);複製代碼

cloud.js 雲函數

LeanCloud 很好地支持了雲函數,能夠幫助你完成後端的數據觸發性 Hook 腳本及定時腳本。

例如每一條評論存儲在 Comment Table 裏,那麼對於這條評論,咱們能夠捕捉到

beforeSave                  // 存儲以前
afterSave                   // 存儲以後
beforeUpdate                // 更新以前
afterUpdate                 // 更新以後
beforeDelete                // 刪除以前
afterDelete                 // 刪除以後複製代碼

加入,咱們想要實現一個功能,就是增長一個 comment 後,更新相應的文章的評論數加一,而刪除後則減一。

// cloud.js 文件
// cloud/comment.js 定義了關於 Comment 數據的 Hook 函數
var comment = require('./cloud/comment');
AV.Cloud.afterSave('Comment', comment.afterSave);
AV.Cloud.afterDelete('Comment', comment.afterDelete);複製代碼
// cloud/comment.js
exports.afterSave = function(request, response) {
    ... // update 相關文章的數據
    if (ok) {
        response.success();
    } else {
        response.error('...');
    }
})複製代碼

webpack.config.js

webpack 是一個模塊打包工具,隨着它的插件、業務愈來愈強大,它也像是以前的 gruntgulp 同樣分攤了一部分腳本自動化的功能。

  • webpack.config.base.js:基本的打包配置文件,主要用於開發環境熱更新
  • webpack.config.prod.js:生產環境的配置文件,引用了 base,定義打包需求,生成 build 好的文件

這裏我就不展開關於 webpack 自己的配置優化的部分。


代碼結構

除了上面說的最基本的服務器開啓文件,整個項目的代碼結構以下:

config.js
server.js

app.js
/routes                 // 各個路由的後端業務邏輯
/views                  // 網頁渲染的 jade 文件
/vue                    // 各個頁面的 vue 業務邏輯
/redis                  // 緩存定義
/public                 // 外部訪問的靜態文件
/assets                 // 後端靜態文件
  /data                 // 後端靜態數據
  /scss                 // SCSS 樣式文件

cloud.js
/cloud                  // 雲函數相關定義文件

webpack.config.js       // webpack 打包配置文件
webpack.config.base.js
webpack.config.prod.js複製代碼

當咱們要增長一個頁面的時候

咱們再以 Styleguide 爲例,若是咱們要添加這樣的一個網頁代碼:

  1. 咱們確認它應該在 app.js 的路由的哪一個模塊下,/styleguide 是一個獨立頁面,於是它應該被定義在 /routes/page.js 裏,並定義到:
    // page.js 文件
     exports.styleguide = function(req, res) {
         res.render('styleguide', {
             title: '掘金前端 Style Guide'
         })
     }複製代碼
  2. 路由綁定:
    // app.js
     app.get('/styleguide', page.styleguide);複製代碼
  3. 因爲它是是一個網頁,於是咱們還要在 /views 裏面定義 /views/styleguide.jade
  4. 這個時候咱們會看這個頁面是否會是一個前端網頁:
    • 是:在 /vue 裏定義,並要在 webpack.config.base.js 裏定義打包邏輯
    • 否:則在 page.js 裏後端渲染頁面是傳入數據
  5. 基於網頁的複雜度來測試是否須要獨立的樣式,則須要定義 /assets/scss/styleguide.scss(更多 CSS 結構咱們會在另一篇文章中詳細描述)

這樣,整個 Styleguide 頁面會影響到的後端代碼是:

app.js                  // 路由綁定到 /styleguide
/routes
  page.js               // 定義了 styleguide 後端業務
/views
  styleguide.jade
/vue
  /styleguide           // styleguide 相關 vue 前端業務
    main.js
    app.vue
/assets
  /scss
    /pages/styleguide   // [optional] styleguide 內的複雜組件樣式
      __style.scss
      layout.scss
      ...
    styleguide.scss     // styleguide 相關獨立樣式

webpack.config.base.js  // 定義新的 styleguide 相對應的 entry複製代碼

當咱們要增長 Hook 函數的時候

舉例,咱們要開發一個數據的 Hook 函數到 LeanCloud,好比說每當一個新的 Comment 生成的時候,咱們要更新對應文章的評論數及最新的評論:

cloud.js                // AV.Cloud.afterSave('Comment', comment.afterSave)
/cloud
  comment.js            // exports.afterSave = function(request, response) {}複製代碼

當咱們要增長一個定時腳本的時候

  1. 定義腳本在 /cloud/____.js 文件裏
  2. 更新 cloud.js 文件註冊腳本,如:AV.Cloud.define('cloudFunctionName', functionName)
  3. 部署後,在 LeanCloud 的定時腳本控制檯定義運行的週期及時間

一些最佳實踐

Node.js

  1. 能用環境變量卻別開的數據,都放到 config.js 裏,不要用 ifelse 語句區分
  2. 善用 npm scripts 綁定運行函數,如:
    npm run dev             // 開發,測試數據
     npm run dev-backend     // 後端開發,測試數據
     npm run dev-build       // 測試數據,打包
     npm run prod            // 開發,生產數據
     npm run prod-backend    // 後端開發,生產數據
     npm run prod-build      // 生產數據,部署前的打包
     npm run test            // 測試
     npm start               // 開啓服務器複製代碼
  3. 函數名竟可能簡單易懂,相似於 getPopEntries 能夠明確到 getPopularEntries
  4. 每當安裝、刪除庫的時候,記得用 npm install/uninstall PACKAGE --save/--save-dev,隨時更新庫
  5. 命名規範
    • 常數:I_LOVE_YOU 用下劃線加大寫字母
    • 變量:iLoveYou 駝峯,不管是普通變量仍是函數名
    • Class:ILoveYou 首字母大寫的駝峯,包括 LeanCloud 本身的數據 Table 名
    • 當內嵌的回調函數用到了相似變量名則,使用 _iLoveYou 加前置下劃線

LeanCloud

  1. 不要重複 LeanCloud 定義好的數據類名,如 AV.User,不要使用 '_User'
  2. 善用 Promise,將複雜的業務改寫爲清晰的異步處理流:
    start()
         .then(step1)
         .then(step2)
         .then(step3)
         .then(step4)
         .catch(errorHandler)
         .finally(callback)複製代碼
    • 但當心,LeanCloud 修改了幾個 keyword 函數名,如:
      • always 替換了原有的 finally
      • AV.Promise.error 替換了 Promise.reject
    • AV.Promise.when([promise1, promise2, promise3]) 能夠在三個 promise 都完成的狀況下作異步操做
  3. LeanStorage 數據庫查詢的技巧:
    • 多用自帶的一些語句,如 exists, startsWith, matches
    • 利用 select 拿去部分數據
    • 查詢一個數據時,善用 query.first()query.get(id),可是注意:
      • first()then(function(obj) {}) 中的 obj 多是 null
      • get() 後若是得不到數據會直接引起 error
    • 使用 AV.Object.createWithoutData(TABLE_NAME, ID) 來實現指針查詢,無需取一遍數據
    • 關聯查詢:query.include('reply.user'),一個文章查詢能夠用這類語句直接查出來一個評論的回覆的用戶數據,也就是說拿出來的一個 comment 能夠訪問到 comment.get('reply')comment.get('reply').get('user')
    • 內嵌查詢:matchesQuery

Git 管理

origin/
  master                    // 線上版本
  |- hotfix-login           // 熱修復,如登陸異常
  release                   // 最新的要部署的版本
  develop                   // 開發分支
  |- feature-homepage-v2    // 正在開發的業務,如第二版的首頁
  |- feature-timeline-api   // 正在開發的業務,如 Timeline 的 API

developer-ming
  master
  release
  develop
  |- feature-timeline-api   // 我正在開發這個 feature,不斷和 origin 同步複製代碼

新的業務

  1. 任何的一個新的業務開發都要在本地從 develop fork 出來一個新的 branch feature-name
  2. 業務開發完成後,提交 Pull Request,feature-name -> develop,記得打 label 到 feature
  3. Code Review,若是有錯誤,在 feature-name 裏修復
  4. 相關負責人 Merge Pull Request,假刪除這個分支

部署新的業務

  1. develop 上不斷 merge 新的 review 過的業務功能
  2. 部署前,發 Pull Request 到 develop -> release
  3. 相關負責人 Code Review,合併代碼
  4. npm run build 打包業務代碼,準備部署
  5. 部署前的 commit,打 label 到 publish
  6. 發 PR 到 release -> master,標註版本號
  7. 部署,若是出錯,回滾或者新建 hotfix 分支

小技巧

  1. developrelease 的同步,用 git rebase
  2. developfeature 分支不作 build 操做
  3. 多人負責一個 feature 的時候,能夠就一個功能再分拆到各個 branches

下一步的展望

根據產品接下來的發展路徑,有幾個重要的功能須要優化。

  1. 後端渲染頁面,SEO 優化
  2. 文章頁面的收斂,利用 Browser Agent
    • 分享文章:Web Desktop 的詳情頁
    • 分享文章:Web Mobile 的閱讀頁
    • 原創文章:Web Desktop/Mobile 的閱讀頁
    • 沸點活動:Web Desktop/Mobile 的詳情頁
    • 全部文章:App 內的閱讀頁 / JSBridge
  3. 不一樣頁面的先後端打包,根據不一樣頁面的需求,加載相應的程序組件
    • 脫離手動打包、配合部署
    • 組件如:
      • 用戶
      • Vue 通用樣式組件
      • 頁面自己的業務代碼
  4. 網頁間的跳轉邏輯
  5. API 服務器獨立,並配合移動端也無需求,通用 API 實現
  6. 推送邏輯重構
相關文章
相關標籤/搜索