掘金技术整理(一)掘金的后端架构

掘金从上线到如今,网站的前端重构了 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. 推送逻辑重构
相关文章
相关标签/搜索