分层标准

从本章起,正式进入企业级 Web 服务器核心内容。通常,一块残缺的业务逻辑是由视图层、管制层、服务层、模型层独特定义与实现的,如下图:

从上至下,抽象层次逐步加深。从下至上,业务细节逐步清晰。视图层属于 Web 前端内容,本文采纳 JavaScript Modules 进行演示。

本章着重说说管制层与服务层,对业务逻辑外围局部进行开展。

写一个简易版的商铺治理

间接从上一章已实现的工程 host1-tech/nodejs-server-examples - 00-static 开始着手,先编写服务层内容:

$ mkdir src/services        # 新建 src/services 目录寄存服务层逻辑$ tree -L 2 -I node_modules # 展现除了 node_modules 之外的目录内容构造.├── Dockerfile├── package.json├── public│   └── index.html├── src│   ├── server.js│   └── services└── yarn.lock
// src/services/shop.js// 店铺数据const memoryStorage = {  '1001': { name: '良品铺子' },  '1002': { name: '来伊份' },  '1003': { name: '三只松鼠' },  '1004': { name: '百草味' },};// 模仿延时async function delay(ms = 200) {  await new Promise((r) => setTimeout(r, ms));}class ShopService {  async init() {    await delay();  }  async find({ id, pageIndex = 0, pageSize = 10 }) {    await delay();    if (id) {      return [memoryStorage[id]].filter(Boolean);    }    return Object.keys(memoryStorage)      .slice(pageIndex * pageSize, (pageIndex + 1) * pageSize)      .map((id) => ({ id, ...memoryStorage[id] }));  }  async modify({ id, values }) {    await delay();    const target = memoryStorage[id];    if (!target) {      return null;    }    return Object.assign(target, values);  }  async remove({ id }) {    await delay();    const target = memoryStorage[id];    if (!target) {      return false;    }    return delete memoryStorage[id];  }}// 单例模式let service;module.exports = async function () {  if (!service) {    service = new ShopService();    await service.init();  }  return service;};

以上服务层提供了店铺管理所需的根底业务逻辑,存储临时以内存和延时模仿,当初通过管制层向外裸露 RESTful 接口:

$ mkdir src/controllers     # 新建 src/controllers 目录寄存管制层逻辑$ tree -L 2 -I node_modules # 展现除了 node_modules 之外的目录内容构造.├── Dockerfile├── package.json├── public│   └── index.html├── src│   ├── controllers│   ├── server.js│   └── services└── yarn.lock
// src/controllers/shop.jsconst { Router } = require('express');const shopService = require('../services/shop');class ShopController {  shopService;  async init() {    this.shopService = await shopService();    const router = Router();    router.get('/', this.getAll);    router.get('/:shopId', this.getOne);    router.put('/:shopId', this.put);    router.delete('/:shopId', this.delete);    return router;  }  getAll = async (req, res) => {    const { pageIndex, pageSize } = req.query;    const shopList = await this.shopService.find({ pageIndex, pageSize });    res.send({ success: true, data: shopList });  };  getOne = async (req, res) => {    const { shopId } = req.params;    const shopList = await this.shopService.find({ id: shopId });    if (shopList.length) {      res.send({ success: true, data: shopList[0] });    } else {      res.status(404).send({ success: false, data: null });    }  };  put = async (req, res) => {    const { shopId } = req.params;    const { name } = req.query;    const shopInfo = await this.shopService.modify({      id: shopId,      values: { name },    });    if (shopInfo) {      res.send({ success: true, data: shopInfo });    } else {      res.status(404).send({ success: false, data: null });    }  };  delete = async (req, res) => {    const { shopId } = req.params;    const success = await this.shopService.remove({ id: shopId });    if (!success) {      res.status(404);    }    res.send({ success });  };}module.exports = async () => {  const c = new ShopController();  return await c.init();};
// src/controllers/index.jsconst { Router } = require('express');const shopController = require('./shop');module.exports = async function initControllers() {  const router = Router();  router.use('/api/shop', await shopController());  return router;};
// src/server.jsconst express = require('express');const { resolve } = require('path');const { promisify } = require('util');+const initControllers = require('./controllers');const server = express();const port = parseInt(process.env.PORT || '9000');const publicDir = resolve('public');async function bootstrap() {  server.use(express.static(publicDir));+  server.use(await initControllers());  await promisify(server.listen.bind(server, port))();  console.log(`> Started on port ${port}`);}bootstrap();

当初应用 yarn start 启动利用,通过浏览器即可间接拜访接口 http://localhost:9000/api/shop 与 http://localhost:9000/api/shop/1001。

补充一个店铺治理界面

以 JavaScript Modules 写一个店铺治理界面仅作演示(理论生产中倡议应用 React 或 Vue),调用 GETPUTDELETE 接口对店铺信息进行查问、批改、删除:

<!-- public/index.html --><html>  <head>    <meta charset="utf-8" />  </head>  <body>-    <h1>It works!</h1>+    <div id="root"></div>++    <script type="module">+      import { refreshShopList, bindShopInfoEvents } from './index.js';++      async function bootstrap() {+        await refreshShopList();+        await bindShopInfoEvents();+      }++      bootstrap();+    </script>  </body></html>
// public/index.jsexport async function refreshShopList() {  const res = await fetch('/api/shop');  const { data: shopList } = await res.json();  const htmlItems = shopList.map(    ({ id, name }) => `<li data-shop-id="${id}">  <div data-type="text">${name}</div>  <input type="text" placeholder="输出新的店铺名称" />  <a href="#" data-type="modify">确认批改</a>  <a href="#" data-type="remove">删除店铺</a></li>`  );  document.querySelector('#root').innerHTML = `<h1>店铺列表:</h1><ul class="shop-list">${htmlItems.join('')}</ul>`;}export async function bindShopInfoEvents() {  document.querySelector('#root').addEventListener('click', async (e) => {    e.preventDefault();    switch (e.target.dataset.type) {      case 'modify':        await modifyShopInfo(e);        break;      case 'remove':        await removeShopInfo(e);        break;    }  });}export async function modifyShopInfo(e) {  const shopId = e.target.parentElement.dataset.shopId;  const name = e.target.parentElement.querySelector('input').value;  await fetch(`/api/shop/${shopId}?name=${encodeURIComponent(name)}`, {    method: 'PUT',  });  await refreshShopList();}export async function removeShopInfo(e) {  const shopId = e.target.parentElement.dataset.shopId;  const res = await fetch(`/api/shop/${shopId}`, { method: 'DELETE' });  await refreshShopList();}

拜访 http://localhost:9000/ 即可体验店铺治理性能:

本章源码

host1-tech/nodejs-server-examples - 01-api-and-layering

更多浏览

从零搭建 Node.js 企业级 Web 服务器(零):动态服务