Objective, UML class diagram, worked fixed-vs-sliding trace, sample report, OOP mapping and conclusion — written against the full project codebase.
Public APIs must survive overload, scraping and aggressive retries without blocking legitimate users. This project simulates an API gateway that (a) admits at most N requests per time window per client, (b) judges behaviour with five detection policies (fixed window, sliding window, burst, abnormal pattern, retry abuse), and (c) turns every verdict into a severity level, a risk score and an exportable report. Everything runs in memory as plain Java objects — no network, no server — so each decision is deterministic and inspectable.
The figure below captures the domain backbone of the simulator in one view: who owns whom, and who implements what. Read the diamonds for lifetime, and the dashed triangles for pluggability.
model/Client.java, RequestLog is model/RequestLog.java, and RatePolicy is policy/RatePolicy.java with policy/FixedWindowPolicy.java and policy/SlidingWindowPolicy.java as the two realizations. Records are immutable value objects, so the live log list does not sit inside the client record — it sits one level up, in service/RequestLogger's Map<clientId, RequestLog> (persisted through service/RequestLogService) — a single gatekeeper still owns the log.Policy: 5 requests per 10 seconds. CLIENT_A sends at 10:15:36, :37, :38, :39, :39 | :40, :40, :41, :42, :43 — five requests before the :40 edge, five on and after it.
| Policy | Window(s) examined | Max count | Result |
|---|---|---|---|
| Fixed Window | 10:15:30 – 10:15:39 | 5 | ALLOWED — 5 ≤ 5, counter resets at :40 |
| Fixed Window | 10:15:40 – 10:15:49 | 5 | ALLOWED — 5 ≤ 5, all ten admitted, limit evaded |
| Sliding Window | 10:15:36 – 10:15:45 (rolling) | 6 | BLOCKED — CRITICAL — the 6th request (10:15:40) already sees six in-window requests |
The fixed window has a boundary-burst weakness. A client can send 2× the limit across a window edge. The sliding window closes this gap, but it costs more: O(n) memory to keep timestamps, compared with O(1) counters per window. This is the trade-off real API gateways weigh, and it is why production systems use approximations such as the sliding-window counter or the token bucket.
FixedWindowPolicy vs SlidingWindowPolicy encode in code.What the simulator prints for the trace above (plus one quiet client for contrast):
| Client | Policy | Max count | Limit | Level |
|---|---|---|---|---|
| CLIENT_A | Fixed Window (:30–:39) | 5 | 5 | NORMAL |
| CLIENT_A | Fixed Window (:40–:49) | 5 | 5 | NORMAL — boundary exploit, 10 admitted |
| CLIENT_A | Sliding Window (rolling) | 6 | 5 | CRITICAL — “Sliding window abuse detected: 6 requests within 10s window (limit: 5)” |
| CLIENT_B | Fixed Window | 2 | 5 | NORMAL |
| CLIENT_B | Burst Detection | 4 | 4 | WARNING — “Burst detected: 4 requests within 3 seconds” |
policy/SlidingWindowPolicy, which escalates the moment any rolling window exceeds the max. CLIENT_B approaches the limit under neither counting method. Where the table shows NORMAL (“no violation fired”), the live dashboard shows the same state on its risk badge (ControlPanelView.updateRiskLevel()) and the report prints “No violations” (AbuseReport.getSummary()).| Concept | Where in this codebase | How it is used |
|---|---|---|
| Encapsulation | AbuseReport, RateLimitEnforcer, ClientActivityTracker | Counters, block tables and violation lists are private; state changes only through addViolation(), setLevel(), processRequest(); histories are exposed as unmodifiable views. |
| Polymorphism | RateLimitAnalyzer.analyze() | One List<RatePolicy> holds five detectors; the same evaluate() call dispatches to five different algorithms at runtime. |
| Strategy pattern | RatePolicy + 5 policies | Detection algorithms are interchangeable behind one interface; the composition root wires them, so swapping a strategy is a one-line change. |
| Enums | RequestType, ViolationLevel, EventType | Fixed vocabularies (operations, severities, outcomes) with display names; enum ordinals drive the severity ladder. |
| Java Time API | ServiceRequest, enforcer, all policies | LocalDateTime stamps plus Duration.between do all window maths — no external clock library, no string parsing. |
Together these keep detection logic behind one interface, state tamper-proof, and time maths exact — which is why new policies, clients and report formats slot in without touching existing code.
Correct rate limiting needs both admission control and behavioural detection: quotas stop floods while pattern policies catch bots that respect quotas. Fixed windows are cheap but exploitable at the boundary; sliding windows cost more and close the gap. Because everything here runs deterministically in memory, every verdict is reproducible, explainable and reportable.