RUNTIME CREW · GROUP 02 · ASSIGNMENT ANSWER

API Rate-Limit & Abuse Simulator — Answer Sheet

Objective, UML class diagram, worked fixed-vs-sliding trace, sample report, OOP mapping and conclusion — written against the full project codebase.

1

Objective and problem statement

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.

2

Class diagram (UML)

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.

«interface» RatePolicy + evaluate(log, report) + getName() + getDescription() FixedWindowPolicy - maxRequests: int - window: Duration + evaluate(log, report) + getMaxRequests() + getWindow() SlidingWindowPolicy - maxRequests: int - window: Duration + evaluate(log, report) + getMaxRequests() + getWindow() realization realization Client - clientId: String - name: String + getClientId() + equals()/hashCode() RequestLog - clientId: String - requests: List + addRequest() + getRequests() ServiceRequest - clientId: String - requestType: enum - timestamp: DateTime + getTimestamp() 1 0..* owns 1 0..* records ◆ = composition (filled diamond on the owner's side — log entries live and die with the client) ▷ = realization (dashed + hollow triangle — policies implement RatePolicy)
Fig 2.1 — Domain classes with composition (Client ◆ RequestLog) and realization (policies ▷ RatePolicy). Multiplicities: one client owns 0..* logs; one log records 0..* requests.
UML concept behind Figure 2.1. The diagram shows composition — a Client has a list of RequestLog entries (filled diamond on the owner's side, lifetime bound to the owner) — and realization — both policies implement the RatePolicy interface (dashed line with a hollow triangle pointing at the interface). In other words: filled diamond = composition (the Client owns its RequestLog list; logs die with the client); dashed line + hollow triangle = realization (both policies implement RatePolicy, so the analyzer can swap them at runtime without touching its loop). In the implementation, Client is 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.
3

Worked trace: fixed vs. sliding window

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.

PolicyWindow(s) examinedMax countResult
Fixed Window10:15:30 – 10:15:395ALLOWED — 5 ≤ 5, counter resets at :40
Fixed Window10:15:40 – 10:15:495ALLOWED — 5 ≤ 5, all ten admitted, limit evaded
Sliding Window10:15:36 – 10:15:45 (rolling)6BLOCKED — 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.

Reading the trace: each fixed window honestly reports “5”, yet the client sustained ten requests in ten seconds — double the quota. The sliding check never resets, so the sixth arrival trips the detector. Cheap metering versus strict protection is exactly the design choice FixedWindowPolicy vs SlidingWindowPolicy encode in code.
4

Sample output / report

What the simulator prints for the trace above (plus one quiet client for contrast):

ClientPolicyMax countLimitLevel
CLIENT_AFixed Window (:30–:39)55NORMAL
CLIENT_AFixed Window (:40–:49)55NORMAL — boundary exploit, 10 admitted
CLIENT_ASliding Window (rolling)65CRITICAL — “Sliding window abuse detected: 6 requests within 10s window (limit: 5)”
CLIENT_BFixed Window25NORMAL
CLIENT_BBurst Detection44WARNING — “Burst detected: 4 requests within 3 seconds”
Table 4.1 — Per-policy verdicts: observed max count against the limit, and the severity each verdict carries into the risk score.
Reading Table 4.1. CLIENT_A under the fixed window remains exactly at the limit, so no violation fires; under the sliding window the same traffic reaches double the limit (10/5 = 2.0×), hence CRITICAL — the same traffic judged by 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()).
5

OOP concept mapping and conclusion

ConceptWhere in this codebaseHow it is used
EncapsulationAbuseReport, RateLimitEnforcer, ClientActivityTrackerCounters, block tables and violation lists are private; state changes only through addViolation(), setLevel(), processRequest(); histories are exposed as unmodifiable views.
PolymorphismRateLimitAnalyzer.analyze()One List<RatePolicy> holds five detectors; the same evaluate() call dispatches to five different algorithms at runtime.
Strategy patternRatePolicy + 5 policiesDetection algorithms are interchangeable behind one interface; the composition root wires them, so swapping a strategy is a one-line change.
EnumsRequestType, ViolationLevel, EventTypeFixed vocabularies (operations, severities, outcomes) with display names; enum ordinals drive the severity ladder.
Java Time APIServiceRequest, enforcer, all policiesLocalDateTime 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.

6

Conclusion

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.