来源:Node.js

MVC

MVC(Model-View-Controller)是一种常见的软件架构模式,用于设计和组织应用程序的代码

它将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller),它们各自承担不同的职责

  • 模型
    • 表示应用程序的数据和业务逻辑
    • 负责处理数据的存储、检索、验证和更新等操作
    • 通常包含与数据库、文件系统或外部服务进行交互的代码
  • 视图
    • 将模型的数据以可视化的形式呈现给用户
    • 负责用户界面的展示,包括各种图形元素、页面布局和用户交互组件等
    • 通常根据模型的状态动态生成和更新
  • 控制器
    • 充当模型和视图之间的中间人,负责协调两者之间的交互
    • 接收用户输入(例如按钮点击、表单提交等),并根据输入更新模型的状态或调用相应的模型方法
    • 可以根据模型的变化来更新视图的显示

MVC将应用程序的逻辑、数据和界面分离,提高了代码的可维护性、可扩展性和可重用性

MVC的清晰结构将不同的职责分配给不同的组件,使开发人员能够更好地管理和修改应用程序的各个部分

IoC和DI

IoC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)是软件开发中常用的设计模式和技术,用于解耦和管理组件之间的依赖关系

虽然经常一起使用,但它们是不同的概念

控制反转

控制反转是一种设计原则,它将组件的控制权从组件自身转移到外部容器

传统上,组件负责自己的创建和管理,而控制反转则将这个责任转给了一个外部的容器或框架

容器负责创建组件实例并管理它们的生命周期,组件只需声明自己所需的依赖关系,并通过容器获取这些依赖

这种控制权的反转使得组件更加松耦合、可测试、可维护

依赖注入

依赖注入是实现控制反转的一种具体技术,它通过将组件的依赖关系从组件内部移动到外部容器来实现松耦合

组件不再负责创建或管理它所依赖的其他组件,而是通过构造函数、属性或方法参数等方式将依赖关系注入到组件中

可以通过构造函数注入(Constructor Injection)、属性注入(Property Injection)或方法注入(Method Injection)等方式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A {
// 注入依赖B
constructor (b) {
// 如果在A内部创建和管理B,会造成A和B的耦合
// new B()

this.b = b
}
}

class B {
constructor () {}
}

class Container {
// 通过外部容器向A注入依赖B,实现解耦
constructor () {
let b = new B()
new A(b)
}
}

MVC项目

安装依赖

构建出这样一种项目结构

基本结构

需要在tsconfig.ts中开启装饰器语法和反射、关闭严格模式

1
2
3
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": false,

main.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 这是装饰器的基础,需要先引入它
import 'reflect-metadata'
import { InversifyExpressServer } from 'inversify-express-utils'
import { Container } from 'inversify'
import express from 'express'
import { User } from './src/user/controller'
import { UserService } from './src/user/service'

// 控制反转中的容器
const container = new Container()

// 向容器注入模块
container.bind(User).to(User)
container.bind(UserService).to(UserService)

// 将容器与express绑定
const server = new InversifyExpressServer(container)

// 支持json
server.setConfig(app => {
app.use(express.json())
})
// 构建express
const app = server.build()

app.listen(11451, ()=> {
console.log('server on 11451')
})

src/user/controller.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 控制路由
*/
import { controller, httpGet as Get, httpPost as Post } from "inversify-express-utils"
// UserService
import { UserService } from "./service"
// 注入器
import { inject } from "inversify"
// express类型
import type { Request, Response } from "express"

@controller('/user')
export class User {

// 将UserService注入
constructor(@inject(UserService) private readonly userService: UserService) {
}

@Get('/index')
public getIndex(req: Request, res: Response) {
let result = this.userService.getList()
res.send(result)
}

@Post('/create')
public createUser(req: Request, res: Response) {
let result = this.userService.createUser()
res.send(result)
}
}

src/user/service.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 执行业务
*/
// 注入器
import { injectable } from "inversify"

@injectable()
export class UserService {
public getList() {
return {
list: ['yajue', 'kmr', 'mur']
}
}

public createUser() {
return {
status: 'success'
}
}
}

封装Prisma

以依赖注入的方式封装Prisma,让使用更便捷

main.ts中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { PrismaClient } from '@prisma/client'
import { PrismaDB } from './src/db'

/**
* 封装Prisma
*/
// 注入一个工厂函数,用于封装PrismaClient
container.bind<PrismaClient>(PrismaClient).toFactory(()=> {
return ()=> {
return new PrismaClient()
}
})
// 再将prisma类注入给容器
container.bind(PrismaDB).to(PrismaDB)

src/db/index.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 封装Prisma
*/
import { injectable, inject } from "inversify"
import {PrismaClient} from '@prisma/client'

// 将prisma工厂函数封装进一个类
@injectable()
export class PrismaDB {
prisma: PrismaClient

constructor (@inject('PrismaClient') private readonly prismaClient: ()=>PrismaClient) {
this.prisma = this.prismaClient()
}
}

src/user/controller.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 控制层,用于控制路由
*/
import { controller, httpGet as Get, httpPost as Post } from "inversify-express-utils"
// UserService
import { UserService } from "./service"
// 注入器
import { inject } from "inversify"
// express类型
import type { Request, Response } from "express"

@controller('/user')
export class User {

// 将UserService注入
constructor(@inject(UserService) private readonly userService: UserService) {
}

@Get('/index')
public async getIndex(req: Request, res: Response) {
let result = await this.userService.getList()
res.send(result)
}

@Post('/create')
public async createUser(req: Request, res: Response) {
let result = await this.userService.createUser(req.body)
res.send(result)
}
}

src/user/service.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 注入器
import { injectable, inject } from "inversify"
import { PrismaDB } from "../db"

@injectable()
export class UserService {

constructor(@inject(PrismaDB) private readonly PrismaDB:PrismaDB) {

}

public async getList() {
return this.PrismaDB.prisma.user.findMany()
}

public async createUser(user: any) {
return await this.PrismaDB.prisma.user.create({
data: user
})
}
}

DTO

DTO层用于验证传递对象的合规性

src/user/user.dto.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { IsNotEmpty, IsEmail } from 'class-validator'
import { Transform } from 'class-transformer'

export class UserDto {
// 必填项
@IsNotEmpty({message: 'name is required'})
// 转换规则:去除首尾空格 也可以用于自定义验证
@Transform(({value})=> value.trim())
name: string

@IsNotEmpty({message: 'email is required'})
// 邮箱 参数1:校验配置 参数2:同其他
@IsEmail({}, {message: 'email must be formed'})
email: string
}

在接收数据的地方进行验证,比如src/user/service.ts的createUser:

1
2
3
4
5
6
7
8
9
10
11
12
public async createUser(user: UserDto) {
const userDto = plainToClass(UserDto, user)
const errors = await validate(userDto)
// errors数组里有内容即验证不通过
if(errors.length) {
return errors
} else {
return await this.PrismaDB.prisma.user.create({
data: user
})
}
}