Hello everyone and welcome back,
Today we'll cover the second part of the microservices application - the code for the individual services.
To quickly recall from the first part, the services we'll build today are:
- Products: Used to list and search for products.
- Orders: Used to place new orders. This is protected by the auth server so users can't place orders unless they're logged in.
- Notifications: Used to notify users when a new order is placed.
- Auth: Used to check user authentication before placing orders.
So, without any more delay, let's start
PS: The code here is incomplete, for the full code check out the Github repo
Products Service
This will be a simple Node, Express REST API. There's one GET
endpoint to list all products. The code is like:
const express = require('express');
const app = express();
app.get('/', (req, res, next) => {
const { ids } = req.query;
let resultProducts = products;
if (ids) {
const productIds = ids.split(',');
resultProducts = products.filter((p) => {
return productIds.includes(p.id));
}
}
res.status(200).json(resultProducts);
});
You'll notice we have a query parameter named ids
. We'll use this to fetch a subset of all the products by their id.
Also, for simplicity, the complete list of products is coming from a local variable here.
Orders Service
This is also a Node, Express REST API, but not quite as simple as the products one. There's still only one POST
endpoint, but since it does multiple things, let's look at the code in pieces.
First, we extract the userId
and productIds
from the request body and use the productIds
to fetch more product details (mainly prices and names) from the products service:
const { productIds, userId } = req.body;
const { data: orderedProducts } = await axios.get(
`${PRODUCTS_APP_HOST}/?ids=${productIds.join(',')}`
);
Then, we calculate the total amount for the order and a comma separated names list of the products:
let totalAmount = 0;
let orderedProductNames = '';
for (const product of orderedProducts) {
totalAmount += product.price;
orderedProductNames += orderedProductNames === '' ? product.name : `, ${product.name}`
}
Lastly, we publish a message for the nats-broker
(we'll look at the notification service next) and send a response back to the API request. Similar to the products service, there's no database here also, so we just return a plain JSON object with a random id in the response.
natsClient.sendMessage(
NEW_ORDER_MESSAGING_CHANNEL,
`Hello ${userId}, your order is confirmed. Products: ${orderedProductNames}`
);
res.status(201).json({
productIds,
totalAmount,
userId,
id: Math.floor(Math.random() * 9999)
});
Notifications Service
This service is a Nats subscriber, listening for messages published to the "new order placed channel". For now, we just log the incoming messages to the console, but in a real world scenario we can notify users via SMS, Emails, etc.
The code consists of 2 functions, one is the handler for messages and another to start the subscriber.
function handleMessages(err, message) {
if (err) {
console.log(`Error while reading messages: ${err}`);
return;
}
console.log(`Message received on channel "${NEW_ORDER_MESSAGING_CHANNEL}"`);
console.log(message);
}
async function main() {
await natsClient.startup();
natsClient.subscribe(NEW_ORDER_MESSAGING_CHANNEL, handleMessages);
}
main().catch(console.log);
The nats-client
code
This is a common file and can be used by any service who wants to talk to the Nats broker.
const { connect, StringCodec } = require('nats');
const NATS_BROKER_URL = process.env.NATS_BROKER_URL;
class NatsClient {
#conn;
#codec;
constructor() {
this.#codec = StringCodec();
}
async startup() {
this.#conn = await connect({ servers: NATS_BROKER_URL });
console.log(`Connected to nats broker on "${this.#conn.getServer()}"`);
}
sendMessage(channel, message) {
this.#conn.publish(
channel,
this.#codec.encode(message)
);
}
subscribe(channel, cb) {
this.#conn.subscribe(channel, {
callback: (err, payload) => {
cb(err, this.#codec.decode(payload.data))
}
});
console.log(`Subscribed to messaging channel "${channel}"`);
}
}
Currently, the orders service uses the sendMessage
method and the notifications service uses the subscribe
method.
Auth Service
This is again a REST API. But, just to mix things up a bit, it's built in Python using the FastAPI framework. Currently, there's only one endpoint to verify the Authorization
header, which checks for a hardcoded token value.
@app.get("/verify")
def verify_token(authorization: Annotated[str | None, Header()] = None):
if authorization != "secret-auth-token":
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content=None
)
return JSONResponse(
status_code=status.HTTP_200_OK,
content=None
)
Wrap up
That was all for the individual services code. In the next part we'll look at the root level docker-compose.yml
files and the nginx.conf
file to start all the components of the application and make things work.
I hope you'll enjoy this series and learn something new.
Feel free to post any questions you have in the comments below.
Cheers!
Top comments (0)