01. From one box to many classes — draw the inside before coding¶
~14 min read. One HLD box looks simple, until code asks awkward questions.
Built on the ELI5 in 00-eli5.md. The floor plan — class diagram or schema design — tells us where each class must sit.
1) Start with one service box, not the whole city¶
In HLD, you may draw one box called Tweet Service. That box is fine for architecture review. It is useless for writing code on Monday. See. Code needs names, methods, fields, and contracts.
So what to do? Open one box and ask five small questions.
- What data must this box remember?
- What actions must this box perform?
- Which actions change state?
- Which actions only read state?
- Which outside systems does this box touch?
Now break the box into candidate rooms. Each room should own one clear job. Do not start with patterns. Start with responsibilities.
A quick first cut for Tweet Service may look like this.
┌──────────────────────┐
│ Tweet Service box │
└──────────┬───────────┘
│ open it
▼
┌────────────┬────────────┬──────────────┐
│ Tweet │ TweetApp │ TweetRepo │
│ entity │ service │ persistence │
└────────────┴────────────┴──────────────┘
Notice the split. Data shape becomes an entity. Use cases become an application service. Storage becomes a repository. Already the floor plan is becoming visible.
A bad decomposition sounds like this. "Let one TweetManager do everything." That one class validates input, writes SQL, checks auth, sends notifications, formats responses, and logs metrics. Simple at first. Painful by sprint three.
A better decomposition sounds like this. "Let one class orchestrate, and helpers do focused work." Simple, no?
2) Find entities, services, repositories, and contracts¶
Use nouns first. Nouns often reveal entities and value objects. For Tweet Service, nouns are tweet, user, timeline, hashtag.
Then use verbs. Verbs often reveal service methods. Create, delete, like, retweet, fetch timeline.
Now map them.
Noun → likely code shape
Tweet → Tweet entity
User → UserRef value object
Hashtag→ Hashtag parser or value object
Verb → likely code shape
createTweet → TweetService method
getTimeline → TimelineService method
save → TweetRepository method
Three common buckets help.
Entities¶
Entities carry identity and core state. A tweet has tweetId, authorId, text, createdAt. A tweet may also know its own invariants. Text length cannot exceed 280. Deleted tweets cannot be liked.
Services¶
Services coordinate steps across objects. They answer use-case questions. Can this user post now? Should hashtags be indexed? Whom should we notify?
Repositories¶
Repositories hide storage details. The rest of code should not care about SQL or DynamoDB. That hidden path is the hallway. The hallway is a contract, not a table scan.
A first pass class list can be tiny.
class Tweet {
TweetId id;
UserId authorId;
String text;
Instant createdAt;
}
interface TweetRepository {
void save(Tweet tweet);
Optional<Tweet> findById(TweetId id);
}
class TweetService {
private final TweetRepository tweetRepository;
TweetId createTweet(CreateTweetCommand command) { ... }
}
See what happened. We did not start with ten classes. We started with the minimum believable set. That matters in interviews and real teams.
3) Work one use case end to end¶
Let us decompose one use case with numbers. Requirement: user can post one tweet every 30 seconds. Requirement: text max length is 280. Requirement: hashtag count max is 5.
Now the flow becomes concrete.
Create tweet request
│
▼
┌──────────────────────┐
│ TweetController │ receives JSON
└──────────┬───────────┘
▼
┌──────────────────────┐
│ TweetService │ orchestrates checks
└───────┬───────┬──────┘
│ │
│ ├────────→ RateLimitPolicy
│ ├────────→ HashtagExtractor
│ └────────→ TweetRepository
▼
Tweet entity
Now assign responsibilities, one by one.
- Controller parses transport concerns.
- Service coordinates business steps.
- Entity guards tweet invariants.
- Repository stores and fetches tweets.
- Policy object checks rate limit rule.
Here is a concrete sequence.
- Receive
{authorId: 42, text: "Hello #lld"}. - Check text length, say 10 characters.
- Extract hashtags, say 1 hashtag.
- Check recent tweets by user 42.
- If last tweet was 18 seconds ago, reject.
- Else create tweet and save.
Code sketch:
class TweetService {
private final TweetRepository tweetRepository;
private final RateLimitPolicy rateLimitPolicy;
private final HashtagExtractor hashtagExtractor;
TweetId createTweet(CreateTweetCommand command) {
if (command.text().length() > 280) {
throw new IllegalArgumentException("Tweet too long");
}
List<String> hashtags = hashtagExtractor.extract(command.text());
if (hashtags.size() > 5) {
throw new IllegalArgumentException("Too many hashtags");
}
rateLimitPolicy.checkCanPost(command.authorId());
Tweet tweet = Tweet.create(command.authorId(), command.text());
tweetRepository.save(tweet);
return tweet.id();
}
}
This is where many candidates slip. They push every rule into the service. Then the service becomes fat. Some rules belong inside the entity. Some belong in a policy. Some belong in infrastructure.
So what to do? Keep orchestration in service. Keep core truth near the entity. Keep storage behind repository.
4) Draw a reviewable class map¶
Before coding ten files, draw one small diagram. That is your working floor plan. It need not be pretty. It must be reviewable.
┌──────────────────────┐
│ TweetController │
└──────────┬───────────┘
▼
┌──────────────────────┐ ┌──────────────────────┐
│ TweetService ├─────→│ RateLimitPolicy │
└───────┬──────────────┘ └──────────────────────┘
├────────────────────→┌──────────────────────┐
│ │ HashtagExtractor │
│ └──────────────────────┘
└────────────────────→┌──────────────────────┐
│ TweetRepository │
└──────────┬───────────┘
▼
┌─────────┐
│ Tweet │
└─────────┘
A decent class map answers these questions.
- Which class owns business orchestration?
- Which class owns validation invariants?
- Which class touches the database?
- Which dependency can be mocked in tests?
- Which dependency is just a utility?
One more sanity check helps. If you cannot explain each class in one sentence, your decomposition is still muddy. That means the rooms are not clear yet. Go back once. Save pain later.
Where this lives in the wild¶
- At X, a backend engineer splits the Notifications service into template resolver, channel sender, and delivery repository.
- At Swiggy, a backend engineer decomposes Cart service into cart aggregate, pricing service, and cart repository.
- At Uber, a marketplace engineer breaks Driver Incentives into rules engine, payout calculator, and incentive store.
- At Razorpay, a payments engineer separates Payment Link service into link entity, expiry policy, and persistence adapter.
- At LinkedIn, a feed engineer decomposes Post service into post entity, visibility policy, and feed fanout coordinator.
Pause and recall¶
- What is the first mistake in turning one HLD box into code?
- Why should repositories hide storage details behind a hallway?
- Which responsibilities belong in service versus entity?
- What five questions should you ask before drawing classes?
Interview Q&A¶
Why entity plus service, not one giant manager class?¶
Because entity owns state truth, while service coordinates use cases. That split improves tests, change safety, and readability. Common wrong answer to avoid: "More classes always means better design."
Why repository, not SQL directly inside service?¶
Because storage changes often, and business rules should stay stable. Repository gives one replaceable contract for tests and migrations. Common wrong answer to avoid: "Repository is only for big companies."
Why start from use cases, not from design patterns?¶
Because patterns solve shaped problems, not vague confusion. Use cases reveal the real responsibilities first. Common wrong answer to avoid: "Factory and singleton should appear early."
Why one small class diagram, not immediate coding?¶
Because a quick map exposes missing responsibilities before implementation spreads. Small planning saves large refactoring later. Common wrong answer to avoid: "Good developers can keep everything in mind."
Apply now (5 min)¶
Exercise: Take the HLD box "Coupon Service" and list nouns and verbs. Split them into entities, services, repositories, and policies. Write at least six class names. Add one interface for storage.
Sketch from memory: Draw the Tweet Service class map again. Mark controller, service, entity, policy, and repository. Add one arrow for each dependency.
Bridge. Now that classes exist, we need rules that keep each room clean under change. → 02-solid-principles.md