- Section 02: A Mini-Microservices App
- Table of Contents
- App Overview
- Project Setup
- Posts Service Creation
- Implementing a Comments Service
- React Project Setup
- Request Minimization Strategies
- An Async Solution
- Common Questions Around Async Events
- Event Bus Overview
- A Basic Event Bus Implementation
- Emitting Post Creation Events
- Emitting Comment Creation Events
- Receiving Events
- Creating the Data Query Service
- Parsing Incoming Events
- Using the Query Service
- Adding a Simple Feature
- Issues with Comment Filtering
- A Second Approach
- How to Handle Resource Updates
- Creating the Moderation Service
- Adding Comment Moderation
- Handling Moderation
- Updating Comment Content
- Dealing with Missing Events
Goals
- Get a taste of a microservices architecture
- Build as much as possible from scratch
What services should we create?
- For now, we will create one separate service for each resource in our app
Initial App Setup
- Generate a new React App using Create-React-App
- Create an Express-based project for the Posts Service
- Create an Express-based project for the Comments Service
Notes on Sync Communication
Pro | Con |
---|---|
Conceptually easy to understand! | Introduces a dependency between services |
If any inter-service request fails, the overall request fails | |
The entire request is only as fast as the slowest request | |
Can easily introduce webs of requests |
Notes on Async Communication
Pros | Cons |
---|---|
Query Service has zero dependencies on other services! | Data duplication. |
Query Service will be extremely fast! | Harder to understand |
Wait, so you're saying we need to create a new service every time we need to join some data?!?!?!?!?!?
Absolutely not! In reality, might not even have posts and comments in separate services in the first place
Who cares that each service is independent?
Independent services + the reliability that brings is one of the core reasons of using microservices in the first place
This is so over the top complicated for little benefit
Seems that way now! Adding in some features starts to get really easy when we use this architecture
This system won't correctly in the following scenario....
There are some very special things we need to consider with this design. I've got solutions for most (maybe?) of the concerns you may have
Event Bus
- Many different implementations. RabbitMQ, Kafka, NATS...
- Receives events, publishes them to listeners
- Many different subtle features that make async communication way easier or way harder
- We are going to build our own event bus using Express. It will not implement the vast majority of features a normal bus has.
- Yes, for our next app we will use a production grade, open source event bus
// event-bus/index.js
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const app = express();
app.use(bodyParser.json());
app.post('/events', (req, res) => {
const event = req.body;
axios.post('http://localhost:4000/events', event);
axios.post('http://localhost:4001/events', event);
axios.post('http://localhost:4002/events', event);
res.send({ status: 'OK' });
});
app.listen(4005, () => {
console.log('Listening on 4005');
});
// posts/index.js
const posts = {};
app.post('/posts', async (req, res) => {
const id = randomBytes(4).toString('hex');
const { title } = req.body;
posts[id] = {
id,
title
};
await axios.post('http://localhost:4005/events', {
type: 'PostCreated',
data: {
id,
title
}
});
res.status(201).send(posts[id]);
});
// comments/index.js
const commentsByPostId = {};
app.post('/posts/:id/comments', async (req, res) => {
const commentId = randomBytes(4).toString('hex');
const { content } = req.body;
const comments = commentsByPostId[req.params.id] || [];
comments.push({ id: commentId, content });
commentsByPostId[req.params.id] = comments;
await axios.post('http://localhost:4005/events', {
type: 'CommentCreated',
data: {
id: commentId,
content,
postId: req.params.id
}
});
res.status(201).send(comments);
});
// posts/index.js
// comments/index.js
app.post('/events', (req, res) => {
console.log('Received Event', req.body.type);
res.send({});
});
// query/index.js
app.get('/posts', (req, res) => {});
app.post('/events', (req, res) => {});
// query/index.js
app.get('/posts', (req, res) => {
res.send(posts);
});
app.post('/events', (req, res) => {
const { type, data } = req.body;
if (type === 'PostCreated') {
const { id, title } = data;
posts[id] = { id, title, comments: [] };
}
if (type === 'CommentCreated') {
const { id, content, postId } = data;
const post = posts[postId];
post.comments.push({ id, content });
}
console.log(posts);
res.send({});
});
Feature Request
- Add in comment moderation.
- Flag comments that contain the word 'orange'.
Feature Clarifications
- Super easy to implement in the React app, but not if the filter list changes frequently
- Super easy to implement in the existing comments service, but let's assume we want to add a new service
- It might take a long time for the new service to moderate a comment.
- The query service is about presentation logic
- It is joining two resources right now (posts and comments), but it might join 10!
- Does it make sense for a presentation service to understand how to process a very precise update?
// moderation/index.js
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const app = express();
app.use(bodyParser.json());
app.post('/events', (req, res) => {});
app.listen(4003, () => {
console.log('Listening on 4003');
});