1. 当单体遇到分布式:微服务的前世今生

去年双十一那天,某电商平台的订单服务突然崩溃——这是典型单体架构的"午夜惊魂"。当业务发展到千万级日活时,服务拆分就成为必然选择。就像乐高积木需要模块化设计,我们的应用系统也需要通过微服务实现业务解耦。

在Node.js生态中,Express和NestJS这对"轻量级与重量级"组合,就像是瑞士军刀与多功能工具箱的区别。举个直观的例子:传统Express搭建的简单用户服务可能只有300行代码,而采用NestJS的商品服务却能在同等功能下保持更好的可维护性。


2. 技术栈选择:Express的轻与NestJS的重

2.1 Express的敏捷之刃

(技术栈:Express + TypeScript)

// user-service/app.ts
import express, { Request, Response } from 'express';
import { UserRepository } from './repositories/user.repository';

const app = express();
app.use(express.json());

// 用户查询接口
app.get('/users/:id', async (req: Request, res: Response) => {
    try {
        const user = await UserRepository.findById(req.params.id);
        res.json({ data: user });
    } catch (error) {
        res.status(500).json({ error: '服务不可用' });
    }
});

// 健康检查端点
app.get('/health', (req, res) => {
    res.sendStatus(200);
});

app.listen(3000, () => {
    console.log('用户服务已启动:3000');
});

这段代码展示了典型Express微服务的核心要素:路由管理、错误处理、健康检查。但缺乏依赖注入等企业级功能,当需要实现服务注册等治理功能时,就需要自行封装中间件。


2.2 NestJS的架构之美(技术栈:NestJS + TypeScript)

// product-service/src/product.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { ProductService } from './product.service';

@Controller('products')
export class ProductController {
    constructor(private readonly productService: ProductService) {}

    @Get(':id')
    async getProduct(@Param('id') id: string) {
        return this.productService.findById(id);
    }
}

// product-service/src/product.module.ts
import { Module } from '@nestjs/common';
import { ProductController } from './product.controller';
import { ProductService } from './product.service';

@Module({
    controllers: [ProductController],
    providers: [ProductService]
})
export class ProductModule {}

NestJS通过装饰器和模块化设计,实现了开箱即用的依赖注入。产品服务的领域层、应用层天然分离,配合CLI工具能快速生成服务脚手架。


3. 微服务治理三板斧

3.1 服务发现:微服务的黄页电话簿(技术栈:NestJS + Consul)

// gateway/src/app.module.ts
import { ConsulModule } from '@nest-micro/consul';

@Module({
    imports: [
        ConsulModule.register({
            host: 'consul-server',
            port: '8500',
            service: {
                id: 'gateway',
                name: 'api-gateway'
            }
        })
    ]
})
export class AppModule {}

通过集成Consul实现服务注册发现,就像给每个微服务分配专属名片。当用户服务扩容时,网关会自动感知新实例,智能分配流量。


3.2 配置中心:动态更新的魔法书(技术栈:Express + Apollo)

// order-service/config-loader.js
const { ApolloClient } = require('apollo-client');
const configClient = new ApolloClient({
    configServer: 'http://config:8080',
    appId: 'order-service'
});

// 热更新配置
configClient.on('refresh', (newConfig) => {
    process.env.RATE_LIMIT = newConfig.rateLimit;
});

采用Apollo配置中心实现动态参数调整。当需要紧急调整限流阈值时,无需重启服务即可生效,仿佛给运行中的飞机更换发动机。


3.3 断路器模式:系统的紧急制动器(技术栈:NestJS + Resilience)

// payment-service/src/payment.service.ts
import { CircuitBreaker } from 'resilience-js';

export class PaymentService {
    @CircuitBreaker({
        timeout: 3000,
        errorThreshold: 0.3,
        resetTimeout: 10000
    })
    async processPayment(order: Order) {
        return axios.post('http://bank-service/pay', order);
    }
}

当银行服务响应超时率达到30%时,支付服务会自动熔断,避免雪崩效应。就像电路系统中的保险丝,在过载时主动切断故障链路。


4. 混合架构实战:Express与NestJS的协奏曲

在实际电商系统中,我们可以这样设计:

  • 用户服务(Express):高频但逻辑简单的读取操作
  • 商品服务(NestJS):需要严格领域模型的核心业务
  • 订单服务(NestJS):涉及复杂事务管理的重点模块

混合部署的关键在于统一通信协议。建议所有服务都暴露RESTful接口,并使用JSON-RPC进行内部通信:

// Express服务调用NestJS服务示例
const { JSONRPCClient } = require('json-rpc');

const inventoryClient = new JSONRPCClient({
    url: 'http://inventory-service/rpc'
});

router.post('/orders', async (req, res) => {
    try {
        const stock = await inventoryClient.request('checkStock', req.body);
        // ...后续处理
    } catch (error) {
        res.status(503).json({ error: '库存服务暂不可用' });
    }
});

5. 场景与边界的艺术

5.1 适用场景分析

  • 社交平台:用户关系服务适合用Express快速迭代,即时通讯服务需要NestJS保证稳定性
  • IoT系统:设备管理服务适合NestJS维护状态,数据采集服务可用Express处理高并发
  • 金融系统:账户核心采用NestJS严格分层,对账服务用Express处理批量任务

5.2 技术选型决策树

是否需要类型系统? → 是 → NestJS
         ↓
        否 → 是否需要快速验证原型? → 是 → Express
                          ↓
                         否 → 需要哪些企业级功能?

6. 避坑指南:血泪换来的经验

  1. 服务拆分粒度:某旅游平台曾把机票搜索拆分成10个微服务,导致调试噩梦。建议按照业务变更频率拆分,高频变动的服务独立部署。

  2. 版本地狱:在电商系统中,采用语义化版本控制:

    GET /api/v1/products
    Accept: application/json; version=1.2
    
  3. 日志追踪:使用全链路ID关联日志,ELK方案配置示例:

    morgan(':traceId :method :url')
    
  4. 数据一致性:采用Saga模式时,补偿操作要比主操作更健壮。建议补偿接口实现幂等性:

    @Post('/cancel')
    async cancelOrder(@Body() payload: { id: string, reason: string }) {
        return this.orderService.cancel(payload.id); // 幂等实现
    }
    

7. 未来已来:Serverless与微服务的碰撞

当函数计算遇上微服务架构,新的可能性正在展开。比如将Express服务部署为Serverless函数:

functions:
    userService:
        handler: user-service/app.handler
        events:
            - http: GET /users/{id}

这种混合架构既能享受微服务的灵活性,又能获得Serverless的弹性扩缩能力。但需要注意冷启动问题,可通过预置并发优化。