编者注:作者是网易高级前端@季含婷,当前负责网易供应商业务线前端开发以及接口管理平台建设工作,专注于中后台前端以及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);
}
}
三、总结
这种分层结构不仅仅是为了使代码看上去清晰,更像是我们对一个系统的拆解和组装,降低代码的耦合度,提高代码的可扩展性。它可以让你在遇到代码交接的情况下减少提刀砍人的可能性,可以让多人协作开发更容易。