TypeScript越来越流行,在大型项目中应用广泛,今天来聊聊TypeScript进阶架构设计,如何设计高可用高并发的系统。

TypeScript,越来越,流行,了,从,最,开始,的,小众,语言,到,现在,几乎,成为,大型,前端,项目,的,标配,很多,公司,的,前端,项目,都,在,用,TypeScript,而且,Node.js,后端,项目,用,TypeScript,的,也,越来越,多,TypeScript,的,类型,系统,能,帮,我们,在,编译,阶段,发现,很多,错误,提升,代码,质量,和,可维护性。

但是,很多,人,用,TypeScript,只是,把,JavaScript,加上,类型,没有,充分,发挥,TypeScript,的,优势,也,没有,用,TypeScript,做,好,架构,设计,今天,这篇,文章,就,来,聊聊,TypeScript,进阶,架构,设计,如何,用,TypeScript,设计,高可用,高并发,的,系统。

一、TypeScript类型系统的高级用法

要,做,好,TypeScript,架构,设计,首先,要,充分,理解,和,使用,TypeScript,的,类型,系统,很多,人,只用,了,TypeScript,的,基础,类型,比如,string,number,boolean,interface,等等,但是,TypeScript,的,类型,系统,非常,强大,有,很多,高级,用法,能,帮,我们,做,更,好,的,类型,约束,和,架构,设计,下面,介绍,一些,常用,的,高级,类型,用法。

1. 泛型,Generics

泛型,是,TypeScript,最,重要,的,高级,特性,之一,能,让,我们,写,出,更,通用,更,灵活,的,代码,同时,保持,类型,安全,泛型,允许,我们,在,定义,函数,类,接口,的,时候,不,指定,具体,类型,而是,在,使用,的,时候,再,指定,类型,这样,同一个,函数,或,类,能,处理,多种,类型,的,数据,同时,保持,类型,安全。

比如,一个,通用,的,API,响应,类型:

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

// 使用
const userResponse: ApiResponse<User> = await api.getUser();
const listResponse: ApiResponse<User[]> = await api.getUserList();

这样,同一个,ApiResponse,接口,能,处理,不同,类型,的,data,同时,保持,类型,安全,不用,为,每个,接口,单独,定义,响应,类型,很,方便。

泛型,还,能用,在,函数,类,等等,地方,比如,通用,的,工具,函数,通用,的,数据结构,类,等等,都,可以,用,泛型,来,实现,在,架构,设计,中,泛型,非常,重要,能,让,我们,的,代码,更,通用,更,灵活,同时,保持,类型,安全,是,大型,项目,中,必须,掌握,的,特性。

2. 联合类型和交叉类型

联合类型,Union Types,和,交叉类型,Intersection Types,也是,TypeScript,常用,的,高级,类型,能,让,我们,更,灵活,地,组合,类型。

联合类型,用,|,表示,一个,值,可以,是,多种,类型,中,的,一种,比如:

type Status = 'success' | 'error' | 'loading';
type Result = string | number | null;

联合类型,能,让,我们,更,精确,地,描述,类型,比如,状态,只能,是,几个,固定,值,中,的,一个,就,用,字符串,字面量,联合类型,这样,能,在,编译,阶段,检查,状态,是否,合法,避免,拼写,错误,等等。

交叉类型,用,&,表示,把,多个,类型,合并,成,一个,类型,新,类型,拥有,所有,类型,的,属性,比如:

interface User {
  id: number;
  name: string;
}

interface Timestamp {
  createdAt: Date;
  updatedAt: Date;
}

type UserWithTimestamp = User & Timestamp;

这样,UserWithTimestamp,就,同时,拥有,User,和,Timestamp,的,所有,属性,不用,重复,定义,交叉类型,在,组合,多个,接口,或,类型,的,时候,很,有用,能,让,我们,更,灵活,地,组合,类型,避免,重复,代码。

3. 类型守卫和类型收窄

类型守卫,Type Guards,和,类型,收窄,Type Narrowing,是,TypeScript,中,处理,联合类型,的,重要,技术,能,让,我们,在,代码,中,精确,地,判断,和,收窄,类型,避免,类型,错误。

比如,对于,联合类型,string | number,我们,需要,判断,当前,值,是,string,还是,number,然后,做,不同,的,处理,这,时候,就,需要,类型,守卫:

function process(value: string | number) {
  if (typeof value === 'string') {
    // 这里 value 被收窄为 string
    console.log(value.toUpperCase());
  } else {
    // 这里 value 被收窄为 number
    console.log(value.toFixed(2));
  }
}

typeof,就是,一个,类型,守卫,能,帮,我们,收窄,类型,除了,typeof,还有,instanceof,in,等等,类型,守卫,也,可以,自定义,类型,守卫,函数,用,is,关键字:

function isUser(value: any): value is User {
  return value && typeof value.id === 'number' && typeof value.name === 'string';
}

if (isUser(value)) {
  // 这里 value 被收窄为 User
  console.log(value.name);
}

类型,守卫,和,类型,收窄,在,处理,复杂,类型,和,联合类型,的,时候,很,重要,能,让,我们,的,代码,更,类型,安全,避免,运行时,错误,在,大型,项目,中,经常,会,用到,要,掌握。

4. 映射类型和条件类型

映射类型,Mapped Types,和,条件类型,Conditional Types,是,TypeScript,更,高级,的,类型,特性,能,让,我们,基于,已有,类型,创建,新,类型,非常,强大。

映射类型,能,让,我们,遍历,一个,类型,的,所有,属性,创建,新,的,类型,比如,把,所有,属性,变成,可选,的,或者,只读,的:

// 所有属性变成可选
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// 所有属性变成只读
type Readonly<T> = {
  [P in keyof T]: readonly T[P];
};

TypeScript,内置,了,很多,映射类型,工具,比如,Partial,Readonly,Record,Pick,Omit,等等,这些,都,是,基于,映射类型,实现,的,能,帮,我们,快速,创建,新,类型,不用,重复,定义。

条件类型,能,让,我们,根据,条件,选择,不同,的,类型,类似,三元,运算符:

type IsString<T> = T extends string ? true : false;

条件类型,非常,强大,能,实现,很多,复杂,的,类型,逻辑,TypeScript,内置,的,很多,工具,类型,比如,Exclude,Extract,NonNullable,ReturnType,等等,都,是,基于,条件类型,实现,的。

映射类型,和,条件类型,虽然,比较,高级,但是,在,大型,项目,的,架构,设计,中,很,有用,能,帮,我们,更,灵活,地,操作,类型,减少,重复,代码,提升,类型,系统,的,表达,能力,建议,深入,学习,和,掌握。

二、TypeScript项目架构设计

掌握,了,类型,系统,的,高级,用法,之后,下面,来,聊聊,TypeScript,项目,的,架构,设计,好,的,架构,设计,能,让,项目,更,易维护,更,易扩展,更,高可用,高并发。

1. 模块化设计,按领域划分模块

TypeScript,项目,首先,要,做,好,模块化,设计,按,领域,划分,模块,而,不是,按,技术,层,划分,很多,人,喜欢,按,技术,层,划分,目录,比如,controllers,services,models,utils,等等,但是,这种,划分,方式,在,项目,大,了,之后,会,很,难,维护,因为,一个,领域,的,代码,分散,在,多个,目录,里,修改,一个,功能,要,在,多个,目录,之间,跳,来,跳,去,很,麻烦,而且,模块,之间,的,依赖,关系,也,不,清晰,容易,产生,循环,依赖。

更好,的,方式,是,按,领域,划分,模块,也就是,领域,驱动,设计,DDD,的,思路,把,同一个,领域,的,代码,放,在,一个,模块,里,比如,用户,模块,订单,模块,商品,模块,等等,每个,模块,内部,再,按,技术,层,划分,比如,controller,service,model,等等,这样,一个,领域,的,代码,都,在,一个,目录,里,修改,一个,功能,只,需要,在,一个,模块,里,修改,很,方便,而且,模块,之间,的,依赖,关系,也,更,清晰,每个,模块,对外,暴露,明确,的,接口,其他,模块,只能,通过,接口,访问,不能,直接,访问,内部,实现,这样,耦合,更,低,内聚,更,高,更,易维护,和,扩展。

比如,目录,结构,可以,这样:

src/
  modules/
    user/
      controller.ts
      service.ts
      model.ts
      types.ts
      index.ts  // 对外暴露的接口
    order/
      controller.ts
      service.ts
      model.ts
      types.ts
      index.ts
    product/
      ...
  shared/
    utils/
    types/
    constants/
  app.ts

这样,每个,模块,独立,内聚,对外,通过,index.ts,暴露,接口,其他,模块,只,能,通过,这个,接口,访问,不能,直接,访问,内部,实现,耦合,低,内聚,高,很,好,维护,和,扩展。

2. 分层架构,职责清晰

在,每个,模块,内部,也要,做,好,分层,架构,让,各层,职责,清晰,一般,来说,可以,分,为,控制层,Controller,服务层,Service,数据,访问层,Repository/DAO,和,模型,层,Model,等等,每层,职责,明确,只,做,自己,该,做,的,事情,不要,越权。

控制层,负责,接收,请求,参数,校验,调用,服务层,返回,响应,不,包含,业务,逻辑,服务层,负责,核心,业务,逻辑,处理,事务,调用,数据,访问层,数据,访问层,负责,和,数据库,交互,CRUD,操作,不,包含,业务,逻辑,模型,层,负责,定义,数据,模型,和,类型,这样,每层,职责,清晰,代码,更,易维护,也,更,易测试,因为,每层,都,可以,独立,测试。

而且,分层,架构,也,有利于,高可用,和,高并发,因为,各层,职责,清晰,能,独立,优化,比如,数据,访问层,可以,加,缓存,分库分表,等等,服务层,可以,做,异步,处理,消息队列,等等,控制层,可以,做,限流,熔断,等等,每层,独立,优化,不,影响,其他,层,能,提升,整个,系统,的,性能,和,可用性。

3. 依赖注入,解耦组件

依赖,注入,Dependency Injection,是,架构,设计,中,很,重要,的,模式,能,帮,我们,解耦,组件,提升,代码,的,可测试性,和,可维护性,在,TypeScript,项目,中,可以,用,依赖,注入,容器,来,管理,组件,的,依赖,关系,比如,InversifyJS,TypeDI,等等,这些,库,能,帮,我们,实现,依赖,注入,让,组件,之间,解耦。

依赖,注入,的,核心,思想,是,组件,不,自己,创建,依赖,而是,通过,构造,函数,或者,属性,接收,依赖,由,外部,容器,来,注入,依赖,这样,组件,和,依赖,之间,解耦,组件,只,依赖,接口,不,依赖,具体,实现,能,很,方便,地,替换,依赖,也,很,方便,测试,因为,测试,的,时候,可以,注入,模拟,的,依赖,不用,依赖,真实,的,实现。

比如,服务层,依赖,数据,访问层,通过,依赖,注入,接收:

@injectable()
class UserService {
  constructor(
    @inject(UserRepository) private userRepository: UserRepository
  ) {}
  
  async getUser(id: number) {
    return this.userRepository.findById(id);
  }
}

这样,UserService,不,自己,创建,UserRepository,而是,通过,构造,函数,接收,由,容器,注入,这样,UserService,和,UserRepository,解耦,测试,的,时候,可以,注入,模拟,的,UserRepository,不用,依赖,真实,的,数据库,很,方便,测试。

依赖,注入,在,大型,项目,中,很,重要,能,让,架构,更,清晰,组件,更,解耦,更,易测试,和,维护,建议,在,TypeScript,项目,中,使用,依赖,注入,模式。

三、错误处理和类型安全

高可用,系统,的,一个,重要,方面,就是,错误,处理,好,的,错误,处理,能,让,系统,更,稳定,更,易调试,TypeScript,的,类型,系统,能,帮,我们,做,更,好,的,错误,处理,下面,聊聊。

1. 自定义错误类型,精确标识错误

在,TypeScript,项目,中,不要,只用,原生,的,Error,类型,要,自定义,错误,类型,精确,标识,不同,的,错误,这样,能,在,捕获,错误,的,时候,精确,判断,错误,类型,做,不同,的,处理,也,能,携带,更,多,的,错误,信息,方便,调试。

比如:

class ApiError extends Error {
  constructor(
    public code: number,
    message: string,
    public details?: any
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

class NotFoundError extends ApiError {
  constructor(message: string = 'Not Found') {
    super(404, message);
    this.name = 'NotFoundError';
  }
}

class ValidationError extends ApiError {
  constructor(message: string = 'Validation Error', details?: any) {
    super(400, message, details);
    this.name = 'ValidationError';
  }
}

这样,不同,的,错误,有,不同,的,类型,捕获,错误,的,时候,可以,用,instanceof,判断,错误,类型,做,不同,的,处理:

try {
  // 业务代码
} catch (error) {
  if (error instanceof NotFoundError) {
    // 处理404错误
  } else if (error instanceof ValidationError) {
    // 处理参数校验错误
  } else if (error instanceof ApiError) {
    // 处理其他API错误
  } else {
    // 处理未知错误
  }
}

自定义,错误,类型,能,让,错误,处理,更,精确,更,清晰,也,能,携带,更,多,信息,方便,调试,和,问题,定位,在,大型,项目,中,很,重要。

2. 用类型约束错误,避免忽略错误

TypeScript,的,类型,系统,能,帮,我们,避免,忽略,错误,比如,函数,返回,类型,是,Promise<T>,就,不能,忽略,它,的,错误,必须,处理,或者,向上,抛,还有,可以,用,Result,类型,来,显式,表示,可能,失败,的,操作,让,调用,方,必须,处理,错误,不能,忽略。

比如,定义,一个,Result,类型:

type Result<T, E = Error> = 
  | { success: true; value: T }
  | { success: false; error: E };

// 使用
function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { success: false, error: 'Division by zero' };
  }
  return { success: true, value: a / b };
}

const result = divide(10, 0);
if (result.success) {
  console.log(result.value);
} else {
  console.error(result.error);
}

这样,函数,的,返回,类型,显式,表示,可能,失败,调用,方,必须,检查,success,字段,才能,获取,值,不能,忽略,错误,能,有效,避免,忽略,错误,的,问题,提升,系统,的,可靠性。

3. 全局错误处理,统一错误响应

在,系统,的,最,外层,要,做,全局,错误,处理,统一,捕获,和,处理,错误,返回,统一,格式,的,错误,响应,不要,让,错误,直接,暴露,给,用户,也,不要,每个,地方,都,单独,处理,错误,重复,代码。

比如,在,Node.js,后端,项目,中,可以,用,中间件,做,全局,错误,处理:

app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(error);
  
  if (error instanceof ApiError) {
    res.status(error.code).json({
      code: error.code,
      message: error.message,
      details: error.details,
    });
  } else {
    res.status(500).json({
      code: 500,
      message: 'Internal Server Error',
    });
  }
});

这样,所有,错误,都,会,被,全局,错误,中间件,捕获,统一,处理,返回,统一,格式,的,响应,不用,每个,接口,都,单独,处理,错误,减少,重复,代码,也,能,保证,错误,响应,格式,统一,提升,用户,体验,也,方便,前端,处理,错误。

在,前端,项目,中,也,要,做,全局,错误,处理,比如,全局,的,API,错误,处理,全局,的,未捕获,异常,处理,等等,统一,处理,错误,给,用户,友好,的,提示,也,上报,错误,信息,方便,调试,和,问题,定位。

四、性能优化和高并发

高并发,系统,的,核心,就是,性能,优化,TypeScript,项目,也,要,做,好,性能,优化,下面,聊聊,一些,常用,的,性能,优化,手段。

1. 异步编程,充分利用事件循环

Node.js,是,单线程,事件,驱动,的,要,充分,利用,异步,编程,不要,阻塞,事件,循环,在,TypeScript,项目,中,要,用,async/await,来,写,异步,代码,清晰,易读,同时,要,注意,不要,有,阻塞,操作,比如,同步,文件,读写,同步,网络,请求,等等,都,要,用,异步,版本,避免,阻塞,事件,循环。

而且,对于,一些,不,需要,等待,结果,的,操作,可以,异步,处理,不用,等待,比如,发送,邮件,记录,日志,等等,可以,放到,消息队列,里,异步,处理,不用,阻塞,主,流程,能,提升,系统,的,响应,速度,和,并发,能力。

2. 缓存,减少重复计算和IO

缓存,是,提升,性能,最,有效,的,手段,之一,能,减少,重复,计算,和,IO,操作,大幅,提升,响应,速度,和,并发,能力,在,TypeScript,项目,中,可以,用,多级,缓存,比如,内存,缓存,Redis,缓存,CDN,缓存,等等,根据,数据,的,特点,选择,合适,的,缓存,策略。

比如,热点,数据,变化,不,频繁,的,可以,放,在,Redis,缓存,里,减少,数据库,查询,页面,静态,资源,可以,放,在,CDN,上,减少,服务器,压力,一些,计算,结果,可以,放,在,内存,缓存,里,减少,重复,计算,等等,缓存,能,大幅,提升,系统,性能,但是,也要,注意,缓存,一致性,问题,缓存,更新,策略,缓存,击穿,穿透,雪崩,等等,问题,要,处理,好。

3. 连接池,复用连接

对于,数据库,连接,Redis,连接,等等,要,用,连接池,复用,连接,不要,每次,操作,都,新建,连接,因为,新建,连接,开销,很,大,会,严重,影响,性能,连接池,能,复用,连接,减少,连接,创建,的,开销,提升,性能,和,并发,能力。

比如,MySQL,用,mysql2,的,连接池,Redis,用,ioredis,的,连接,池,等等,都,要,配置,合适,的,连接池,大小,根据,并发,量,调整,不要,太小,也,不要,太大,太小,不够,用,太大,浪费,资源,合适,的,连接池,大小,能,让,性能,最,优。

4. 负载均衡,水平扩展

对于,高并发,系统,单台,服务器,肯定,扛,不住,要,做,负载,均衡,水平,扩展,通过,增加,服务器,数量,来,提升,系统,的,并发,能力,和,可用性,在,Node.js,项目,中,可以,用,PM2,的,集群,模式,利用,多核,CPU,也,可以,用,Nginx,做,反向,代理,和,负载,均衡,把,请求,分发,到,多个,Node.js,实例,上,还,可以,用,容器,编排,平台,比如,Kubernetes,来,做,自动,扩缩容,根据,负载,自动,调整,实例,数量,提升,系统,的,弹性,和,可用性。

水平,扩展,是,提升,高并发,能力,的,核心,手段,通过,增加,机器,数量,来,提升,系统,的,处理,能力,只要,系统,是,无状态,的,就,能,很,容易,地,水平,扩展,所以,在,架构,设计,的,时候,要,尽量,让,服务,无状态,把,状态,存,在,外部,存储,里,比如,Redis,数据库,等等,这样,就能,方便,地,水平,扩展。

五、写在最后

TypeScript,是,一门,很,优秀,的,语言,它,的,类型,系统,能,帮,我们,写,出,更,安全,更,易维护,的,代码,但是,要,真正,发挥,TypeScript,的,优势,还,需要,做,好,架构,设计,包括,类型,系统,的,高级,用法,项目,模块化,和,分层,设计,依赖,注入,错误,处理,性能,优化,等等,这些,都,做,好,了,才能,用,TypeScript,设计,出,高可用,高并发,的,系统。

今天,分享,了,我,在,TypeScript,进阶,架构,设计,方面,的,一些,经验,和,心得,包括,类型,系统,的,高级,用法,项目,架构,设计,错误,处理,性能,优化,等等,希望,能,给,使用,TypeScript,的,朋友,一些,参考,和,启发,帮,大家,更,好,地,使用,TypeScript,设计,高质量,的,系统。

当然,这些,只是,我,个人,的,一些,经验,和,心得,可能,不,一定,都,对,也,不,一定,适合,所有,项目,仅供,参考,大家,还是,要,根据,自己,的,项目,实际,情况,选择,合适,的,架构,和,方法,不要,盲目,照搬,而且,TypeScript,和,前端,技术,发展,很,快,新,的,特性,和,最佳,实践,也,在,不断,出现,大家,要,多,关注,官方,文档,和,社区,动态,不断,学习,更新,自己,的,知识。

最后,希望,大家,都,能,用,好,TypeScript,设计,出,高可用,高并发,高质量,的,系统,也,欢迎,大家,在,评论,区,分享,你们,的,TypeScript,架构,设计,经验,和,心得,一起,交流,一起,进步,谢谢,大家,的,阅读。