iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 7
1
Modern Web

30 天打造 MERN Stack Boilerplate系列 第 7

Day 07 - Backend - MVC

前面了解了 Backend 所需的 API Server 和 Database,加上 Middleware 這個利器,接著就可以談論怎麼組織 Express 程式,讓 Client、Express API Server、Database 三者互相溝通協調。

MVC

MVC 是一種軟體架構模式,把軟體切割成三大元素:M odel、V iew、C ontroller。如果從 Express Web App 的角度來看,Server 收到 Request 後先比對路由,之後進入 Middlewares 及 Controller 所控制的執行流程,Middlewares 或 Controller 可能隨時操作 Mongoose 的 Model 來向資料庫存取資料,存取結束後進入下一個 Middleware,當所有 Middleware 都走過了,Controller 再把資料灌進 View 樣板中渲染(Render)出 Html。我們在 Boilerplate 中的 View 的部分是使用 React,但那又是另一門學問了,我們留待後面章節再來介紹。

組織模組

有了上述 MVC 概念後,我們來看看 Boilerplate 是怎麼拆解和組織架構的:

檔案結構

各位可以比對 Day 02 - File Organization 中列出的完整架構,這裡只取出 server 資料夾的部分。

/src/server/
  models/
  controllers/
  middlewares/
  routes/
  app.js

app.js 是程式進入點,models 資料夾儲存 Model 相關的模組,controllers 資料夾儲存 Controller 相關的模組,其餘資料夾依此類推,各資料夾內可以建立各自的 index.js 來整合資料夾內的模組。

程式碼範例

程式進入點依序註冊 Middlewares 與 Routes,接著啟動 Server:

// src/server/app.js

import express from 'express';
import middlewares from './middlewares';
import routes from './routes';

let app = express();
middlewares({ app });
routes({ app });
app.listen(port, (err) => {
  console.log('Listening at port', port);
});

middlewares/index.js 統整 Middlewares:

// src/server/middlewares/index.js

import middleware1 from './middleware1';
import middleware2 from './middleware2';

export default ({ app }) => {
  app.use(middleware1);
  app.use(middleware2);
  // ...
};

每一個 Express Middleware 模組獨立寫成一份檔案:

// src/server/middlewares/middleware1.js
// src/server/middlewares/middleware2.js
// ...

export default (req, res, next) => {
  if (/* some condition */) {
    // send reject response
  } else {
    next();
  }
};

routes/index.js 一樣整合整個 App 的路由:

// src/server/routes/index.js

import apiRoutes from './api';
import anotherRoutes from './another';
import stillAnotherRoutes from './stillAnother';
import otherRoutes from './other';

export default ({ app }) => {
  apiRoutes({ app });
  anotherRoutes({ app });
  stillAnotherRoutes({ app });
  otherRoutes({ app });
  // ...
};

在 API 的路由中設定對應的 Middlewares 和 Controller:

// src/server/routes/api.js

import bodyParser from '../middlewares/bodyParser';
import todoController from '../controllers/todo';

export default ({ app }) => {
  app.get('/api/todos', todoController.list);
  app.post('/api/todos', bodyParser.json, todoController.create);
  app.put('/api/todos/:id', bodyParser.json, todoController.update);
  app.delete('/api/todos/:id', todoController.remove);
};

Controller 和 Mongoose Model 互動後取回資料,再以 Json 形式回應給 Client。我們的 Server 主要是用於提供 API 服務,所以此例中沒有給出 Render View 的範例寫法。

import Todo from '../models/Todo';

export default {
  list(req, res) {
    // ...
  },

  create(req, res) {
    // ...
  },

  update(req, res) {
    // ...
  },

  remove(req, res) {
    Todo.remove({_id: req.params.id}, (err) => {
      res.json({});
    });
  },
};

Model 部分的程式碼在此就不贅述,請參考 Day 05 - Backend - Technique Stack

值得一提的是,我們在這程式中用到了 ES6 的 Object Literal 技巧來傳遞 app 這個變數,這麼做的好處是保留彈性,日後我們想傳遞別的參數時,不必影響原有的程式架構。

至此,我們算是優雅地建立出 Backend 的服務了,Model、Controller、Middleware、Route、...皆有各自獨立的資料夾,如有必要,也可以建立資料夾內的 index.js,這樣日後要擴充模組時就十分容易,同時也維持了 MVC 的結構。


上一篇
Day 06 - Backend - Express
下一篇
Day 08 - Frontend - Technique Stack
系列文
30 天打造 MERN Stack Boilerplate30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言