Node.js 微服务架构设计:基于Express + TypeScript 的企业级应用开发实践

浅夏微凉
浅夏微凉 2026-01-31T11:07:26+08:00
0 0 1

引言

在现代软件开发领域,微服务架构已成为构建大型分布式系统的重要模式。Node.js凭借其非阻塞I/O特性和优秀的性能表现,成为微服务架构实现的理想选择。本文将深入探讨如何使用Express框架和TypeScript语言特性来构建企业级的微服务应用,涵盖服务拆分、接口设计、错误处理、日志记录等关键要素。

微服务架构概述

什么是微服务架构

微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件设计方法。每个服务都围绕特定的业务功能构建,并且可以独立部署、扩展和维护。这种架构模式具有以下优势:

  • 可扩展性:可以针对特定服务进行水平或垂直扩展
  • 技术多样性:不同服务可以使用不同的技术栈
  • 独立部署:服务可以独立开发、测试和部署
  • 容错性:单个服务的故障不会影响整个系统

Node.js在微服务中的优势

Node.js在微服务架构中表现出色,主要体现在:

  1. 高性能:基于事件驱动和非阻塞I/O模型,能够处理大量并发请求
  2. 轻量级:启动速度快,内存占用少
  3. 生态系统丰富:NPM包管理器提供了大量的工具和库
  4. TypeScript支持:提供静态类型检查,提高代码质量和开发效率

环境搭建与项目初始化

项目结构设计

microservice-project/
├── packages/
│   ├── user-service/
│   │   ├── src/
│   │   │   ├── controllers/
│   │   │   ├── models/
│   │   │   ├── routes/
│   │   │   ├── services/
│   │   │   └── utils/
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── order-service/
│       ├── src/
│       │   ├── controllers/
│       │   ├── models/
│       │   ├── routes/
│       │   ├── services/
│       │   └── utils/
│       ├── package.json
│       └── tsconfig.json
├── shared/
│   ├── types/
│   └── utils/
├── docker-compose.yml
└── package.json

初始化TypeScript项目

# 创建项目目录
mkdir microservice-project
cd microservice-project

# 初始化npm项目
npm init -y

# 安装基础依赖
npm install express cors helmet morgan dotenv

# 安装开发依赖
npm install -D typescript @types/express @types/node nodemon ts-node

# 创建tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "types": ["node"],
    "declaration": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Express框架基础配置

基础服务器搭建

// packages/user-service/src/server.ts
import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';

// 加载环境变量
dotenv.config();

class App {
  public app: Application;
  public port: number;

  constructor() {
    this.app = express();
    this.port = parseInt(process.env.PORT || '3001', 10);
    this.initializeMiddlewares();
  }

  private initializeMiddlewares(): void {
    // 安全中间件
    this.app.use(helmet());
    
    // CORS配置
    this.app.use(cors({
      origin: process.env.ALLOWED_ORIGINS?.split(',') || ['*'],
      credentials: true
    }));
    
    // 日志中间件
    this.app.use(morgan('combined'));
    
    // 解析JSON请求体
    this.app.use(express.json({ limit: '10mb' }));
    this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
  }

  public listen(): void {
    this.app.listen(this.port, () => {
      console.log(`Server running on port ${this.port}`);
    });
  }
}

export default new App();

路由配置结构

// packages/user-service/src/routes/index.ts
import { Router } from 'express';
import userRoutes from './user.routes';

const router = Router();

router.use('/users', userRoutes);

export default router;
// packages/user-service/src/routes/user.routes.ts
import { Router } from 'express';
import { UserController } from '../controllers/user.controller';

const router = Router();
const userController = new UserController();

router.get('/', userController.getAllUsers.bind(userController));
router.get('/:id', userController.getUserById.bind(userController));
router.post('/', userController.createUser.bind(userController));
router.put('/:id', userController.updateUser.bind(userController));
router.delete('/:id', userController.deleteUser.bind(userController));

export default router;

TypeScript类型系统应用

服务接口定义

// shared/types/user.types.ts
export interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
  updatedAt: Date;
}

export interface CreateUserDto {
  name: string;
  email: string;
}

export interface UpdateUserDto {
  name?: string;
  email?: string;
}

export interface UserQueryParams {
  page?: number;
  limit?: number;
  sortBy?: string;
  sortOrder?: 'asc' | 'desc';
}

数据模型实现

// packages/user-service/src/models/user.model.ts
import { User, CreateUserDto } from '../../../shared/types/user.types';

export class UserModel {
  private users: User[] = [
    {
      id: '1',
      name: 'John Doe',
      email: 'john@example.com',
      createdAt: new Date(),
      updatedAt: new Date()
    }
  ];

  public async findAll(): Promise<User[]> {
    return this.users;
  }

  public async findById(id: string): Promise<User | null> {
    return this.users.find(user => user.id === id) || null;
  }

  public async create(userData: CreateUserDto): Promise<User> {
    const newUser: User = {
      id: Date.now().toString(),
      name: userData.name,
      email: userData.email,
      createdAt: new Date(),
      updatedAt: new Date()
    };
    
    this.users.push(newUser);
    return newUser;
  }

  public async update(id: string, userData: Partial<User>): Promise<User | null> {
    const userIndex = this.users.findIndex(user => user.id === id);
    
    if (userIndex === -1) {
      return null;
    }
    
    this.users[userIndex] = {
      ...this.users[userIndex],
      ...userData,
      updatedAt: new Date()
    };
    
    return this.users[userIndex];
  }

  public async delete(id: string): Promise<boolean> {
    const userIndex = this.users.findIndex(user => user.id === id);
    
    if (userIndex === -1) {
      return false;
    }
    
    this.users.splice(userIndex, 1);
    return true;
  }
}

控制器层设计

基础控制器结构

// packages/user-service/src/controllers/base.controller.ts
import { Request, Response, NextFunction } from 'express';
import { Logger } from '../utils/logger.util';

export abstract class BaseController {
  protected logger: Logger;

  constructor() {
    this.logger = new Logger(this.constructor.name);
  }

  protected sendSuccessResponse(res: Response, data: any, statusCode: number = 200): void {
    res.status(statusCode).json({
      success: true,
      data
    });
  }

  protected sendErrorResponse(
    res: Response, 
    error: any, 
    statusCode: number = 500
  ): void {
    const errorResponse = {
      success: false,
      error: error.message || 'Internal Server Error'
    };

    // 记录错误日志
    this.logger.error(`Error ${statusCode}: ${error.message}`, { error });

    res.status(statusCode).json(errorResponse);
  }

  protected handleAsyncError(
    fn: (req: Request, res: Response, next: NextFunction) => Promise<any>
  ): (req: Request, res: Response, next: NextFunction) => void {
    return (req: Request, res: Response, next: NextFunction) => {
      Promise.resolve(fn(req, res, next)).catch(next);
    };
  }
}

用户控制器实现

// packages/user-service/src/controllers/user.controller.ts
import { Request, Response, NextFunction } from 'express';
import { BaseController } from './base.controller';
import { UserModel } from '../models/user.model';
import { CreateUserDto, UpdateUserDto, UserQueryParams } from '../../../shared/types/user.types';

export class UserController extends BaseController {
  private userModel: UserModel;

  constructor() {
    super();
    this.userModel = new UserModel();
  }

  public getAllUsers = this.handleAsyncError(
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const queryParams: UserQueryParams = req.query;
        
        // 验证查询参数
        if (queryParams.page && (isNaN(Number(queryParams.page)) || Number(queryParams.page) < 1)) {
          throw new Error('Invalid page parameter');
        }
        
        if (queryParams.limit && (isNaN(Number(queryParams.limit)) || Number(queryParams.limit) < 1)) {
          throw new Error('Invalid limit parameter');
        }

        const users = await this.userModel.findAll();
        this.sendSuccessResponse(res, users);
      } catch (error) {
        this.sendErrorResponse(res, error);
      }
    }
  );

  public getUserById = this.handleAsyncError(
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const { id } = req.params;
        
        if (!id) {
          throw new Error('User ID is required');
        }

        const user = await this.userModel.findById(id);
        
        if (!user) {
          return this.sendErrorResponse(res, new Error('User not found'), 404);
        }

        this.sendSuccessResponse(res, user);
      } catch (error) {
        this.sendErrorResponse(res, error);
      }
    }
  );

  public createUser = this.handleAsyncError(
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const userData: CreateUserDto = req.body;
        
        // 验证输入数据
        if (!userData.name || !userData.email) {
          throw new Error('Name and email are required');
        }

        const user = await this.userModel.create(userData);
        this.sendSuccessResponse(res, user, 201);
      } catch (error) {
        this.sendErrorResponse(res, error);
      }
    }
  );

  public updateUser = this.handleAsyncError(
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const { id } = req.params;
        const userData: UpdateUserDto = req.body;

        if (!id) {
          throw new Error('User ID is required');
        }

        const updatedUser = await this.userModel.update(id, userData);
        
        if (!updatedUser) {
          return this.sendErrorResponse(res, new Error('User not found'), 404);
        }

        this.sendSuccessResponse(res, updatedUser);
      } catch (error) {
        this.sendErrorResponse(res, error);
      }
    }
  );

  public deleteUser = this.handleAsyncError(
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const { id } = req.params;

        if (!id) {
          throw new Error('User ID is required');
        }

        const deleted = await this.userModel.delete(id);
        
        if (!deleted) {
          return this.sendErrorResponse(res, new Error('User not found'), 404);
        }

        this.sendSuccessResponse(res, { message: 'User deleted successfully' });
      } catch (error) {
        this.sendErrorResponse(res, error);
      }
    }
  );
}

错误处理机制

自定义错误类

// packages/user-service/src/utils/error.util.ts
export class AppError extends Error {
  public statusCode: number;
  public isOperational: boolean;

  constructor(
    message: string,
    statusCode: number = 500,
    isOperational: boolean = true
  ) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = isOperational;
    
    // 保持错误堆栈信息
    Error.captureStackTrace(this, this.constructor);
  }
}

export class ValidationError extends AppError {
  constructor(message: string) {
    super(message, 400, true);
  }
}

export class NotFoundError extends AppError {
  constructor(message: string = 'Resource not found') {
    super(message, 404, true);
  }
}

export class UnauthorizedError extends AppError {
  constructor(message: string = 'Unauthorized access') {
    super(message, 401, true);
  }
}

全局错误处理中间件

// packages/user-service/src/middleware/error.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/error.util';
import { Logger } from '../utils/logger.util';

const logger = new Logger('ErrorMiddleware');

export const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
): void => {
  let error = err;
  
  // 如果是自定义错误,直接使用
  if (err instanceof AppError) {
    error = err;
  } else {
    // 处理其他类型的错误
    error = new AppError(
      err.message || 'Internal Server Error',
      500,
      false
    );
  }

  logger.error(`[${req.method} ${req.path}] ${error.message}`, {
    error: error.stack,
    statusCode: error.statusCode,
    url: req.url,
    method: req.method,
    ip: req.ip
  });

  // 返回错误响应
  res.status(error.statusCode).json({
    success: false,
    error: error.message,
    ...(process.env.NODE_ENV === 'development' && {
      stack: error.stack
    })
  });
};

错误处理集成

// packages/user-service/src/server.ts
import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';
import router from './routes';
import { errorHandler } from './middleware/error.middleware';

dotenv.config();

class App {
  public app: Application;
  public port: number;

  constructor() {
    this.app = express();
    this.port = parseInt(process.env.PORT || '3001', 10);
    this.initializeMiddlewares();
    this.initializeRoutes();
    this.initializeErrorHandling();
  }

  private initializeMiddlewares(): void {
    this.app.use(helmet());
    this.app.use(cors({
      origin: process.env.ALLOWED_ORIGINS?.split(',') || ['*'],
      credentials: true
    }));
    this.app.use(morgan('combined'));
    this.app.use(express.json({ limit: '10mb' }));
    this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
  }

  private initializeRoutes(): void {
    this.app.use('/api/v1', router);
    
    // 健康检查端点
    this.app.get('/health', (req, res) => {
      res.status(200).json({
        status: 'OK',
        timestamp: new Date().toISOString(),
        service: 'user-service'
      });
    });
  }

  private initializeErrorHandling(): void {
    // 404处理
    this.app.use('*', (req, res) => {
      res.status(404).json({
        success: false,
        error: 'Route not found'
      });
    });

    // 全局错误处理
    this.app.use(errorHandler);
  }

  public listen(): void {
    this.app.listen(this.port, () => {
      console.log(`User service running on port ${this.port}`);
    });
  }
}

export default new App();

日志记录系统

日志工具实现

// packages/user-service/src/utils/logger.util.ts
import winston from 'winston';
import path from 'path';
import fs from 'fs';

// 确保日志目录存在
const logDir = path.join(__dirname, '../../logs');
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir, { recursive: true });
}

export class Logger {
  private logger: winston.Logger;

  constructor(context: string) {
    this.logger = winston.createLogger({
      level: process.env.LOG_LEVEL || 'info',
      format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.errors({ stack: true }),
        winston.format.json()
      ),
      defaultMeta: { context },
      transports: [
        // 错误日志文件
        new winston.transports.File({
          filename: path.join(logDir, 'error.log'),
          level: 'error',
          maxsize: 5242880, // 5MB
          maxFiles: 5
        }),
        // 所有日志文件
        new winston.transports.File({
          filename: path.join(logDir, 'combined.log'),
          maxsize: 5242880,
          maxFiles: 5
        }),
        // 控制台输出
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.colorize(),
            winston.format.simple()
          )
        })
      ]
    });
  }

  public info(message: string, meta?: any): void {
    this.logger.info(message, meta);
  }

  public error(message: string, meta?: any): void {
    this.logger.error(message, meta);
  }

  public warn(message: string, meta?: any): void {
    this.logger.warn(message, meta);
  }

  public debug(message: string, meta?: any): void {
    this.logger.debug(message, meta);
  }
}

请求日志中间件

// packages/user-service/src/middleware/request.logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { Logger } from '../utils/logger.util';

const logger = new Logger('RequestLogger');

export const requestLogger = (
  req: Request,
  res: Response,
  next: NextFunction
): void => {
  const startTime = Date.now();
  
  // 记录请求开始
  logger.info(`[REQUEST] ${req.method} ${req.url}`, {
    method: req.method,
    url: req.url,
    headers: req.headers,
    ip: req.ip,
    userAgent: req.get('User-Agent')
  });

  // 监听响应结束
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    
    logger.info(`[RESPONSE] ${req.method} ${req.url} - ${res.statusCode}`, {
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration: `${duration}ms`,
      ip: req.ip
    });
  });

  next();
};

数据验证与安全

输入验证中间件

// packages/user-service/src/middleware/validation.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { body, validationResult, ValidationChain } from 'express-validator';

export const validate = (req: Request, res: Response, next: NextFunction): void => {
  const errors = validationResult(req);
  
  if (!errors.isEmpty()) {
    const errorMessages = errors.array().map(error => error.msg);
    
    return res.status(400).json({
      success: false,
      error: 'Validation failed',
      details: errorMessages
    });
  }
  
  next();
};

export const userValidationRules = (): ValidationChain[] => [
  body('name')
    .notEmpty()
    .withMessage('Name is required')
    .isLength({ min: 2, max: 100 })
    .withMessage('Name must be between 2 and 100 characters'),
  body('email')
    .notEmpty()
    .withMessage('Email is required')
    .isEmail()
    .withMessage('Invalid email format')
    .normalizeEmail()
];

安全中间件

// packages/user-service/src/middleware/security.middleware.ts
import { Request, Response, NextFunction } from 'express';
import rateLimit from 'express-rate-limit';

// 速率限制
export const rateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100, // 限制每个IP 100个请求
  message: {
    success: false,
    error: 'Too many requests from this IP, please try again later.'
  },
  standardHeaders: true,
  legacyHeaders: false
});

// 请求大小限制
export const requestSizeLimiter = (req: Request, res: Response, next: NextFunction): void => {
  const maxSize = 10 * 1024 * 1024; // 10MB
  
  if (req.headers['content-length'] && parseInt(req.headers['content-length']) > maxSize) {
    return res.status(413).json({
      success: false,
      error: 'Request entity too large'
    });
  }
  
  next();
};

配置管理

环境配置文件

// packages/user-service/src/config/index.ts
import dotenv from 'dotenv';

// 加载环境变量
dotenv.config();

export const config = {
  port: process.env.PORT || 3001,
  nodeEnv: process.env.NODE_ENV || 'development',
  database: {
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT || '5432', 10),
    username: process.env.DB_USER || 'postgres',
    password: process.env.DB_PASSWORD || 'password',
    database: process.env.DB_NAME || 'userservice'
  },
  jwt: {
    secret: process.env.JWT_SECRET || 'your-secret-key',
    expiresIn: process.env.JWT_EXPIRES_IN || '24h'
  },
  cors: {
    allowedOrigins: process.env.ALLOWED_ORIGINS?.split(',') || ['*'],
    credentials: process.env.CORS_CREDENTIALS === 'true'
  },
  logging: {
    level: process.env.LOG_LEVEL || 'info',
    file: process.env.LOG_FILE || './logs/app.log'
  }
};

Docker化部署

Dockerfile配置

# packages/user-service/Dockerfile
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 复制package文件
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production

# 复制源代码
COPY . .

# 构建项目
RUN npm run build

# 暴露端口
EXPOSE 3001

# 创建非root用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

# 启动命令
CMD ["npm", "run", "start:prod"]

Docker Compose配置

# docker-compose.yml
version: '3.8'

services:
  user-service:
    build:
      context: .
      dockerfile: packages/user-service/Dockerfile
    ports:
      - "3001:3001"
    environment:
      - NODE_ENV=production
      - PORT=3001
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_USER=postgres
      - DB_PASSWORD=password
      - DB_NAME=userservice
    depends_on:
      - postgres
    restart: unless-stopped

  order-service:
    build:
      context: .
      dockerfile: packages/order-service/Dockerfile
    ports:
      - "3002:3002"
    environment:
      - NODE_ENV=production
      - PORT=3002
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_USER=postgres
      - DB_PASSWORD=password
      - DB_NAME=orderservice
    depends_on:
      - postgres
    restart: unless-stopped

  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=userservice
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    restart: unless-stopped

volumes:
  postgres_data:

监控与健康检查

健康检查端点

// packages/user-service/src/routes/health.routes.ts
import { Router } from 'express';
import { Logger } from '../utils/logger.util';

const router = Router();
const logger = new Logger('HealthCheck');

router.get('/health', (req, res) => {
  res.status(200).json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    service: 'user-service',
    version: process.env.npm_package_version || '1.0.0'
  });
});

router.get('/metrics', (req, res) => {
  const memoryUsage = process.memoryUsage();
  const uptime = process.uptime();
  
  res.status(200).json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    metrics: {
      memory: {
        rss: `${Math.round(memoryUsage.rss / 1024 / 1024)} MB`,
        heapTotal: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)} MB`,
        heapUsed: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)} MB`
      },
      uptime: `${Math.round(uptime)} seconds`,
      nodeVersion: process.version,
      platform: process.platform
    }
  });
});

export default router;

性能优化实践

缓存机制实现

// packages/user-service/src/utils/cache.util.ts
import NodeCache from 'node-cache';

class Cache {
  private cache: NodeCache;

  constructor() {
    this.cache = new NodeCache({
      stdTTL: 60 * 60, // 1小时默认过期时间
      checkperiod: 60 * 10 // 10分钟检查一次
    });
  }

  public get<T>(key: string): T | null {
    return this.cache.get<T>(key) || null;
  }

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000