Building your first microservices
Chapters 1 to 5 of Bootstrapping Microservices as a hands-on path: design ideas, a first HTTP service, Docker, Docker Compose, storage, a database and messaging.
key idea / coordinatorservice / good pathin progress / watch outproblem / failureneutral
Why microservices: managing complexity
Every application grows complex. Microservices do not remove complexity. They cut it into small parts with hard boundaries, so the part you work on every day stays simple.
A microservice is a small, independent process that runs on its own deployment schedule and can be updated without touching the others. One developer or one small team can own it.
A microservices application is a distributed program: many small services collaborating. Some are exposed (a gateway for web or mobile users), most are not. An orchestration platform (Kubernetes in the book) runs them as a cluster, on a cloud vendor.
| Term | Plain meaning |
|---|---|
| Microservice | small independent process, own deployment schedule, one owner |
| Microservices application | distributed program built from collaborating services |
| Cluster | group of computers presented as one pool of computing power |
| Orchestration | automated management of services (Kubernetes does it here) |
From Bootstrapping Microservices, ch. 1 (sections 1.4 to 1.6).
Design principles and how big a service should be
Designing microservices is ordinary software design. The book keeps to a few rules: do not over-design or future-proof, keep refactoring so the design stays simple, and let a good design emerge. Plan every iteration, but expect the plan to change.
Domain-driven design (DDD) answers "what goes in each service". A bounded context is a bubble where all terms and logic fit together, and it maps well to one microservice. A concept like Video can appear in two contexts with slightly different meanings.
DRY (do not repeat yourself) bends here. The cost of duplicated code is often lower than the cost of a bad shared abstraction, and sharing code across process and team boundaries is hard. Share when it truly makes sense.
From Bootstrapping Microservices, ch. 1 (section 1.14).
The example app, the way of working, the tools
The running example is FlixTube, a small video-streaming product (video streaming is a classic microservices use case). It grows from one service to a full application over the book.
The development philosophy is simple: iterate in small steps, keep it working after every step, and move from simple to complex. If a step goes wrong, revert to the last committed state and lose almost nothing.
| Tool | Why it is here |
|---|---|
| Node.js | network oriented, light, quick to bootstrap an HTTP server; huge community; JavaScript runs on front end and back end |
| Git | commit each working iteration so you can wind back |
| VS Code | editor with terminal; later also used to browse logs and shell scripts |
| Docker, Compose, Kubernetes | introduced in later sections of this lesson and in lesson 19 |
The choice of Node.js and Azure is only an example. Docker, Kubernetes and Terraform exist precisely so you can swap the language and the cloud vendor.
From Bootstrapping Microservices, ch. 1 (section 1.15) and ch. 2 (sections 2.3 to 2.5).
The first microservice: HTTP video streaming
Chapter 2 builds one Express web server with a single route that streams a video file to the browser. The file is read as a stream and piped to the response, so the server never loads the whole video into memory.
Configuration comes from environment variables, the standard way and supported by all the tools used later. The service reads PORT and refuses to start if it is missing, so a forgotten setting fails loudly, not silently.
Two modes follow from this chapter and are reused in every later one. Production mode installs only runtime dependencies and starts with the standard npm start. Development mode adds nodemon, which restarts the service on every code change.
From Bootstrapping Microservices, ch. 2 (sections 2.6.1 to 2.6.8).
Docker: container, image, Dockerfile, registry
A container virtualizes both the operating system and the hardware, so it is lighter than a virtual machine that carries a full guest OS. That is why many containers fit on one computer and microservices stay affordable.
An image is a snapshot (code, dependencies, assets) and a container is a running instance of it. A Dockerfile is the script that builds the image. A registry stores images so any machine with credentials can pull them.
| Command | Job |
|---|---|
| docker build | package the project as an image (the -t option names it) |
| docker image list | list local images with tag and size |
| docker run | start a container from an image, mapping ports and setting variables |
| docker tag, login, push | tag, authenticate and upload the image to the registry |
| docker container list | show running containers |
From Bootstrapping Microservices, ch. 3 (sections 3.3 to 3.10).
Docker Compose for development
Starting and stopping many containers by hand wastes time. The Docker Compose file is a script that describes the whole application as a set of containers. One command builds and starts them; another stops and removes them.
Compose also gives you what a lone service lacks: a reproducible way to boot the whole app, drop one project and start another, and keep several configurations for different slices of the app. Wrap the long commands in small shell scripts.
| Docker Compose | Kubernetes | |
|---|---|---|
| Best at | booting the whole app on a laptop | running the app in production on a cluster |
| Start-up | one file, one command | many commands, many configuration files |
| Weight | light | heavy; a local copy eats laptop resources |
| Scales to | one machine | many machines |
From Bootstrapping Microservices, ch. 4 (section 4.3).
Adding storage and a database
Files go in cloud storage (Azure Storage in the book, but S3 or Google Cloud Storage would do the same job). The video-streaming service does not talk to it directly. A small video-storage microservice hides the provider, following single responsibility. Swapping providers later disturbs only that one service.
The same question applies to where the bytes live. The author prefers a stateless cluster: files and data sit outside Kubernetes, in cloud storage and a managed database. The cluster can then be destroyed and rebuilt at will without data loss, which also enables blue-green deployments (lesson 27).
For records, a MongoDB server starts in Compose from a public image, so a database exists in seconds. Services find it by host name and database name, both given as environment variables. In production the book recommends an external, managed database.
From Bootstrapping Microservices, ch. 4 (sections 4.4 and 4.5).
The history service and live reload in Compose
The new history microservice records which videos a user watched. It receives a "viewed" message from the streaming service and writes it to its own database. It is the example used to explore messaging, and later it can feed recommendations.
Now the single Dockerfile splits in two. Dockerfile-dev favors fast iteration; Dockerfile-prod favors performance and security. Keep them side by side so the dev one does not drift far ahead.
Production mode should not be left for deployment day. Keep a second Compose file for it and run it now and then, for example with the -f option, so problems do not pile up unseen. Test dev mode every few minutes, production mode every few hours.
From Bootstrapping Microservices, ch. 5 (sections 5.4 and 5.5).
Direct and indirect messaging
Services must cooperate, and the book names two styles. It prefers direct and indirect over "synchronous" and "asynchronous", because those words already mean something else to programmers.
Direct messages over HTTP are easy: HTTP and DNS are everywhere, and in Compose the container name is the host name. One controller can orchestrate a strict sequence, sending a message, waiting for the reply, then sending the next. The price is tight coupling.
| Direct (HTTP) | Indirect (RabbitMQ) | |
|---|---|---|
| Target | a named service | a queue or exchange |
| Reply | immediate response | none; sender may not know a receiver exists |
| Coupling | tight | loose |
| Control | one service can orchestrate | behavior emerges from the services |
| Good for | strict sequences, commands | notifications and evolvable designs |
From Bootstrapping Microservices, ch. 5 (sections 5.6 and 5.7).
Indirect messaging with RabbitMQ
RabbitMQ is a mature broker built on AMQP, started in Compose from a public image. Services use the amqplib library. Messages are published to a queue or an exchange. The sender resolves the broker by DNS and never meets the receiver.
A service "asserts" a queue, meaning it creates it only if it does not exist yet, so many services can share it. Consumers acknowledge each message once handled.
Add a new consumer and the sender never changes. That is the benefit and the risk. With no central controller, behavior emerges from the interplay of messages. It is harder to read, but there is no single point of failure orchestrating everything.
From Bootstrapping Microservices, ch. 5 (section 5.8).
Recap
- Microservices manage complexity by splitting it into small parts with hard boundaries. Right-sized beats smallest.
- Use single responsibility, loose coupling, high cohesion; DDD bounded contexts guide boundaries. Tolerate some duplication over bad sharing.
- Work in small, working, committed iterations, from simple to complex.
- Configure with environment variables; use npm start for production and nodemon for development.
- Docker packages the service: Dockerfile builds an image, a registry stores it, a container runs it.
- Compose boots the whole app in development. Production uses Kubernetes, not Compose.
- Hide storage behind a service, keep the cluster stateless, and give each service its own database.
- Split Dockerfile-dev and Dockerfile-prod; share the source folder with the container for live reload.
- Direct messages (HTTP) are simple and orchestrated but coupled. Indirect messages (RabbitMQ queues and exchanges) are loose and emergent.
Quick check
Show answer
The database becomes an integration point. The services are coupled through data, can no longer change independently, and the shared database becomes a bottleneck. Give each its own database, even if on one server.
Show answer
An exchange bound to one anonymous queue per consumer. A single shared queue would give each message to only one of them.
Show answer
Data lives outside, so the cluster can be destroyed and rebuilt freely, which also makes blue-green upgrades possible.