前言
在现代软件开发领域,微服务架构已经成为构建大型分布式系统的重要模式。Node.js凭借其异步非阻塞I/O模型和丰富的生态系统,成为了构建微服务的理想选择。本文将深入探讨如何使用Express框架、TypeScript类型安全、Docker容器化以及Kubernetes集群部署来构建一个完整的现代化微服务系统。
微服务架构概述
什么是微服务架构
微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件架构模式。每个服务:
- 运行在自己的进程中
- 通过轻量级通信机制(通常是HTTP API)进行交互
- 可以独立部署和扩展
- 专注于特定的业务功能
微服务的优势与挑战
优势:
- 独立开发和部署
- 技术栈多样化
- 易于扩展和维护
- 故障隔离性好
挑战:
- 分布式系统复杂性增加
- 数据一致性问题
- 网络延迟和故障处理
- 运维成本上升
Express框架基础搭建
项目初始化与依赖安装
首先,我们创建一个新的Node.js项目并安装必要的依赖:
mkdir microservice-demo
cd microservice-demo
npm init -y
安装Express及相关依赖:
npm install express cors helmet morgan dotenv
npm install --save-dev typescript @types/node @types/express @types/cors @types/helmet @types/morgan ts-node nodemon
基础服务器配置
创建src/server.ts文件:
import express, { Application, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';
// 加载环境变量
dotenv.config();
const app: Application = express();
const PORT: number = parseInt(process.env.PORT || '3000', 10);
// 中间件配置
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 基础路由
app.get('/', (req: Request, res: Response) => {
res.json({
message: 'Welcome to Microservice Demo',
timestamp: new Date().toISOString()
});
});
// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!',
message: err.message
});
});
// 404处理
app.use((req: Request, res: Response) => {
res.status(404).json({
error: 'Not Found',
message: 'Route not found'
});
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
export default app;
TypeScript配置文件
创建tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"types": ["node"],
"typeRoots": ["./node_modules/@types"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
TypeScript类型安全实践
接口定义与类型声明
在src/types/index.ts中定义数据接口:
export interface User {
id: string;
name: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
export interface CreateUserRequest {
name: string;
email: string;
}
export interface UpdateUserRequest {
name?: string;
email?: string;
}
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
export interface PaginationOptions {
page: number;
limit: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
服务层实现
创建src/services/userService.ts:
import { User, CreateUserRequest, UpdateUserRequest } from '../types';
import { ApiResponse } from '../types';
class UserService {
private users: User[] = [];
private nextId: number = 1;
async createUser(userData: CreateUserRequest): Promise<ApiResponse<User>> {
try {
const newUser: User = {
id: this.generateId(),
name: userData.name,
email: userData.email,
createdAt: new Date(),
updatedAt: new Date()
};
this.users.push(newUser);
return {
success: true,
data: newUser
};
} catch (error) {
return {
success: false,
error: 'Failed to create user',
message: error.message
};
}
}
async getUserById(id: string): Promise<ApiResponse<User>> {
try {
const user = this.users.find(u => u.id === id);
if (!user) {
return {
success: false,
error: 'User not found'
};
}
return {
success: true,
data: user
};
} catch (error) {
return {
success: false,
error: 'Failed to get user',
message: error.message
};
}
}
async updateUser(id: string, userData: UpdateUserRequest): Promise<ApiResponse<User>> {
try {
const userIndex = this.users.findIndex(u => u.id === id);
if (userIndex === -1) {
return {
success: false,
error: 'User not found'
};
}
const updatedUser = {
...this.users[userIndex],
...userData,
updatedAt: new Date()
};
this.users[userIndex] = updatedUser;
return {
success: true,
data: updatedUser
};
} catch (error) {
return {
success: false,
error: 'Failed to update user',
message: error.message
};
}
}
async deleteUser(id: string): Promise<ApiResponse<boolean>> {
try {
const userIndex = this.users.findIndex(u => u.id === id);
if (userIndex === -1) {
return {
success: false,
error: 'User not found'
};
}
this.users.splice(userIndex, 1);
return {
success: true,
data: true
};
} catch (error) {
return {
success: false,
error: 'Failed to delete user',
message: error.message
};
}
}
private generateId(): string {
return `user_${this.nextId++}`;
}
}
export default new UserService();
API路由设计与实现
路由模块化
创建src/routes/users.ts:
import express, { Router } from 'express';
import userService from '../services/userService';
import { CreateUserRequest, UpdateUserRequest } from '../types';
const router: Router = express.Router();
// 创建用户
router.post('/', async (req, res) => {
try {
const userData: CreateUserRequest = req.body;
const result = await userService.createUser(userData);
if (result.success) {
res.status(201).json(result);
} else {
res.status(400).json(result);
}
} catch (error) {
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
// 获取用户列表
router.get('/', async (req, res) => {
try {
// 这里可以添加分页、排序等逻辑
const users = await userService.getAllUsers();
res.json({
success: true,
data: users,
count: users.length
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Failed to fetch users'
});
}
});
// 获取单个用户
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
const result = await userService.getUserById(id);
if (result.success) {
res.json(result);
} else {
res.status(404).json(result);
}
} catch (error) {
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
// 更新用户
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const userData: UpdateUserRequest = req.body;
const result = await userService.updateUser(id, userData);
if (result.success) {
res.json(result);
} else {
res.status(404).json(result);
}
} catch (error) {
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
// 删除用户
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
const result = await userService.deleteUser(id);
if (result.success) {
res.json(result);
} else {
res.status(404).json(result);
}
} catch (error) {
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
export default router;
主路由配置
更新src/server.ts中的路由配置:
import express, { Application, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';
// 导入路由
import userRoutes from './routes/users';
// 加载环境变量
dotenv.config();
const app: Application = express();
const PORT: number = parseInt(process.env.PORT || '3000', 10);
// 中间件配置
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 路由配置
app.use('/api/users', userRoutes);
// 基础路由
app.get('/', (req: Request, res: Response) => {
res.json({
message: 'Welcome to Microservice Demo',
timestamp: new Date().toISOString()
});
});
// 健康检查端点
app.get('/health', (req: Request, res: Response) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
service: 'user-service'
});
});
// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!',
message: err.message
});
});
// 404处理
app.use((req: Request, res: Response) => {
res.status(404).json({
error: 'Not Found',
message: 'Route not found'
});
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
export default app;
Docker容器化部署
Dockerfile配置
创建Dockerfile:
# 使用官方Node.js运行时作为基础镜像
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建TypeScript项目
RUN npm run build
# 暴露端口
EXPOSE 3000
# 创建非root用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
# 启动应用
CMD ["npm", "start"]
Docker Compose配置
创建docker-compose.yml:
version: '3.8'
services:
user-service:
build: .
container_name: user-service
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
restart: unless-stopped
networks:
- microservice-network
# 数据库服务示例
database:
image: postgres:15-alpine
container_name: user-db
environment:
POSTGRES_DB: userservice
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
networks:
- microservice-network
volumes:
postgres_data:
networks:
microservice-network:
driver: bridge
.dockerignore文件
创建.dockerignore:
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output
Kubernetes部署实践
Kubernetes部署配置
创建k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-deployment
labels:
app: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 80
targetPort: 3000
type: ClusterIP
Ingress配置
创建k8s/ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
rules:
- host: api.userservice.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: user-service
port:
number: 80
ConfigMap配置
创建k8s/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: user-service-config
data:
NODE_ENV: "production"
LOG_LEVEL: "info"
API_TIMEOUT: "5000"
Service Account和RBAC
创建k8s/rbac.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: user-service-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: user-service-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: user-service-binding
subjects:
- kind: ServiceAccount
name: user-service-sa
roleRef:
kind: Role
name: user-service-role
apiGroup: rbac.authorization.k8s.io
监控与日志管理
日志配置
创建src/config/logger.ts:
import winston from 'winston';
import { format, transports } from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: format.combine(
format.timestamp(),
format.errors({ stack: true }),
format.json()
),
defaultMeta: { service: 'user-service' },
transports: [
new transports.Console({
format: format.combine(
format.colorize(),
format.simple()
)
}),
new transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: '50m',
maxFiles: 5
}),
new transports.File({
filename: 'logs/combined.log'
})
]
});
export default logger;
健康检查和监控
更新src/server.ts添加健康检查:
import express, { Application, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';
// 导入路由和日志
import userRoutes from './routes/users';
import logger from './config/logger';
// 加载环境变量
dotenv.config();
const app: Application = express();
const PORT: number = parseInt(process.env.PORT || '3000', 10);
// 中间件配置
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 健康检查端点
app.get('/health', (req: Request, res: Response) => {
logger.info('Health check endpoint accessed');
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
service: 'user-service',
version: process.env.npm_package_version || '1.0.0'
});
});
// API端点
app.use('/api/users', userRoutes);
// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
logger.error(`Error: ${err.message}`, { stack: err.stack });
res.status(500).json({
error: 'Something went wrong!',
message: err.message
});
});
// 404处理
app.use((req: Request, res: Response) => {
logger.warn(`Route not found: ${req.originalUrl}`);
res.status(404).json({
error: 'Not Found',
message: 'Route not found'
});
});
app.listen(PORT, () => {
logger.info(`Server is running on port ${PORT}`);
});
export default app;
性能优化与最佳实践
缓存策略实现
创建src/middleware/cache.ts:
import { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
export const cacheMiddleware = (duration: number = 300) => {
return async (req: Request, res: Response, next: NextFunction) => {
const key = `cache:${req.originalUrl}`;
try {
const cachedData = await redis.get(key);
if (cachedData) {
return res.json(JSON.parse(cachedData));
}
// 重写res.json方法来缓存响应
const originalJson = res.json;
res.json = function(data) {
redis.setex(key, duration, JSON.stringify(data));
return originalJson.call(this, data);
};
next();
} catch (error) {
console.error('Cache error:', error);
next();
}
};
};
请求限流
创建src/middleware/rateLimit.ts:
import { Request, Response, NextFunction } from 'express';
import rateLimit from 'express-rate-limit';
export const createRateLimiter = (maxRequests: number, windowMs: number) => {
return rateLimit({
windowMs,
max: maxRequests,
message: {
error: 'Too many requests',
message: `Too many requests from this IP, please try again after ${windowMs / 1000} seconds`
},
standardHeaders: true,
legacyHeaders: false,
});
};
数据库连接池优化
更新src/config/database.ts:
import { Pool } from 'pg';
import { createPool } from 'generic-pool';
const pool = new Pool({
user: process.env.DB_USER || 'postgres',
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'userservice',
password: process.env.DB_PASSWORD || 'password',
port: parseInt(process.env.DB_PORT || '5432', 10),
max: 20, // 最大连接数
min: 5, // 最小连接数
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// 创建连接池
const connectionPool = createPool({
create: () => pool.connect(),
destroy: (client) => client.release(),
validate: (client) => !client._invalid && !client._ended,
max: 10,
min: 2,
acquireTimeoutMillis: 30000,
idleTimeoutMillis: 30000,
reapIntervalMillis: 1000,
});
export default connectionPool;
测试策略
单元测试配置
创建src/__tests__/userService.test.ts:
import userService from '../services/userService';
import { User, CreateUserRequest } from '../types';
describe('UserService', () => {
beforeEach(() => {
// 清空用户数据
(userService as any).users = [];
});
test('should create a new user', async () => {
const userData: CreateUserRequest = {
name: 'John Doe',
email: 'john@example.com'
};
const result = await userService.createUser(userData);
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
expect(result.data.name).toBe(userData.name);
expect(result.data.email).toBe(userData.email);
});
test('should get user by id', async () => {
const userData: CreateUserRequest = {
name: 'Jane Smith',
email: 'jane@example.com'
};
const createResult = await userService.createUser(userData);
const userId = createResult.data!.id;
const getResult = await userService.getUserById(userId);
expect(getResult.success).toBe(true);
expect(getResult.data!.name).toBe(userData.name);
});
test('should update user', async () => {
const userData: CreateUserRequest = {
name: 'Original Name',
email: 'original@example.com'
};
const createResult = await userService.createUser(userData);
const userId = createResult.data!.id;
const updateData = {
name: 'Updated Name'
};
const updateResult = await userService.updateUser(userId, updateData);
expect(updateResult.success).toBe(true);
expect(updateResult.data!.name).toBe(updateData.name);
});
});
集成测试
创建src/__tests__/api.test.ts:
import request from 'supertest';
import app from '../server';
describe('API Endpoints', () => {
describe('GET /health', () => {
it('should return health check status', async () => {
const response = await request(app)
.get('/health')
.expect(200);
expect(response.body).toHaveProperty('status', 'OK');
expect(response.body).toHaveProperty('service', 'user-service');
});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'Test User',
email: 'test@example.com'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('name', userData.name);
expect(response.body.data).toHaveProperty('email', userData.email);
});
});
});
CI/CD流水线
GitHub Actions配置
创建.github/workflows/ci-cd.yml:
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linting
run: npm run lint
- name: Build application
run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build Docker image
run: |
docker build -t user-service:${{ github.sha }} .
docker tag user-service:${{ github.sha }} user-service:latest
- name: Push to registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push user-service:${{ github.sha }}
docker push user-service:latest
- name: Deploy to Kubernetes
run: |
echo "${{ secrets.KUBECONFIG }}" > kubeconfig
export KUBECONFIG=kubeconfig
kubectl set image deployment/user-service-deployment
评论 (0)