Week 14

Architecture Patterns

Communication Patterns

Background Processing

Architectural Trade-offs

Messaging

Hands-on: RabbitMQ

Practice

Assignment

Backend Track

Content

Let’s get practical

These exercises follow the chapters of this week. They are for self-study — nothing to submit. Each one takes between ten minutes and an hour, and they are independent: pick the ones that cover what you want to practise.

Exercise 1 — Make the architecture call

No code for this one. For each situation below, decide monolith or microservices, and write down one sentence explaining why. Then compare your answers with the trade-off table in the Architecture Patterns chapter.

  1. Two developers are building the first version of a booking app for a local gym.
  2. A video platform where uploads need twenty times more computing power than the rest of the site, and traffic to the rest of the site is small.
  3. A company with eight teams that all wait for each other before every release.
  4. A student project that must be finished and deployed in three weeks.

Exercise 2 — Two services talking over HTTP

Create two small Spring Boot applications. greeting-service exposes GET /greetings/{name} and returns a message. frontdesk-service exposes GET /welcome/{name}, calls the greeting service with a RestClient, and returns what it received.

Run them on two ports (for example 8080 and 8081) and check that the whole chain works in the browser.

Now break it on purpose:

  1. Stop greeting-service and call /welcome/Ana again. What does the caller return? How long does it take?
  2. Add a Thread.sleep(10000) to the greeting endpoint, restart it, and call /welcome/Ana once more. Notice that your user is now waiting for someone else's slowness.
  3. Configure a read timeout of two seconds on the RestClient and handle the failure with a friendly message instead of a stack trace.

Exercise 3 — Move slow work off the request path

Take any endpoint and make it slow on purpose: have it call a service method that sleeps for five seconds and then logs finished. Time the request — five seconds.

Now add @EnableAsync to your application class and @Async to the slow method. Time the request again: it should return immediately, with finished appearing in the logs five seconds later.

Look closely at the logs: the thread name in the @Async line is different from the one handling the request. That is the whole trick.

Exercise 4 — Scheduled cleanup job

In any Spring Boot project (or a fresh one from start.spring.io), create a table with a timestamp column — for example notes with created_at. Write a job with @Scheduled(fixedDelay = 60000) that deletes rows older than 10 minutes and logs how many it removed. Watch it fire every minute.

Exercise 5 — Prove your service is stateless

Add two endpoints to a small application: POST /notes stores a note in an in-memory Map, and GET /notes/{id} reads it back. Confirm it works.

Now start the same application twice, on ports 8080 and 8081. Create a note on 8080, then try to read it from 8081.

The note is gone — each instance has its own memory. Imagine a load balancer sending your users to a random instance every time. Now move the storage to your PostgreSQL database and repeat: the note is readable from both instances, and it survives a restart.

Exercise 6 — A separate worker process

In the Hands-on chapter, one application both produced and consumed. Split it into two processes from the same codebase using Spring profiles (Week 11): put @Profile("worker") on TaskWorker, so only an instance started with that profile consumes.

Run the API normally, and start a second instance with SPRING_PROFILES_ACTIVE=worker (give it another port, e.g. SERVER_PORT=8081). POST tasks to the API and watch the worker's logs process them.

Exercise 7 — Kill, restart, and deduplicate

Stop the worker. Publish three messages from the management UI and watch Ready: 3. Start the worker and watch the queue drain.

Then make the consumer idempotent: treat each message as a task ID (like task-42), remember which IDs were already processed (an in-memory Set is fine, a database table is better), and skip duplicates with a log line like already processed, skipping. Publish the same ID twice to prove it.

Exercise 8 — Scale the workers

Keep the slow Thread.sleep(3000) in your consumer. Start two worker instances (two terminals, different ports, same worker profile), then send ten tasks in a row.

Watch both workers' logs: the broker hands each message to exactly one of them, so the two workers share the batch and the queue drains roughly twice as fast. Nothing in your code changed — you scaled by starting another process.

Exercise 9 — Trace across services

Make the task ID appear in every log line about it, in both processes: the API logs accepted task-42, the worker logs processing task-42 and done task-42. Pick one ID and reconstruct its full journey using only the two log outputs.


The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

CC BY-NC-SA 4.0 Icons

Built with ❤️ by the HackYourFuture community · Thank you, contributors

Found a mistake or have a suggestion? Let us know in the feedback form.