





















































Hi ,
Welcome to WebDevPro #98! This time, we’re bringing you something different.
Not the news, but the thinking behind the build.
This feature unpacks a practical approach to system design: the one that starts with clarity, not code. It’s based on the ideas from Spring System Design in Practice by Rodrigo Santiago, and reframed here through the lens of day-to-day product work.
If you’ve ever found yourself untangling a feature too late in the game, this one’s for you.
We walk through how product decisions become service boundaries, how design artefacts stack to reduce guesswork, and how to avoid building logic that doesn’t belong.
Along the way, you’ll see what it takes to move from an initial idea to a clean, testable implementation.
Have any ideas you want to see in the next article? Hit Reply!
Your editor,
Kinnari Chohan
Advertise with us
Interested in reaching our audience? Reply to this email or write to kinnaric@packt.com.
Learn more about our sponsorship opportunities here.
Spend long enough shipping web products and you’ll notice a pattern: failed launches rarely crumble under the weight of sophisticated concurrency bugs or obscure network edge-cases. They fail because the team never fully aligned on the problem the software was meant to solve, how success would be measured, or where one person’s responsibility ended and another’s began. The code just makes those early omissions visible.
This piece walks through a lightweight approach for turning raw business ideas into production-ready microservices, without over-engineering or skipping the fundamentals. You’ll learn how to:
We’ll use a fictional rental platform, HomeIt, to ground the examples. It’s a thin wrapper around problems every web engineer encounters: user registration, content publishing, payments, external partners, and traffic spikes.
A well-written requirement is one of the most valuable assets in software development. When it’s incomplete or unclear, the consequences pile up: months of code thrown away, critical flows missing at release, integration bugs appearing late in QA, and launch delays that sap morale and budgets.
Functional versus non-functional requirements
Functional requirements spell out what the system should do: “support Visa payments,” “let tenants send rental proposals.”
Non-functional requirements describe how well it should do it: “process a thousand payments per minute,” “remain available during a regional outage.” One creates value; the other safeguards it. Both are essential.
The quickest way to surface missing pieces is to walk through two states with product owners or clients:
Push for measurable indicators (“mean payment latency under 200 ms”, “95 % of image uploads processed within 30 s”) and ask about edge-cases: alternate user behaviors, upstream service failures, peak-hour traffic.
Capture the answers in a living requirements document; it becomes the north star for architecture, test plans, and scalability budgeting.
Brainstorms fill whiteboards. Backlogs create movement.
In our HomeIt app, the product trio collected dozens of nice-to-have features: messaging, realtor partnerships, and payment automation. To sequence them, they scored each idea on vision, size of problem, ROI, cost, dependencies, and uniqueness.
The top results told a compelling story:
Once a feature rose to the top, the team moved it through four layers of refinement:
For example, take “Tenant sends a rental proposal”. It starts as a journey and breaks into use-case steps (identify property, compose proposal, landlord reviews, tenant receives result) and finally into sprintable sizes such as “Persist proposal draft” or “Notify landlord.”
This funnel keeps developers focused and stakeholders clear on progress without diving into implementation details.
Once shaped, features are locked into quarterly increments. Twelve weeks is short enough to stay responsive to learning yet long enough to deliver a coherent slice of customer value.
Once your user stories are refined, underline every noun. That simple step reveals the backbone of your system. In HomeIt those nouns include Rental Property, Rental Proposal, Payment, User; each a potential domain.
A domain deserves isolation when it owns a clear life-cycle (inactive → published), encapsulates its own rules, can scale independently, and can evolve without side effects for adjacent parts of the system.
Mapping out these relationships sharpens shared understanding. Even a basic domain diagram – boxes for entities, verbs on arrows – can clarify the system on a single page. From that, teams can shape schemas, define service boundaries, or plan event topics.
Take the Rental Property domain in HomeIt, for example. It moves through inactive → processing media → ready → published as a landlord uploads photos and clears validations.
Naming those transitions early helped the team prepare for asynchronous workflows and plan for safe, repeatable state updates, long before a single line was written.
Domain modelling also unlocked a new structure. While sketching, the team noticed that price negotiation didn’t sit cleanly inside proposals or payments. So they carved out a Counteroffer domain to keep responsibilities focused and avoid muddled logic.
Domains define the what. Services handle the how.
A service represents an operation performed within a domain, starting with the basics like create, read, update, and delete (CRUD), then extending into actions that reflect the business itself.
In practice, though, many REST APIs expose only the surface of an object. The real behavior gets buried behind overloaded parameters or generic endpoints, which makes systems harder to understand and even harder to evolve.
To bring clarity, the team ran each domain through four simple lenses:
In the Partnership Proposal domain of the HomeIt, CRUD handles draft management, but business logic demands extra verbs: approveProposal, rejectProposal, and startChat for realtor-landlord communication.
Naming these early helps everyone see where specific endpoints, async events, or even separate services may be needed, before that logic gets buried in brittle glue code.
Linear prose often falls short when describing how services interact over time. That’s where sequence diagrams come in: actors across the x-axis, time flowing down the y-axis, arrows capturing the exchange of messages.
The key is deciding how much detail to include. A payment operations engineer might want to see retries and error handling. A product manager might just need to follow the main steps. The right level of depth depends on who’s reading.
With PlantUML, teams can write diagrams as plaintext: code you can version alongside everything else. Paste a script into plantuml.com, generate an SVG, and share it in a pull request. A small edit to the script redraws the whole diagram; no dragging boxes around in slides.
Sequence diagrams become even more useful when placed next to the domain model. Domains help define the system’s boundaries. Sequences show how those parts collaborate. Used together, they reveal integration risks early, well before testing begins.
The following sequence sketch illustrates that flow, capturing how a tenant initiates a proposal, how the system delegates responsibilities across services, and where retries and idempotent updates come into play:
Here’s how a complete slice of functionality moves through the pipeline, starting from a blank page and ending in production-ready service code.
Problem statement
Landlords often travel. They need a way to partner with local realtors so property showings can continue in their absence.
Feature and backlog
This feature scored 91 out of 150. It’s valuable, but not essential to initial rentals, so it’s scheduled for Q2, after property search and messaging.
Requirements
Before accepting a partnership, the landlord must be able to review availability, fee percentage, and realtor rating. The system needs to handle 10,000 proposals per day and recover from a realtor API outage within five minutes.
Domains
Rental Property, Partnership Proposal, User. Payment remains a separate concern for now.
Services
createProposal, approveProposal, listPartnerships, scheduleVisit.
Domain flow
A realtor fills out the proposal form.
The system records the draft and marks it pending approval.
The landlord accepts.
The proposal status updates to approved.
The realtor’s contact now appears on the property page .
Sequence sketch
Tenant → Search → Property
Realtor → Proposal → Landlord
Proposal → Payments
(Arrows show retries and idempotent updates.)
Implementation choices
Property and Proposal are built as two Spring Boot microservices, each with its own database.
When a ProposalApproved event is triggered, it signals the messaging service to open a chat channel.
Targets for availability (three-nines) and latency (P99 under 400 ms) inform Kubernetes settings and circuit-breaker policies.
Every step builds on the one before it.
By the time coding begins, every engineer understands the requirements, the API contracts, and how the system should recover when things go wrong.
In summary, clarify requirements early. They’re key to saving time and budget. Score features before building to bring structure to decisions. Model nouns first to avoid hidden monoliths, and design services around real business actions. Finally, sketch timelines early to prevent costly last-minute changes.
Here’s your next Sprint challenge
Block off half a day for a deep-dive on requirements. Create a domain diagram. Draft one sequence sketch for an upcoming feature. Pin them beside the sprint board.
You’ll reduce ambiguity now and make onboarding smoother later.