Microservices framework
Core Concepts of Microservices Architecture
Microservices architecture is a development approach that divides a single application into a set of small services, each running in its own process and communicating through lightweight mechanisms (typically HTTP APIs). These services are built around business capabilities, can be deployed independently, and may use different programming languages and data storage technologies. Node.js, with its lightweight, event-driven, and non-blocking I/O model, is an ideal choice for implementing microservices.
A typical microservices architecture includes the following key components:
- Service Discovery
- API Gateway
- Configuration Center
- Circuit Breaker
- Distributed Tracing
Node.js Microservices Framework Options
Express + Related Middleware
Express is the most basic Node.js web framework, which can be used to build microservices when combined with various middleware:
const express = require('express');
const { json } = require('body-parser');
const app = express();
app.use(json());
app.get('/products/:id', async (req, res) => {
const product = await productService.get(req.params.id);
res.json(product);
});
app.listen(3000, () => console.log('Product service started'));
NestJS
NestJS is a progressive Node.js framework with built-in support for microservices architecture:
import { Controller, Get } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class AppController {
@MessagePattern({ cmd: 'sum' })
accumulate(data: number[]): number {
return (data || []).reduce((a, b) => a + b);
}
}
Fastify
Fastify is a high-performance, low-overhead framework suitable for building microservices:
const fastify = require('fastify')({ logger: true });
fastify.get('/users', async (request, reply) => {
return userService.getAll();
});
fastify.listen(3000, (err) => {
if (err) throw err;
});
Service Communication Mechanisms
REST API
The most commonly used synchronous communication method:
// Using axios to call other services
const axios = require('axios');
async function getOrderDetails(orderId) {
try {
const response = await axios.get(`http://order-service/orders/${orderId}`);
return response.data;
} catch (error) {
console.error('Failed to fetch order', error);
throw error;
}
}
gRPC
A high-performance RPC framework:
// order.proto
service OrderService {
rpc GetOrder (OrderRequest) returns (OrderResponse);
}
message OrderRequest {
string id = 1;
}
message OrderResponse {
string id = 1;
string status = 2;
}
// Client implementation
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('order.proto');
const orderProto = grpc.loadPackageDefinition(packageDefinition);
const client = new orderProto.OrderService(
'localhost:50051',
grpc.credentials.createInsecure()
);
client.GetOrder({ id: '123' }, (err, response) => {
console.log('Order:', response);
});
Message Queue (RabbitMQ/Kafka)
Example of asynchronous communication:
const amqp = require('amqplib');
async function consumeOrders() {
const conn = await amqp.connect('amqp://localhost');
const channel = await conn.createChannel();
const queue = 'orders';
await channel.assertQueue(queue, { durable: true });
channel.consume(queue, (msg) => {
const order = JSON.parse(msg.content.toString());
processOrder(order);
channel.ack(msg);
});
}
Service Discovery and Load Balancing
Consul Integration
const consul = require('consul')({ host: 'consul-server' });
// Service registration
consul.agent.service.register({
name: 'product-service',
address: 'localhost',
port: 3000,
check: {
http: 'http://localhost:3000/health',
interval: '10s'
}
}, (err) => {
if (err) throw err;
});
// Service discovery
consul.catalog.service.nodes('order-service', (err, result) => {
const instances = result.map(instance => `http://${instance.ServiceAddress}:${instance.ServicePort}`);
});
Kubernetes Service Discovery
In a K8s environment:
const dns = require('dns');
dns.resolve('order-service.default.svc.cluster.local', (err, addresses) => {
console.log('Available order service instances:', addresses);
});
Configuration Management
Using ConfigMap
const config = {
db: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432
},
redis: {
url: process.env.REDIS_URL || 'redis://localhost:6379'
}
};
Configuration Center (e.g., Apollo)
const { ApolloClient } = require('apollo-client');
const client = new ApolloClient({
configServerUrl: 'http://config-server:8080',
appId: 'product-service',
clusterName: 'default'
});
client.getConfig('db.url').then(url => {
console.log('Database URL:', url);
});
Circuit Breaking and Fault Tolerance
Using the Hystrix Pattern
const circuitBreaker = require('opossum');
const breaker = new circuitBreaker(async (url) => {
const response = await axios.get(url);
return response.data;
}, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
breaker.fallback(() => 'Service unavailable, please try later');
// Using the circuit breaker
breaker.fire('http://order-service/orders')
.then(console.log)
.catch(console.error);
Distributed Tracing
Jaeger Integration
const { initTracer } = require('jaeger-client');
const config = {
serviceName: 'product-service',
sampler: {
type: 'const',
param: 1,
},
reporter: {
logSpans: true,
agentHost: 'jaeger-agent',
},
};
const tracer = initTracer(config);
function getProduct(ctx, id) {
const span = tracer.startSpan('getProduct', { childOf: ctx.span });
// ...Business logic
span.finish();
}
Containerized Deployment
Dockerfile Example
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Kubernetes Deployment File
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-service
spec:
replicas: 3
selector:
matchLabels:
app: product-service
template:
metadata:
labels:
app: product-service
spec:
containers:
- name: product
image: product-service:1.0.0
ports:
- containerPort: 3000
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: db.host
Monitoring and Logging
Prometheus Metrics
const prometheus = require('prom-client');
const httpRequestDurationMicroseconds = new prometheus.Histogram({
name: 'http_request_duration_ms',
help: 'Duration of HTTP requests in ms',
labelNames: ['method', 'route', 'code'],
buckets: [0.1, 5, 15, 50, 100, 200, 300, 400, 500]
});
app.use((req, res, next) => {
const end = httpRequestDurationMicroseconds.startTimer();
res.on('finish', () => {
end({ method: req.method, route: req.route.path, code: res.statusCode });
});
next();
});
ELK Log Collection
const { createLogger, transports } = require('winston');
const { ElasticsearchTransport } = require('winston-elasticsearch');
const esTransport = new ElasticsearchTransport({
level: 'info',
clientOpts: { node: 'http://elasticsearch:9200' }
});
const logger = createLogger({
transports: [esTransport]
});
logger.info('Service started', { timestamp: new Date() });
Testing Strategies
Unit Test Example
const { getProduct } = require('./product-service');
const mockDb = require('./mock-db');
describe('Product Service', () => {
beforeAll(() => {
mockDb.setup();
});
it('should return product by id', async () => {
const product = await getProduct('123');
expect(product.id).toBe('123');
expect(product.name).toBeDefined();
});
});
Contract Testing (Pact)
const { Pact } = require('@pact-foundation/pact');
describe('Product Service', () => {
const provider = new Pact({
consumer: 'OrderService',
provider: 'ProductService',
port: 1234
});
beforeAll(() => provider.setup());
afterEach(() => provider.verify());
afterAll(() => provider.finalize());
describe('GET /products/123', () => {
beforeAll(() => {
return provider.addInteraction({
state: 'product 123 exists',
uponReceiving: 'a request for product 123',
withRequest: {
method: 'GET',
path: '/products/123'
},
willRespondWith: {
status: 200,
body: {
id: '123',
name: 'Test Product'
}
}
});
});
it('should return product 123', async () => {
const response = await axios.get(`${provider.mockService.baseUrl}/products/123`);
expect(response.data.id).toBe('123');
});
});
});
Security Considerations
JWT Validation
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: 'https://auth.example.com/.well-known/jwks.json'
}),
audience: 'product-service',
issuer: 'https://auth.example.com/',
algorithms: ['RS256']
});
app.use(jwtCheck);
Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // 100 requests per IP
});
app.use('/api/', limiter);
Performance Optimization
Caching Strategy
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });
app.get('/products/:id', async (req, res) => {
const cacheKey = `product_${req.params.id}`;
let product = cache.get(cacheKey);
if (!product) {
product = await productService.get(req.params.id);
cache.set(cacheKey, product);
}
res.json(product);
});
Connection Pool Management
const { Pool } = require('pg');
const pool = new Pool({
max: 20, // Maximum connections
idleTimeoutMillis: 30000, // Idle connection timeout
connectionTimeoutMillis: 2000 // Connection timeout
});
async function query(text, params) {
const client = await pool.connect();
try {
return await client.query(text, params);
} finally {
client.release();
}
}
Continuous Integration and Delivery
GitHub Actions Example
name: Node.js CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '16'
- run: npm ci
- run: npm test
- run: npm run build
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: kubectl apply -f k8s/
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:部署工具
下一篇:Serverless应用