Nomenclature in Microservices Architecture: Modules, Components, and Services
“Naming things is hard!” is a common saying in developer circles. One thing that has confused me more than once is how to differentiate between modules, components, and services in the context of microservices.
But recently, I watched this talk by Ian Cooper, where, at some point, he clarifies what is what and how these three terms relate to each other. In this blog post, I’ll summarize the talk and provide my take on the matter.
TL;DR: A module is a named collection of code; components are modules that are independent and easily replaceable; services are components that run in their own process. All services are components, and all components are modules—but not all modules are components, and not all components are services.
Module
A named collection of code that encapsulates related functionality.
Here is one of the formal definitions of a module:
“A module is a lexically contiguous sequence of program statements, bounded by boundary elements, having an aggregate identifier.”
– Yourdon, E., & Constantine, L. L. (1979). Structured design: Fundamentals of a discipline of computer program and systems design. Prentice Hall.
So, a module is a piece of our program that is separated from other pieces of code and that we can reference by name.
// Logger module
export const error = (message: string) => {
console.log(message);
}
export const warn = (message: string) => {
console.warn(message);
}
Component
An independent, easily replaceable module.
In his talk, Ian describes components as modules that are “independently replaceable and upgradeable.” Their key differentiating characteristic is being replaceable. It should be possible to replace a component without impacting other parts of the system.
// logger.interface.ts
export interface Logger {
error: (message: string) => void;
warn: (message: string) => void;
}
// Console logger component
import type { Logger } from './logger.interface';
export const consoleLogger: Logger = {
error: (message: string) => {
console.error(message);
},
warn: (message: string) => {
console.warn(message);
}
}
// File logger component
import type { Logger } from './logger.interface';
export const fileLogger: Logger = {
error: (message: string) => {
fs.writeFileSync('error.log', message);
},
warn: (message: string) => {
fs.writeFileSync('warn.log', message);
}
}
// Usage
const app = makeApp({
// The logger component is easily replaceable
logger: consoleLogger,
// logger: fileLogger,
});
Service
A component that runs in its own process.
Ian defines a service as “a component that exists in its own process,” which clients communicate with over some inter-process communication mechanism such as RPC, REST, HTTP, messaging, etc. Since a service is a type of component, it inherits the requirement of being independently replaceable. By running in its own process, a service creates a stronger boundary than in-process components and modules.
// Logging service
import express from 'express';
// Might instead use the logger module and other modules and components!
import { fileLogger } from './logger';
const app = express();
app.use('/api/logs/error', (req, res) => {
fileLogger.error(req.body);
res.sendStatus(200);
});
app.use('/api/logs/warn', (req, res) => {
fileLogger.warn(req.body);
res.sendStatus(200);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});