引言
在现代软件开发领域,微服务架构已成为构建大型分布式系统的重要模式。Node.js凭借其非阻塞I/O特性和优秀的性能表现,成为微服务架构实现的理想选择。本文将深入探讨如何使用Express框架和TypeScript语言特性来构建企业级的微服务应用,涵盖服务拆分、接口设计、错误处理、日志记录等关键要素。
微服务架构概述
什么是微服务架构
微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件设计方法。每个服务都围绕特定的业务功能构建,并且可以独立部署、扩展和维护。这种架构模式具有以下优势:
- 可扩展性:可以针对特定服务进行水平或垂直扩展
- 技术多样性:不同服务可以使用不同的技术栈
- 独立部署:服务可以独立开发、测试和部署
- 容错性:单个服务的故障不会影响整个系统
Node.js在微服务中的优势
Node.js在微服务架构中表现出色,主要体现在:
- 高性能:基于事件驱动和非阻塞I/O模型,能够处理大量并发请求
- 轻量级:启动速度快,内存占用少
- 生态系统丰富:NPM包管理器提供了大量的工具和库
- 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)