Microservices in JavaScript: Architecture and Practice
Microservice architecture (MSA) has become the de facto standard for building complex, scalable, and fault-tolerant applications. Unlike a monolith, where all code is bound in a single process, microservices break the system into small, independent services. JavaScript, namely the Node.js platform, due to its event-driven model and huge ecosystem, is an excellent choice for implementing microservices. In this article, we will analyze the key principles of the architecture, write several working services, and consider practical aspects of their interaction.
Basic Principles of Microservice Architecture
Before writing code, it is important to understand what makes a microservice a "microservice". The basic principles are:
- Single Responsibility: Each service solves one specific business task (for example, only authorization or only order management).
- Independence: Services are developed, tested, and deployed independently of each other.
- Data Isolation: Each service has its own database. There is no shared repository.
- Loose Coupling: Services communicate over the network (API), not through direct function calls.
- Fault Tolerance: The failure of one service should not bring down the entire system.
Node.js is ideal for these tasks due to its non-blocking nature and asynchronicity, which is critical when dealing with a large number of network calls between services.
Practice: Creating Two Simple Microservices
Let's write two simple services using Express.js: User Service (user management) and Order Service (order management).
1. User Service
Create a file user-service/server.js:
const express = require('express');const app = express();app.use(express.json());
const users = [{ id: 1, name: 'Alice', email: 'alice@example.com' }];
app.get('/users/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (!user) return res.status(404).json({ error: 'User not found' }); res.json(user);});
app.listen(3001, () => console.log('User Service running on port 3001'));2. Order Service
Create a file order-service/server.js:
const express = require('express');const app = express();app.use(express.json());
const orders = [ { id: 1, userId: 1, product: 'Laptop', amount: 1500 }];
app.get('/orders/user/:userId', (req, res) => { const userOrders = orders.filter(o => o.userId === parseInt(req.params.userId)); res.json(userOrders);});
app.listen(3002, () => console.log('Order Service running on port 3002'));Now we have two independent services running on different ports. This is the foundation of microservice architecture.
Interaction Between Services: REST and Message Broker
Services need to communicate. Let's consider two main approaches.
3.1 Synchronous Interaction (REST API)
One service directly calls another service's API. For example, to get orders with the user's name, we will create an API Gateway or a service composer.
// api-gateway.jsconst axios = require('axios');
app.get('/users/:id/orders', async (req, res) => { try { const userResponse = await axios.get(`http://localhost:3001/users/${req.params.id}`); const ordersResponse = await axios.get(`http://localhost:3002/orders/user/${req.params.id}`); res.json({ user: userResponse.data, orders: ordersResponse.data }); } catch (error) { res.status(