Node项目不会代码分层怎么办?来看看网易大神怎么做

编者注:作者是网易高级前端@季含婷,当前负责网易供应商业务线前端开发以及接口管理平台建设工作,专注于中后台前端以及Node应用开发建设工作。

Node项目不会代码分层怎么办?来看看网易大神怎么做

一、背景

刚刚接触Node开发或者后端开发的同学,有时候虽然能跟着网上的例子能去写出一个Node应用,但是网上的大神们或者项目领导说你对你的代码分层了吗?

其实作为一个有追求的开发,自己也很想写出更健壮,更易维护性的大型项目,而不是网上demo级别的例子。

那么我们应该怎么做呢?

那这边就跟大家分享一下我们是如何通过代码分层,来提高项目的档次的。

二、分层方案

本文主要介绍项目中代码的分层结构,大致分为Dao、Model、Service、Controller层,这种分层模式在Java、Android和IOS都有被应用,下面我们看看在node中的应用。
项目配置
编程语言:NodeJS

数据库:mysql

第三方库:@tiger系列包+Sequelize

项目目录结构

2.1 Dao层

一般Dao层是用来和底层数据库通信,负责对数据库的增删改查。

在项目中用了第三方库Sequelize,它是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, SQLite 和 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 读取和复制等功能.

创建一个sequelize对象实例


2.2 Model层

Model即模型,常常和持久化的数据一一对应,Model承载的作用就是数据的抽象,描述了一个数据的定义,Model的实例就是一组组的数据。

抽取model基类:BaseModel
基类BaseModel包含一个model对象以及对应模型的CRUD操作,为继承它的子类提供数据库增删改查的操作方法

import { sequelize } from '../dao';

export class BaseModel {
    model: any;
    constructor(modelName: string, schema: any, option?: any) {
        this.model = sequelize.define(modelName, schema, option);
    }
    // 返回实例化的sequelize模型实例
    getModel() {
        return this.model;
    }
    // 多条查询
    findAll(option: any) {
        return this.model.findAll(option);
    }
    // 单条数据查询
    findOne(option: any) {
        return this.model.findOne(option);
    }
    // 更新方法
    update(values: any, option: any) {
        return this.model.update(values, option);
    }
    // 删除
    delete(option: any) {
        return this.model.destroy(option);
    }
    // 插入单个实体
    create(entity: any) {
        return this.model.create(entity);
    }
    // 批量插入实体集
    createBatch(entitys: any) {
        return this.model.bulkCreate(entitys);
    }
}

创建branch表映射model:BranchModel
BranchModel继承基类BaseModel,除了继承了BaseModel的CURD方法外,还可定义BranchModel自身需要的特殊方法。

import Sequelize from 'sequelize';
import { Service } from '@tiger/boot';

import { BaseModel } from './base.model';

@Service
export class BranchModel extends BaseModel {
    constructor() {
        super('branch', {
            id: { type: Sequelize.BIGINT, allowNull: false, autoIncrement: true, primaryKey: true },
            // 分支名
            branchName: { type: Sequelize.STRING(128), allowNull: false, defaultValue: '' },
            // 服务Id
            serviceId: { type: Sequelize.BIGINT, allowNull: false, defaultValue: 0 },
            // 分支描述
            description: { type: Sequelize.STRING(4096), allowNull: false, defaultValue: '' },
            // 分支创建人的邮箱
            createUser: { type: Sequelize.STRING(128), allowNull: false, defaultValue: '' },
            ...
        }, { 
            timestamps: false, 
            tableName: 'TB_YX_API_BRANCH' 
        });
        this.model = super.getModel();
        // 同步当前模型到数据库中
        this.model.sync();
    }
    // 特殊方法定义
    batchUpdate(){
        // ...
    }
}

2.3 Service层

Service的重点是在于提供服务,可以处理事务和业务逻辑。

创建BranchModel对应的service:BranchService
BranchService主要提供BranchModel的数据处理服务;一个Model最好有一个与之对应的service,这个service包含了业务需要对model处理的所有操作。

import { Service } from '@tiger/boot';

import { BranchModel } from '../../model/branch.model';
import { BranchInfoVO, BranchInfoParamsVO, CreateBranchInfoParamsVO, SetBranchStateParamsVO } from './vo/branch-info.vo';
import { OpenIdInfo } from '../shared/types';

@Service
export class BranchService {
    constructor(
        private branchModel: BranchModel
    ) {
    }
    async getBranchList(serviceId: number): Promise<BranchInfoVO[]> {
        const result = await this.branchModel.findAll({
            where: {
                serviceId: serviceId
            },
            order: [['deleteFlag', 'ASC'], ['updateTime', 'DESC']]
        });
        return result;
    }

    async createBranch(param: CreateBranchInfoParamsVO, user: OpenIdInfo): Promise<any> {
        const values = {
            ...param,
            createUser: user.email,
            createTime: Date.now()
        };
        const result = await this.branchModel.create(values);
        return result;
    }

    async updateBranch(param: BranchInfoParamsVO, user: OpenIdInfo): Promise<any> {
        const values = {
            branchName: param.branchName,
            description: param.description,
            updateUser: user.email,
            updateTime: Date.now()
        };
        const result = await this.branchModel.update(values, { where: { id: param.id } });
        return result;
    }
}

创建controller对应的service:BranchManageService
BranchManageService提供与controller对应的服务,主要做业务逻辑处理。一个controller最好有一个与之对应的service,这个service包含有controller调度的所有操作。

import { Service } from '@tiger/boot';

import { BranchService } from './branch.service';
import { BranchApiDetailQueryVO, ServiceBranchInfoListVO } from './vo/branch-info.vo';

@Service
export class BranchManageService {
    constructor(
        private branchService: BranchService
    ) {
    }
    // 批量查询分支信息
    async queryBranch(branchList: BranchApiDetailQueryVO[]): Promise<ServiceBranchInfoListVO[]> {
        const serviceIdList: number[] = branchList.map(item => Number(item.serviceId)),
            branchIdList: number[] = branchList.map(item => item.branchId),
            result: ServiceBranchInfoListVO[] = [],
            servicePromise = this.branchService.batchQueryServiceInfo(serviceIdList),
            branchPromise = this.branchService.batchQueryBranchInfo(branchIdList),
            res = await Promise.all([servicePromise, branchPromise]),
            [serviceInfos, branchInfos] = res;
        if (serviceInfos.length > 0) {
            serviceInfos.forEach((service: any) => {
                const item: ServiceBranchInfoListVO = {
                    serviceId: service.id,
                    cmdbServiceId: service.cmdbServiceId,
                    cmdbServiceName: service.cmdbServiceName,
                    cmdbProductName: service.cmdbProductName,
                    branchList: branchInfos.filter((branch: any) => branch.serviceId === service.id)
                };
                result.push(item);
            });
        }
        return result;
    }
    ...

2.4 Controller

根据具体的业务场景,可以创建其他服务;
Controller层
controller是控制中心,所有的指令,调度都从这里发出去。与service交互,只负责调用服务,不负责业务逻辑处理。

创建controller:BranchController

import { RestController, RequestMapping,  PostMapping } from '@tiger/boot';
import { Boom } from '@tiger/error';
import { AppConfig, RequestContext, AjaxResult } from '@tiger/core';
import { isNullOrUndefined } from 'util';

import { BranchManageService } from './branch-manage.service';
import {  ServiceBranchInfoListVO, BatchQueryBranchVO } from './vo/branch-info.vo';

@RestController
@RequestMapping(`${AppConfig.contextPath}${AppConfig.xhrPrefix}`, [])
export class BranchController {
    constructor(
        private service: BranchManageService
    ) { }
    /** 批量查询分支信息 */
    @PostMapping('/branch/queryBranch.json')
    async queryBranch(ctx: RequestContext<BatchQueryBranchVO, AjaxResult<ServiceBranchInfoListVO[]>>) {
        // 参数校验
        if (isNullOrUndefined(ctx.request.body)) {
             throw Boom.notFound('参数错误');
        }
        const result = await this.service.queryBranch(ctx.request.body.branchList);
        ctx.body = AjaxResult.success(result);
    }
}

三、总结

这种分层结构不仅仅是为了使代码看上去清晰,更像是我们对一个系统的拆解和组装,降低代码的耦合度,提高代码的可扩展性。它可以让你在遇到代码交接的情况下减少提刀砍人的可能性,可以让多人协作开发更容易。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容