引言
在现代软件开发中,微服务架构已成为构建大型分布式系统的重要模式。Node.js凭借其非阻塞I/O特性和丰富的生态系统,成为实现微服务架构的理想选择。本文将深入探讨如何使用Express框架和TypeScript构建可扩展、可维护的微服务应用,涵盖从基础概念到实际实现的完整流程。
微服务架构概述
什么是微服务架构
微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件架构模式。每个服务:
- 运行在自己的进程中
- 通过轻量级通信机制(通常是HTTP API)进行通信
- 专注于特定的业务功能
- 可以独立部署、扩展和维护
微服务的优势与挑战
优势:
- 技术栈灵活性:不同服务可以使用不同的技术栈
- 独立部署:服务可以独立开发、测试和部署
- 可扩展性:可以根据需求单独扩展特定服务
- 团队自治:不同团队可以负责不同服务
挑战:
- 分布式复杂性:需要处理网络通信、容错等复杂问题
- 数据一致性:跨服务的数据同步和一致性保证
- 运维复杂度:需要更多的监控、日志和调试工具
- 网络延迟:服务间通信可能带来性能开销
Node.js微服务架构技术选型
Express框架选择
Express.js是Node.js最流行的Web应用框架,具有以下优势:
- 简洁的API设计
- 中间件支持丰富
- 社区活跃,文档完善
- 与TypeScript兼容性好
TypeScript的引入价值
TypeScript为JavaScript添加了静态类型检查,为微服务开发带来:
- 编译时错误检查
- 更好的代码提示和重构支持
- 提高代码可维护性
- 便于团队协作开发
项目初始化与基础结构搭建
项目结构设计
microservice-project/
├── package.json
├── tsconfig.json
├── README.md
├── services/
│ ├── user-service/
│ │ ├── src/
│ │ │ ├── controllers/
│ │ │ ├── models/
│ │ │ ├── routes/
│ │ │ ├── middleware/
│ │ │ ├── services/
│ │ │ └── app.ts
│ │ └── package.json
│ └── order-service/
│ ├── src/
│ │ ├── controllers/
│ │ ├── models/
│ │ ├── routes/
│ │ ├── middleware/
│ │ ├── services/
│ │ └── app.ts
│ └── package.json
├── shared/
│ ├── types/
│ ├── utils/
│ └── config/
└── docker-compose.yml
TypeScript配置文件
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"types": ["node", "express"],
"typeRoots": ["./node_modules/@types"],
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"strictBindCallApply": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
服务基础配置
// shared/config/index.ts
export interface ServiceConfig {
port: number;
host: string;
name: string;
database: {
host: string;
port: number;
name: string;
username: string;
password: string;
};
redis: {
host: string;
port: number;
password?: string;
};
jwt: {
secret: string;
expiresIn: string;
};
}
export const config: ServiceConfig = {
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
host: process.env.HOST || 'localhost',
name: process.env.SERVICE_NAME || 'default-service',
database: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 5432,
name: process.env.DB_NAME || 'myapp',
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password'
},
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : 6379,
password: process.env.REDIS_PASSWORD
},
jwt: {
secret: process.env.JWT_SECRET || 'your-secret-key',
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
}
};
用户服务实现
数据模型定义
// services/user-service/src/models/user.model.ts
import { Document, Schema, model } from 'mongoose';
export interface User extends Document {
username: string;
email: string;
password: string;
firstName: string;
lastName: string;
createdAt: Date;
updatedAt: Date;
}
export const UserSchema = new Schema<User>({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3,
maxlength: 30
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Please enter a valid email']
},
password: {
type: String,
required: true,
minlength: 6
},
firstName: {
type: String,
required: true,
trim: true,
maxlength: 50
},
lastName: {
type: String,
required: true,
trim: true,
maxlength: 50
}
}, {
timestamps: true
});
export const UserModel = model<User>('User', UserSchema);
控制器实现
// services/user-service/src/controllers/user.controller.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/user.service';
import { User } from '../models/user.model';
export class UserController {
private userService: UserService;
constructor() {
this.userService = new UserService();
}
async createUser(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const userData: Partial<User> = req.body;
const user = await this.userService.createUser(userData);
res.status(201).json({
success: true,
data: user,
message: 'User created successfully'
});
} catch (error) {
next(error);
}
}
async getUserById(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const { id } = req.params;
const user = await this.userService.getUserById(id);
if (!user) {
res.status(404).json({
success: false,
message: 'User not found'
});
return;
}
res.json({
success: true,
data: user
});
} catch (error) {
next(error);
}
}
async updateUser(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const { id } = req.params;
const userData: Partial<User> = req.body;
const user = await this.userService.updateUser(id, userData);
res.json({
success: true,
data: user,
message: 'User updated successfully'
});
} catch (error) {
next(error);
}
}
async deleteUser(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const { id } = req.params;
await this.userService.deleteUser(id);
res.json({
success: true,
message: 'User deleted successfully'
});
} catch (error) {
next(error);
}
}
async getAllUsers(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const { page = 1, limit = 10 } = req.query;
const users = await this.userService.getAllUsers(
parseInt(page as string),
parseInt(limit as string)
);
res.json({
success: true,
data: users,
pagination: {
page: parseInt(page as string),
limit: parseInt(limit as string),
total: users.length
}
});
} catch (error) {
next(error);
}
}
}
服务层实现
// services/user-service/src/services/user.service.ts
import { User, UserModel } from '../models/user.model';
import bcrypt from 'bcrypt';
import { v4 as uuidv4 } from 'uuid';
export class UserService {
async createUser(userData: Partial<User>): Promise<User> {
try {
// 检查用户是否已存在
const existingUser = await UserModel.findOne({
$or: [{ email: userData.email }, { username: userData.username }]
});
if (existingUser) {
throw new Error('User already exists');
}
// 密码加密
if (userData.password) {
const saltRounds = 10;
userData.password = await bcrypt.hash(userData.password, saltRounds);
}
// 创建用户
const user = new UserModel({
...userData,
_id: uuidv4()
});
return await user.save();
} catch (error) {
throw new Error(`Failed to create user: ${error.message}`);
}
}
async getUserById(id: string): Promise<User | null> {
try {
return await UserModel.findById(id);
} catch (error) {
throw new Error(`Failed to get user: ${error.message}`);
}
}
async updateUser(id: string, userData: Partial<User>): Promise<User | null> {
try {
// 如果更新密码,需要加密
if (userData.password) {
const saltRounds = 10;
userData.password = await bcrypt.hash(userData.password, saltRounds);
}
return await UserModel.findByIdAndUpdate(
id,
{ ...userData },
{ new: true, runValidators: true }
);
} catch (error) {
throw new Error(`Failed to update user: ${error.message}`);
}
}
async deleteUser(id: string): Promise<void> {
try {
await UserModel.findByIdAndDelete(id);
} catch (error) {
throw new Error(`Failed to delete user: ${error.message}`);
}
}
async getAllUsers(page: number, limit: number): Promise<User[]> {
try {
const skip = (page - 1) * limit;
return await UserModel.find()
.skip(skip)
.limit(limit)
.sort({ createdAt: -1 });
} catch (error) {
throw new Error(`Failed to get users: ${error.message}`);
}
}
async findByEmail(email: string): Promise<User | null> {
try {
return await UserModel.findOne({ email });
} catch (error) {
throw new Error(`Failed to find user by email: ${error.message}`);
}
}
}
路由配置
// services/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.post('/', userController.createUser.bind(userController));
router.get('/:id', userController.getUserById.bind(userController));
router.put('/:id', userController.updateUser.bind(userController));
router.delete('/:id', userController.deleteUser.bind(userController));
router.get('/', userController.getAllUsers.bind(userController));
export default router;
应用启动文件
// services/user-service/src/app.ts
import express, { Application, Request, Response, NextFunction } from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import { config } from '../../shared/config';
import userRoutes from './routes/user.routes';
import { errorHandler } from './middleware/error.middleware';
class App {
public app: Application;
constructor() {
this.app = express();
this.initializeMiddleware();
this.initializeRoutes();
this.initializeDatabase();
this.initializeErrorHandling();
}
private initializeMiddleware(): 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: Request, res: Response) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
service: config.name
});
});
}
private async initializeDatabase(): Promise<void> {
try {
await mongoose.connect(
`mongodb://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.name}`,
{
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false
}
);
console.log('Connected to MongoDB');
} catch (error) {
console.error('MongoDB connection error:', error);
process.exit(1);
}
}
private initializeErrorHandling(): void {
this.app.use(errorHandler);
// 404处理
this.app.use((req: Request, res: Response, next: NextFunction) => {
res.status(404).json({
success: false,
message: 'Route not found'
});
});
}
public listen(): void {
const port = config.port;
this.app.listen(port, () => {
console.log(`User service listening at http://localhost:${port}`);
});
}
}
export default App;
API网关设计
API网关核心功能
API网关作为微服务架构的入口点,承担以下职责:
- 路由请求到相应服务
- 身份验证和授权
- 请求/响应转换
- 限流和熔断
- 日志记录和监控
基于Express的API网关实现
// api-gateway/src/app.ts
import express, { Application, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { config } from '../shared/config';
class GatewayApp {
public app: Application;
constructor() {
this.app = express();
this.initializeMiddleware();
this.initializeRoutes();
}
private initializeMiddleware(): 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',
createProxyMiddleware({
target: 'http://user-service:3000',
changeOrigin: true,
pathRewrite: {
'^/api/users': '/api/users'
}
})
);
// 订单服务代理
this.app.use('/api/orders',
createProxyMiddleware({
target: 'http://order-service:3001',
changeOrigin: true,
pathRewrite: {
'^/api/orders': '/api/orders'
}
})
);
// 健康检查端点
this.app.get('/health', (req: Request, res: Response) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
service: 'api-gateway'
});
});
}
public listen(): void {
const port = config.port;
this.app.listen(port, () => {
console.log(`API Gateway listening at http://localhost:${port}`);
});
}
}
export default GatewayApp;
服务间通信机制
HTTP通信实现
// shared/utils/http.client.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
export class HttpClient {
private client: AxiosInstance;
constructor(baseURL: string, timeout: number = 10000) {
this.client = axios.create({
baseURL,
timeout,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
this.client.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 添加认证头等
const token = process.env.AUTH_TOKEN;
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
this.client.interceptors.response.use(
(response: AxiosResponse) => {
return response.data;
},
(error) => {
return Promise.reject(error);
}
);
}
async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.client.get<T>(url, config);
}
async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.client.post<T>(url, data, config);
}
async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.client.put<T>(url, data, config);
}
async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.client.delete<T>(url, config);
}
}
服务发现实现
// shared/services/discovery.service.ts
import { HttpClient } from '../utils/http.client';
export interface ServiceInstance {
id: string;
name: string;
host: string;
port: number;
status: 'UP' | 'DOWN';
lastHeartbeat: Date;
}
export class ServiceDiscovery {
private httpClient: HttpClient;
private serviceRegistry: Map<string, ServiceInstance[]> = new Map();
private discoveryInterval: NodeJS.Timeout | null = null;
constructor(discoveryUrl: string) {
this.httpClient = new HttpClient(discoveryUrl);
}
async discoverServices(): Promise<void> {
try {
const services = await this.httpClient.get<ServiceInstance[]>('/services');
services.forEach(service => {
if (!this.serviceRegistry.has(service.name)) {
this.serviceRegistry.set(service.name, []);
}
this.serviceRegistry.get(service.name)!.push(service);
});
} catch (error) {
console.error('Service discovery failed:', error);
}
}
getService(name: string): ServiceInstance | null {
const instances = this.serviceRegistry.get(name);
if (!instances || instances.length === 0) {
return null;
}
// 简单的负载均衡策略:轮询
const instance = instances[0];
instances.push(instance);
instances.shift();
return instance;
}
startDiscovery(interval: number = 30000): void {
this.discoveryInterval = setInterval(() => {
this.discoverServices().catch(console.error);
}, interval);
}
stopDiscovery(): void {
if (this.discoveryInterval) {
clearInterval(this.discoveryInterval);
}
}
}
中间件与安全机制
认证中间件
// services/user-service/src/middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { config } from '../../../shared/config';
export interface AuthenticatedRequest extends Request {
user?: {
id: string;
email: string;
role: string;
};
}
export const authenticate = (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
throw new Error('Access denied. No token provided.');
}
const decoded = jwt.verify(token, config.jwt.secret) as { id: string; email: string; role: string };
req.user = decoded;
next();
} catch (error) {
res.status(401).json({
success: false,
message: 'Invalid token'
});
}
};
export const authorize = (...roles: string[]) => {
return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
if (!req.user) {
return res.status(401).json({
success: false,
message: 'Authentication required'
});
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: 'Insufficient permissions'
});
}
next();
};
};
请求验证中间件
// services/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()) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array()
});
}
next();
};
export const userValidationRules = (): ValidationChain[] => {
return [
body('username')
.isLength({ min: 3, max: 30 })
.withMessage('Username must be between 3 and 30 characters')
.matches(/^[a-zA-Z0-9_]+$/)
.withMessage('Username can only contain letters, numbers, and underscores'),
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Please provide a valid email address'),
body('password')
.isLength({ min: 6 })
.withMessage('Password must be at least 6 characters long')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.withMessage('Password must contain at least one uppercase letter, one lowercase letter, and one number'),
body('firstName')
.isLength({ min: 1, max: 50 })
.withMessage('First name must be between 1 and 50 characters'),
body('lastName')
.isLength({ min: 1, max: 50 })
.withMessage('Last name must be between 1 and 50 characters')
];
};
监控与日志系统
日志配置
// shared/utils/logger.ts
import winston from 'winston';
import { config } from '../config';
const { combine, timestamp, printf } = winston.format;
const logFormat = printf(({ level, message, timestamp, ...meta }) => {
return `${timestamp} [${level}] ${message} ${
Object.keys(meta).length ? JSON.stringify(meta) : ''
}`;
});
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: combine(
timestamp(),
logFormat
),
defaultMeta: { service: config.name },
transports: [
new winston.transports.Console({
format: combine(
timestamp(),
logFormat
)
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
format: combine(
timestamp(),
logFormat
)
}),
new winston.transports.File({
filename: 'logs/combined.log',
format: combine(
timestamp(),
logFormat
)
})
]
});
export default logger;
性能监控中间件
// services/user-service/src/middleware/monitoring.middleware.ts
import { Request, Response, NextFunction } from 'express';
import logger from '../../shared/utils/logger';
export const performanceMonitor = (req: Request, res: Response, next: NextFunction): void => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
const logData = {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
userAgent: req.get('User-Agent'),
ip: req.ip
};
if (duration > 1000) {
logger.warn('Slow request detected', logData);
} else {
logger.info('Request completed', logData);
}
});
next();
};
export const requestCounter = (req: Request, res: Response, next: NextFunction): void => {
// 这里可以集成Prometheus等监控系统
// 例如:prometheusClient.inc({ method: req.method, path: req.path });
next();
};
部署与容器化
Dockerfile配置
# services/user-service/Dockerfile
FROM node:16-alpine
# 设置工作目录
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["npm", "start"]
Docker Compose配置
# docker-compose.yml
version: '3.8'
services:
mongodb:
image: mongo:5.0
container_name: mongodb
ports:
- "27017:27017"
评论 (0)