Design the abstraction boundary for a "vote" on a large discussion platform where a handful of threads receive an enormous share of all votes. What should the interface promise, what must it hide, and what must it refuse to hide?
Show the full answer Hide the answer
What the interface promises
vote(user, item, direction) — idempotent per (user, item), returning the caller's own vote state
immediately. Note what is not promised: the updated score.
What it should hide
- Where the counter lives — a database row, a sharded counter, a stream aggregation or a periodically flushed accumulator is nobody's business.
- Batching and coalescing. Under load a hot item's votes accumulate and apply in batches.
- Hot-key handling. Detecting that an item is hot and switching it to a different write path is an internal optimisation. Callers must not be asked to know which items are popular.
- Ranking mechanics. Time decay, controversy adjustment and normalisation are consequences of the score, not part of voting.
What it must refuse to hide
1. That the score is eventually consistent. If vote() returned an authoritative score, the
abstraction would have promised something the implementation cannot deliver on a hot thread without
serialising every vote through one row — exactly the bottleneck the design exists to avoid. Hide the
mechanism; never hide the guarantee.
So the API returns the user's own vote, which is strongly consistent and cheap because it is per-user state, and exposes the aggregate through a separate read that is explicitly approximate and cached. Users notice their own vote failing to register. Almost nobody notices a score a few seconds or a few counts stale.
2. Failure. A vote accepted for later application is not the same as a vote durably recorded. If the buffer can lose writes, callers may need to know; silently degrading durability under load produces "my vote disappeared" reports nobody can reproduce.
3. Rate limiting and eligibility. A refused vote must be visibly refused. Encapsulation that swallows a policy decision makes the policy untestable and unappealable.
The generalisable rule
A good abstraction hides mechanism and exposes guarantees. The common failure is the reverse: interfaces that leak implementation detail while quietly weakening the guarantees callers relied on. When you cannot honour a guarantee at scale, change the interface rather than quietly change the behaviour.