Node.js微服务架构实战:Express + TypeScript + Docker构建企业级应用

Zane122
Zane122 2026-02-04T13:01:04+08:00
0 0 1

引言

在现代软件开发领域,微服务架构已成为构建大规模、可扩展应用的重要模式。Node.js凭借其事件驱动、非阻塞I/O特性,成为构建微服务的理想选择。本文将通过一个完整的项目案例,详细介绍如何使用Express框架、TypeScript类型安全和Docker容器化技术来构建企业级的微服务应用。

微服务架构概述

什么是微服务架构

微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件设计方法。每个服务:

  • 运行在自己的进程中
  • 通过轻量级通信机制(通常是HTTP API)进行通信
  • 专注于特定的业务功能
  • 可以独立部署和扩展

微服务的优势与挑战

优势:

  • 技术栈灵活性:不同服务可以使用不同的技术栈
  • 独立部署:单个服务的更新不会影响整个系统
  • 可扩展性:可以根据需求单独扩展特定服务
  • 团队协作:不同团队可以独立开发和维护不同服务

挑战:

  • 服务间通信复杂性
  • 数据一致性问题
  • 分布式系统的复杂性
  • 运维成本增加

技术栈选择与理由

Express.js框架选择

Express.js是Node.js最流行的Web应用框架,具有以下优势:

  • 简洁的API设计
  • 中间件架构支持
  • 丰富的生态系统
  • 良好的性能表现
  • 广泛的社区支持

TypeScript类型安全

TypeScript为JavaScript提供了静态类型检查,能够:

  • 提前发现类型错误
  • 提供更好的IDE支持
  • 增强代码可维护性
  • 改善团队协作效率

Docker容器化

Docker提供了一致的运行环境,确保应用在不同环境中的一致性:

  • 环境隔离
  • 依赖管理简化
  • 快速部署和扩展
  • 资源利用率优化

项目结构设计

整体架构图

微服务架构
├── user-service        # 用户服务
├── order-service       # 订单服务
├── product-service     # 商品服务
├── api-gateway         # API网关
├── config              # 配置管理
└── shared              # 共享模块

服务间通信机制

采用RESTful API + 消息队列的混合方式:

  • REST API:同步调用,适合实时性要求高的场景
  • 消息队列:异步处理,适合解耦和批量处理场景

核心服务实现

用户服务实现

项目初始化

首先创建用户服务的基础结构:

mkdir user-service
cd user-service
npm init -y
npm install express cors helmet morgan dotenv
npm install -D typescript @types/node @types/express @types/cors @types/helmet @types/morgan ts-node nodemon

TypeScript配置文件

创建tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "types": ["node", "express"],
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

核心服务代码

创建src/app.ts

import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';

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

// 导入路由
import userRoutes from './routes/user.routes';

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

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

  private initializeMiddlewares(): void {
    this.app.use(helmet());
    this.app.use(cors());
    this.app.use(morgan('combined'));
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
  }

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

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

export default App;

用户模型定义

创建src/models/user.model.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;
}

用户路由实现

创建src/routes/user.routes.ts

import { Router } from 'express';
import { 
  getAllUsers, 
  getUserById, 
  createUser, 
  updateUser, 
  deleteUser 
} from '../controllers/user.controller';

const router = Router();

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

export default router;

用户控制器实现

创建src/controllers/user.controller.ts

import { Request, Response } from 'express';
import { User, CreateUserDto, UpdateUserDto } from '../models/user.model';

// 模拟数据库存储(实际项目中应使用真正的数据库)
const users: User[] = [
  {
    id: '1',
    name: 'John Doe',
    email: 'john@example.com',
    createdAt: new Date(),
    updatedAt: new Date()
  }
];

export const getAllUsers = (req: Request, res: Response): void => {
  try {
    res.status(200).json({
      success: true,
      data: users
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to fetch users',
      error: error.message
    });
  }
};

export const getUserById = (req: Request, res: Response): void => {
  try {
    const { id } = req.params;
    const user = users.find(u => u.id === id);
    
    if (!user) {
      res.status(404).json({
        success: false,
        message: 'User not found'
      });
      return;
    }
    
    res.status(200).json({
      success: true,
      data: user
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to fetch user',
      error: error.message
    });
  }
};

export const createUser = (req: Request, res: Response): void => {
  try {
    const { name, email }: CreateUserDto = req.body;
    
    // 简单的验证
    if (!name || !email) {
      res.status(400).json({
        success: false,
        message: 'Name and email are required'
      });
      return;
    }
    
    const newUser: User = {
      id: Date.now().toString(),
      name,
      email,
      createdAt: new Date(),
      updatedAt: new Date()
    };
    
    users.push(newUser);
    
    res.status(201).json({
      success: true,
      data: newUser
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to create user',
      error: error.message
    });
  }
};

export const updateUser = (req: Request, res: Response): void => {
  try {
    const { id } = req.params;
    const { name, email }: UpdateUserDto = req.body;
    
    const userIndex = users.findIndex(u => u.id === id);
    
    if (userIndex === -1) {
      res.status(404).json({
        success: false,
        message: 'User not found'
      });
      return;
    }
    
    const updatedUser = {
      ...users[userIndex],
      name: name || users[userIndex].name,
      email: email || users[userIndex].email,
      updatedAt: new Date()
    };
    
    users[userIndex] = updatedUser;
    
    res.status(200).json({
      success: true,
      data: updatedUser
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to update user',
      error: error.message
    });
  }
};

export const deleteUser = (req: Request, res: Response): void => {
  try {
    const { id } = req.params;
    
    const userIndex = users.findIndex(u => u.id === id);
    
    if (userIndex === -1) {
      res.status(404).json({
        success: false,
        message: 'User not found'
      });
      return;
    }
    
    const deletedUser = users.splice(userIndex, 1)[0];
    
    res.status(200).json({
      success: true,
      data: deletedUser,
      message: 'User deleted successfully'
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to delete user',
      error: error.message
    });
  }
};

订单服务实现

项目结构

mkdir order-service
cd order-service
npm init -y
npm install express cors helmet morgan dotenv
npm install -D typescript @types/node @types/express @types/cors @types/helmet @types/morgan ts-node nodemon

订单模型定义

创建src/models/order.model.ts

export interface OrderItem {
  productId: string;
  quantity: number;
  price: number;
}

export interface Order {
  id: string;
  userId: string;
  items: OrderItem[];
  totalAmount: number;
  status: 'pending' | 'completed' | 'cancelled';
  createdAt: Date;
  updatedAt: Date;
}

export interface CreateOrderDto {
  userId: string;
  items: OrderItem[];
}

订单服务核心代码

创建src/app.ts

import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';

dotenv.config();

import orderRoutes from './routes/order.routes';

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

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

  private initializeMiddlewares(): void {
    this.app.use(helmet());
    this.app.use(cors());
    this.app.use(morgan('combined'));
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
  }

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

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

export default App;

订单控制器实现

创建src/controllers/order.controller.ts

import { Request, Response } from 'express';
import { Order, CreateOrderDto } from '../models/order.model';

// 模拟数据库存储
const orders: Order[] = [];

export const getAllOrders = (req: Request, res: Response): void => {
  try {
    const { userId } = req.query;
    
    let filteredOrders = orders;
    
    if (userId) {
      filteredOrders = orders.filter(order => order.userId === userId.toString());
    }
    
    res.status(200).json({
      success: true,
      data: filteredOrders
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to fetch orders',
      error: error.message
    });
  }
};

export const getOrderByUserId = (req: Request, res: Response): void => {
  try {
    const { userId } = req.params;
    
    const userOrders = orders.filter(order => order.userId === userId);
    
    res.status(200).json({
      success: true,
      data: userOrders
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to fetch orders',
      error: error.message
    });
  }
};

export const createOrder = (req: Request, res: Response): void => {
  try {
    const { userId, items }: CreateOrderDto = req.body;
    
    if (!userId || !items || items.length === 0) {
      res.status(400).json({
        success: false,
        message: 'User ID and items are required'
      });
      return;
    }
    
    // 计算总金额
    const totalAmount = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
    
    const newOrder: Order = {
      id: Date.now().toString(),
      userId,
      items,
      totalAmount,
      status: 'pending',
      createdAt: new Date(),
      updatedAt: new Date()
    };
    
    orders.push(newOrder);
    
    res.status(201).json({
      success: true,
      data: newOrder
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to create order',
      error: error.message
    });
  }
};

export const updateOrderStatus = (req: Request, res: Response): void => {
  try {
    const { id } = req.params;
    const { status } = req.body;
    
    const orderIndex = orders.findIndex(order => order.id === id);
    
    if (orderIndex === -1) {
      res.status(404).json({
        success: false,
        message: 'Order not found'
      });
      return;
    }
    
    if (!['pending', 'completed', 'cancelled'].includes(status)) {
      res.status(400).json({
        success: false,
        message: 'Invalid status value'
      });
      return;
    }
    
    const updatedOrder = {
      ...orders[orderIndex],
      status,
      updatedAt: new Date()
    };
    
    orders[orderIndex] = updatedOrder;
    
    res.status(200).json({
      success: true,
      data: updatedOrder
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Failed to update order status',
      error: error.message
    });
  }
};

API网关设计

网关核心功能

API网关作为微服务架构的统一入口,主要功能包括:

  • 路由转发
  • 身份认证和授权
  • 请求/响应转换
  • 限流和熔断
  • 日志记录

网关实现示例

创建src/app.ts

import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';
import axios from 'axios';

dotenv.config();

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

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

  private initializeMiddlewares(): void {
    this.app.use(helmet());
    this.app.use(cors());
    this.app.use(morgan('combined'));
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
  }

  private initializeRoutes(): void {
    // 路由转发到用户服务
    this.app.get('/api/users/:id', async (req, res) => {
      try {
        const response = await axios.get(`http://user-service:${process.env.USER_SERVICE_PORT || '3001'}/api/users/${req.params.id}`);
        res.status(response.status).json(response.data);
      } catch (error) {
        res.status(error.response?.status || 500).json({
          success: false,
          message: 'Failed to fetch user',
          error: error.message
        });
      }
    });

    // 路由转发到订单服务
    this.app.get('/api/orders/:userId', async (req, res) => {
      try {
        const response = await axios.get(`http://order-service:${process.env.ORDER_SERVICE_PORT || '3002'}/api/orders/${req.params.userId}`);
        res.status(response.status).json(response.data);
      } catch (error) {
        res.status(error.response?.status || 500).json({
          success: false,
          message: 'Failed to fetch orders',
          error: error.message
        });
      }
    });

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

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

export default ApiGateway;

Docker容器化部署

Dockerfile配置

为每个服务创建Dockerfile:

用户服务Dockerfile (user-service/Dockerfile)

FROM node:18-alpine

WORKDIR /app

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

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

# 复制源代码
COPY . .

# 构建TypeScript代码
RUN npm run build

# 暴露端口
EXPOSE 3001

# 启动应用
CMD ["npm", "start"]

订单服务Dockerfile (order-service/Dockerfile)

FROM node:18-alpine

WORKDIR /app

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

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

# 复制源代码
COPY . .

# 构建TypeScript代码
RUN npm run build

# 暴露端口
EXPOSE 3002

# 启动应用
CMD ["npm", "start"]

API网关Dockerfile (api-gateway/Dockerfile)

FROM node:18-alpine

WORKDIR /app

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

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

# 复制源代码
COPY . .

# 构建TypeScript代码
RUN npm run build

# 暴露端口
EXPOSE 8080

# 启动应用
CMD ["npm", "start"]

Docker Compose配置

创建docker-compose.yml

version: '3.8'

services:
  user-service:
    build: ./user-service
    ports:
      - "3001:3001"
    environment:
      - NODE_ENV=production
      - PORT=3001
    networks:
      - microservice-network
    restart: unless-stopped

  order-service:
    build: ./order-service
    ports:
      - "3002:3002"
    environment:
      - NODE_ENV=production
      - PORT=3002
    networks:
      - microservice-network
    restart: unless-stopped

  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:8080"
    environment:
      - NODE_ENV=production
      - PORT=8080
      - USER_SERVICE_PORT=3001
      - ORDER_SERVICE_PORT=3002
    networks:
      - microservice-network
    depends_on:
      - user-service
      - order-service
    restart: unless-stopped

  # 可选:添加数据库服务
  mongodb:
    image: mongo:6.0
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db
    networks:
      - microservice-network
    restart: unless-stopped

networks:
  microservice-network:
    driver: bridge

volumes:
  mongo-data:

环境变量配置

创建.env文件:

# 全局环境变量
NODE_ENV=development
PORT=3000

# 用户服务配置
USER_SERVICE_PORT=3001
USER_SERVICE_HOST=user-service

# 订单服务配置
ORDER_SERVICE_PORT=3002
ORDER_SERVICE_HOST=order-service

# API网关配置
API_GATEWAY_PORT=8080
API_GATEWAY_HOST=api-gateway

# 数据库配置(可选)
DB_HOST=mongodb
DB_PORT=27017
DB_NAME=microservice_db

部署与运维最佳实践

监控和日志

日志管理

使用Winston进行结构化日志记录:

// src/utils/logger.ts
import winston from 'winston';

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'microservice' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

export default logger;

健康检查端点

在每个服务中添加详细的健康检查:

// 在app.ts中添加
this.app.get('/health', async (req, res) => {
  try {
    // 检查数据库连接(如果使用)
    // const dbStatus = await this.checkDatabaseConnection();
    
    res.status(200).json({
      status: 'OK',
      timestamp: new Date().toISOString(),
      service: process.env.SERVICE_NAME || 'unknown-service',
      version: process.env.npm_package_version || '1.0.0',
      // dbStatus: dbStatus ? 'healthy' : 'unhealthy'
    });
  } catch (error) {
    res.status(503).json({
      status: 'UNHEALTHY',
      timestamp: new Date().toISOString(),
      error: error.message
    });
  }
});

安全性考虑

身份认证和授权

使用JWT进行身份认证:

// src/middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export const authenticateToken = (req: Request, res: Response, next: NextFunction) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({
      success: false,
      message: 'Access token required'
    });
  }

  jwt.verify(token, process.env.JWT_SECRET || 'default-secret', (err, user) => {
    if (err) {
      return res.status(403).json({
        success: false,
        message: 'Invalid or expired token'
      });
    }
    
    req.user = user;
    next();
  });
};

请求限制

使用express-rate-limit防止恶意请求:

// src/middleware/rateLimit.middleware.ts
import rateLimit from 'express-rate-limit';

export const createRateLimiter = (maxRequests: number, windowMs: number) => {
  return rateLimit({
    windowMs,
    max: maxRequests,
    message: {
      success: false,
      message: 'Too many requests from this IP'
    },
    standardHeaders: true,
    legacyHeaders: false
  });
};

性能优化

连接池配置

为数据库连接设置合适的连接池:

// src/database/connection.ts
import { Pool } from 'pg';

const pool = new Pool({
  user: process.env.DB_USER || 'postgres',
  host: process.env.DB_HOST || 'localhost',
  database: process.env.DB_NAME || 'microservice_db',
  password: process.env.DB_PASSWORD || 'password',
  port: parseInt(process.env.DB_PORT || '5432', 10),
  max: 20, // 最大连接数
  min: 5,  // 最小连接数
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

export default pool;

缓存策略

使用Redis实现缓存:

// src/cache/redis.ts
import redis from 'redis';

const client = redis.createClient({
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT || '6379', 10),
});

client.on('error', (err) => {
  console.error('Redis Client Error:', err);
});

export default client;

测试策略

单元测试

使用Jest进行单元测试:

// src/tests/user.controller.test.ts
import { createUser } from '../controllers/user.controller';
import { Request, Response } from 'express';

describe('User Controller', () => {
  describe('createUser', () => {
    it('should create a new user successfully', () => {
      const mockReq = {
        body: {
          name: 'John Doe',
          email: 'john@example.com'
        }
      } as unknown as Request;
      
      const mockRes = {
        status: jest.fn().mock
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000