Content
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.
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.
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:
greeting-service and call /welcome/Ana again. What does the caller return? How long does it take?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.RestClient and handle the failure with a friendly message instead of a stack trace.localhost to the Compose service name.greeting-service produces a fast, controlled response instead of a hanging request.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.
@Async method into the same class as the caller and call it directly. It runs synchronously again — the proxy is bypassed.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.
cron expression that runs daily at 03:00 (remember: Spring cron has six fields).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.
Map version failed.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.
rabbitmq + api + worker all in Docker Compose. Careful — inside the Compose network, the broker's host is rabbitmq, not localhost.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.
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.
Ctrl+C before it finishes. That task was never acknowledged, so the broker gives it to the other worker. This is at-least-once delivery in action — and exactly why Exercise 7 asked for an idempotent consumer.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/*

Built with ❤️ by the HackYourFuture community · Thank you, contributors
Found a mistake or have a suggestion? Let us know in the feedback form.