This document provides a clear overview of the folder and file responsibilities within the GeoLocateAPI project.
The architecture follows Clean Architecture + Hexagonal Architecture, ensuring each layer has a single, well‑defined purpose.
Pure Java classes containing the domain models.
No Spring, no annotations, no external dependencies.
- model/GeoLocationData.java
Domain model representing geolocation information (lat/lon, country, region, city, timezone, ISP).
Independent from provider DTOs or frameworks.
Orchestrates logic and defines the boundary between the domain and the outside world.
- GeoQueryUseCase.java
Inbound port describing the geolocation queries available to entrypoints (findByIp,findByCity,findByCoordinates).
Controllers depend on this interface, never on the service implementation directly.
- GeoProviderPort.java
Outbound (hexagonal) port defining the contract for external geolocation providers.
Implemented byGeoProviderCompositeAdapterin theinfrastructurelayer.
- GeoService.java
Main application service implementingGeoQueryUseCase. Delegates toGeoProviderPort, manages cache lookups manually via Spring'sCacheManager(cachesgeo-ip,geo-city,geo-coordinates) and signals hits/misses throughCacheHitContext.
- CacheHitContext.java
Thread-local flag set byGeoServiceon every cache access (hit or miss) and read byGeoLocationResponse.from()to populate thecachedfield returned to clients.
- GeoLocationResponse.java
API-facing response DTO (boundary object), distinct from the domain model and provider DTOs. Includes thecachedboolean indicating whether the result was served from cache.
- ExternalServiceException.java – thrown when an upstream provider call fails.
- InvalidInputException.java – thrown for invalid application-level input.
- GlobalExceptionHandler.java –
@RestControllerAdvicethat converts exceptions into consistentErrorResponsepayloads, ensuring internal details never leak to clients.
Concrete implementations of the outbound port.
Handles HTTP calls, provider DTOs, mapping, and fallback orchestration.
- GeoProviderCompositeAdapter.java
ImplementsGeoProviderPort. Coordinates the primary and fallback provider clients for each operation and maps their results into the domainGeoLocationData:- IP lookup:
IpGeoClient(ip-api.com) → falls back toIpInfoFallbackClient(ipinfo.io) - Reverse geocoding:
ReverseGeoClient(Nominatim/OSM) → falls back toBigDataCloudFallbackClient - City search:
CitySearchClient(Open-Meteo) — no fallback configured
- IP lookup:
- client/ip/
IpGeoClient.java– primary IP → geolocation provider (ip-api.com)IpInfoFallbackClient.java– fallback IP → geolocation provider (ipinfo.io)dto/IpApiResponse.java,dto/IpInfoResponse.java– provider-specific response DTOs
- client/reverse/
ReverseGeoClient.java– primary reverse-geocoding provider (OSM/Nominatim)BigDataCloudFallbackClient.java– fallback reverse-geocoding provider (BigDataCloud)dto/ReverseGeoResponse.java,dto/BigDataCloudResponse.java– provider-specific response DTOs
- client/search/
CitySearchClient.java– city name → coordinates provider (Open-Meteo)dto/SearchGeoResponse.java– provider-specific response DTO
All clients use RestTemplate (configured in config/AppConfig), translate provider
responses into GeoLocationData, and wrap failures in ExternalServiceException so the
composite adapter can trigger fallback.
Exposes the REST endpoints and handles transport-level concerns only.
- GeoController.java
REST controller for IP lookup, coordinate search, and city lookup (/api/v1/geo/ip/{ip},/api/v1/geo/coordinates,/api/v1/geo/city/{city}).
Annotated with@RateLimitper endpoint and@Valid/Bean Validation constraints (@ValidIp,@Min/@Maxon coordinates,@Sizeon city name). Contains no business logic.
- RateLimit.java – method-level annotation selecting a
RateLimitTierfor an endpoint. - RateLimitTier.java – enum defining capacity and refill window per tier
(
IP_LOOKUP: 30 req/min,GEO_SEARCH: 60 req/min,DEFAULT: 60 req/min) and building the corresponding Bucket4jBucketConfiguration. - RateLimitService.java – resolves the client identifier (
X-Forwarded-Foror remote address) and looks up/creates a Bucket4jBucketper client+tier. - RateLimitInterceptor.java –
HandlerInterceptorthat consumes a token per request, setsX-RateLimit-Limit/X-RateLimit-Remainingheaders, and returns429 Too Many Requestswith aRetry-Afterheader and JSON body when the limit is exceeded. - WebConfig.java – registers
RateLimitInterceptorin the Spring MVC interceptor chain.
- ValidIp.java – Bean Validation annotation enforcing valid IP address format.
- IpValidator.java –
ConstraintValidatorimplementation backing@ValidIp.
- ErrorResponse.java – standardized error payload (
timestamp,status,error,message,path) returned byGlobalExceptionHandlerfor all failure responses.
Combination of unit, integration, and controller tests.
- GeoControllerTest.java – Tests the controller layer via MockMvc, including validation and rate-limiting behaviour.
- GeoServiceTest.java – Unit tests for the application service orchestration logic.
- GeoServiceCacheTest.java – Verifies cache hit/miss behaviour and
CacheHitContextsignalling forgeo-ip,geo-cityandgeo-coordinates. - GeoProviderCompositeAdapterTest.java – Verifies primary/fallback provider chains and error propagation when all providers fail.
- IpGeoClientTest.java, ReverseGeoClientTest.java, CitySearchClientTest.java – WireMock-based tests validating provider HTTP integration and DTO mapping.
- GeoLocateApiApplicationTests.java – Spring context load test.
- GeoLocateApiApplication.java
The Spring Boot main entry point.
- AppConfig.java – defines the shared
RestTemplatebean (Apache HttpClient5-backed) with connection/response timeouts sourced fromexternal.timeout-ms. - CacheConfig.java – defines the Caffeine-backed
CacheManagerwith thegeo-ip,geo-coordinatesandgeo-citycaches, reading TTL and max-size values fromapplication.yml(cache.*).
- domain → core model
- application → use cases (ports in/out), orchestration service, caching context, DTOs, exceptions
- infrastructure → provider clients (with fallback variants), DTOs, mappings, composite adapter
- presentation → controllers, rate limiting, validation, error formatting
- config → shared beans (HTTP client, cache manager)
- test → full coverage using MockMvc, WireMock and dedicated cache/fallback test suites
This structure enforces separation of concerns and keeps the system maintainable, testable, and extensible.