LLD7 min read
URL Shortener (Object model)
Class-level design for a Bit.ly clone — stores, generators, and the rate-limit decorator.
llddesign
Intro
We've covered URL Shortener as a system in the HLD track. Here we design the application-layer classes: how URLShortener wires up an ID generator, a key-value store, and policy decorators (rate limiter, audit log) without anyone knowing about anyone else.
Functional
- shorten(longUrl, optional alias, optional expiry) → shortUrl.
- expand(shortUrl) → longUrl, or 404.
- Click-tracking optional, swappable.
Non-functional
- ID generator and store must be replaceable (in-memory, Redis, MySQL, …).
- Rate limiting per user added without touching core flow.
Components
UrlShortener
Public façade. Composes id-gen + store + (optional) tracker.
IdGenerator
Interface: Base62Counter, ULID, MurmurHash3.
UrlStore
Interface: InMemoryUrlStore, RedisUrlStore.
RateLimitedShortener
Decorator. Wraps a UrlShortener and rejects on over-limit.
AuditingShortener
Decorator. Emits domain events.
Code
Snippetjava
interface IdGenerator { String next(); }
interface UrlStore {
void put(String shortId, String longUrl, Optional<Instant> expiry);
Optional<String> get(String shortId);
}
class UrlShortener {
private final IdGenerator ids;
private final UrlStore store;
public UrlShortener(IdGenerator ids, UrlStore store) { this.ids = ids; this.store = store; }
public String shorten(String longUrl, Optional<String> alias, Optional<Duration> ttl) {
String id = alias.orElseGet(ids::next);
store.put(id, longUrl, ttl.map(d -> Instant.now().plus(d)));
return id;
}
public Optional<String> expand(String id) { return store.get(id); }
}Pitfalls
- Letting the shortener know which store backs it.
- Putting rate-limit logic inline — should be a decorator.