给框架一个名字,不想起一些怪异的名字,既然是按Spring Boot的设计思想,跟着Spring,我给这个框架起名Summer
Github:github.com/calidan-x/s…
____ _ _ __ __ __ __ _____ ____
/ ___|| | | | \/ | \/ | ____| _ \
\___ \| | | | |\/| | |\/| | _| | |_) |
___) | |_| | | | | | | | |___| _ <
|____/ \___/|_| |_|_| |_|_____|_| \_\
Ver 0.0.1
[2021-03-16 13:12:46] MySQL DB connected
[2021-03-16 13:12:46] Server started port:8080
框架设计思路
loc.ts
反向控制的代码可以说是非常简单。只需要封装好实例存储和读取实例的方法即可。
route-mapping.ts
路由编写是其中最麻烦一块,需要读取controller中的方法传参,方法对应的请求方法,控制器的路由前缀,这些decorator数据最终会被拼装成一个routeMapping全局数据。
decorators/*.ts
所有装饰器(注解)
这里分为3类:
- Restful请求路由相关 @Controller @Get @Post @Put @Delete @Patch
- 请求参数相关 @RequestParam @PathParam @RequestBody
- Loc容器相关 @Service @Component @Autowired
- 对于Controller中使用的装饰器例如@Controller @Get @PathParam,调用route-mapping中的方法,最终合成routeMapping数据。
- 对于Controller,Service等默认Loc容器类装饰器例如@Controller @Service @Component,则调用loc.ts中的实例添加方法加入到loc容器中。
最终生成结构大致如下:
[
'/persons': {
GET: { callMethod: 'personList', params: [] },
POST: { callMethod: 'addPerson', params: [Array] },
controllerName: 'PersonController',
pathRegExp: /^\/persons[\/#\?]?$/i,
pathKeys: [],
controller: PersonController { personService: [PersonService] }
},
'/persons/:id': {
GET: { callMethod: 'personInfo', params: [Array] },
controllerName: 'PersonController',
pathRegExp: /^\/persons(?:\/([^\/#\?]+?))[\/#\?]?$/i,
pathKeys: [ [Object] ],
controller: PersonController { personService: [PersonService] }
}
]
server.ts
服务器主类,封装监听端口的方法。
- 当路由请求过来的时候,为了加快匹配速度可以先找到完全匹配的路由,找不到再遍历从正则的route。
- 找到合适的route后,要做的事就是分解方法传参,用请求发来的queryParam pathParam requestBody根据接口方法顺序组合成一个数组,传给调用方法,根据解析和方法执行结果设置http状态值,将方法返回数据写入请求返回数据。
database.ts
数据库连接类,在尝试Sequelize-Typescript和TypeORM后,觉得TypeORM更佳,所以集成了TypeORM
resouces目录
存放项目配置
logger.ts
日志打印类
summer.ts
框架主入口,扫描注入Loc单例,连接数据库,启动端口监听
开发中遇到的问题
- @PathParam()参数为空时是否可以简写成PathParam?因为TypeScript语言限制,没有重载方法的方式所以无法实现,考虑到大多数时候传参与变量名是一致的,所以最后设计成了。PathParam+PathParamFrom()的模式。
- 简写后ParaParam如何读取方法变量名?将方法toString()后从参数读取。
- 如何对调用接口的数据做验证?使用class-validator。
- 各种DTO与Entity的转换,在Java环境下往往需要做对象与Json格式的反复转换,在NodeJS环境下有没有便捷方法?经过研究,可以先设置tsconfig.json中的useDefineForClassFields为true,target至少要ES6,这样就可以导出类空字段,然后实例化后遍历导入数据。
实践框架,尝试构建工程得到了一些代码实践建议
创建了一个简单的项目,在数据库中创建了一个Person表,实现Person表的读取添加一系列接口
Controller类
import { Controller, Get, Autowired, PathParam, Post, RequestBody } from '../../lib/decorators';
import { PersonRequest } from '../dto/request/person-request';
import { PersonResource } from '../dto/resource/person-resource';
import { PersonService } from '../service/person-service';
@Controller('/persons')
export class PersonController {
@Autowired
personService: PersonService;
@Get('')
async personList() {
const persons = await this.personService.getPersons();
return persons;
}
@Post('')
async addPerson(@RequestBody personRequest: PersonRequest) {
const persons = await this.personService.savePerson(personRequest.toPerson());
return persons;
}
@Get('/:id')
async personInfo(@PathParam id: string) {
const person = await this.personService.getPersonInfo(id);
return PersonResource.from(person);
}
}
Service类
import { getRepository } from 'typeorm';
import { Service } from '../../lib/decorators';
import { Person } from '../entity/person';
@Service
export class PersonService {
personRepository = getRepository(Person);
async getPersons() {
const person = await this.personRepository.find();
return person;
}
async getPersonInfo(id: string) {
const person = await this.personRepository.findOne(id);
return person;
}
async savePerson(person: Person) {
await this.personRepository.save(person);
}
}
代码实践建议:
- 使用class取代 interface / type,因为Entity与验证类都要求使用class,所以后端的typescript开发主要使用class而非前端常用的 interface / type。
- 代码内部可以通过TypeScript核查类型,对于外部输入数据要严防死守,因为TypeScript无法完成在运行时起作用。
- TypeORM比Sequelize-Typescript更好用些。
NodeJS与Java后端框架对比优势与劣势
优势:占用内存小,启动迅速,不用处理多线程问题,非阻塞高并发,少了Setter Getter和Java各种List/ArrayList/HashMap等...代码编写更简易。
劣势:运行时类型的检查,严谨的类型判断,TypeScript对反射与Decorator功能的支持不足导致框架无法更完善的封装高级功能。
总结
NodeJS后端已经有很好生态,各类工程化与微服务需要功能都有了npm版本,内部数据通过TypeScript验证保证类型无误,只要处理好输入数据的验证和转换问题,是可以在实际项目中发挥力量的。
离完整框架的距离,本框架中未实现的功能
- AOP
- 定时任务
- 消息队列
- 文件上传,与表单格式支持
- 验证与安全
- 跨域请求设置
- 框架测试/工程测试
- 申明式请求
- 模板渲染
- API文档生成
- 项目介绍文档
- ….
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情