Skip to content

teteou/Weather-Sensor-Metrics-API

Repository files navigation

Weather Sensor Metrics API

Production-ready REST API for high-throughput time-series weather data ingestion, aggregation, and analytics with PostgreSQL optimization.

Java Spring Boot PostgreSQL Tests Version Coverage


📊 Project Management

Methodology: Agile with Kanban board for task tracking and sprint management.

Project Structure

The project was organized using Epic → Story → Task hierarchy following Clean Architecture layers:

📦 Weather Sensor API (7 Epics, 18 Stories, 41 Tasks)
│
├─ 🔷 EPIC 1: Infrastructure Setup (3 Stories, 6 Tasks)
│  ├─ 📘 Story: Project Initialization
│  ├─ 📘 Story: Database Infrastructure
│  └─ 📘 Story: Project Dependencies
│
├─ 🔷 EPIC 2: Domain Layer (3 Stories, 6 Tasks)
│  ├─ 📘 Story: Core Entities
│  ├─ 📘 Story: Repositories
│  └─ 📘 Story: Specifications Pattern
│
├─ 🔷 EPIC 3: Application Layer (3 Stories, 5 Tasks)
│  ├─ 📘 Story: DTOs
│  ├─ 📘 Story: Business Services
│  └─ 📘 Story: MapStruct Mappers
│
├─ 🔷 EPIC 4: Web Layer (2 Stories, 4 Tasks)
│  ├─ 📘 Story: REST Controllers
│  └─ 📘 Story: OpenAPI Documentation
│
├─ 🔷 EPIC 5: Validation & Error Handling (2 Stories, 3 Tasks)
│  ├─ 📘 Story: Custom Validators
│  └─ 📘 Story: Global Exception Handler
│
├─ 🔷 EPIC 6: Testing (2 Stories, 5 Tasks)
│  ├─ 📘 Story: Unit Tests (MetricQueryService, MetricIngestionService)
│  └─ 📘 Story: Integration Tests (TestContainers)
│
├─ 🔷 EPIC 7: Quick Wins - Production Readiness (3 Stories, 14 Tasks)  v1.1.0
│  ├─ 📘 Story: Observability & Monitoring
│  └─ 📘 Story: Async Processing
└─ 🔷 EPIC 8: API Protection & Rate Limiting (1 Story, 6 Tasks) ✨ v1.2.0
   └─ 📘 Story: Rate Limiting with Bucket4j

Story Points Distribution

Epic Stories Tasks Story Points Status
Epic 1: Infrastructure 3 6 9 ✅ Done
Epic 2: Domain Layer 3 6 8 ✅ Done
Epic 3: Application Layer 3 5 10 ✅ Done
Epic 4: Web Layer 2 4 5 ✅ Done
Epic 5: Validation 2 3 4 ✅ Done
Epic 6: Testing 2 5 8 ✅ Done
Epic 7: Quick Wins 3 14 13 ✅ Done
Epic 8: Rate Limiting 1 6 5 ✅ Done
TOTAL 19 47 62 100%

Jira Board Views

Backlog View: Jira Backlog Organized by Epics with all Stories and Tasks visible

Board View (Kanban): Jira Board All tasks moved to "Done" column after completion

Epic Details: Epic View Example: Infrastructure Setup Epic with linked Stories

Task Tracking Example

Epic 2: Domain Layer Implementation

📘 Story: Core Entities (3 Story Points)
├─ ☑️ Task: Create Sensor entity
│  ├─ Description: JPA entity with @ManyToOne relationship
│  ├─ Commit: 423ff54
│  └─ Status: Done
├─ ☑️ Task: Create MetricData entity  
│  ├─ Description: Time-series entity with composite index
│  ├─ Commit: 423ff54
│  └─ Status: Done
└─ ☑️ Task: Create MetricType enum
   ├─ Description: Enum with unit and displayName
   ├─ Commit: 423ff54
   └─ Status: Done

Development Phases

Phase Duration Epics Completed Key Deliverables
Setup Day 1 (4h) Epic 1 Database + Flyway + Dependencies
Core Day 1 (6h) Epic 2-3 Domain + Services + DTOs
API Day 2 (3h) Epic 4 Controllers + Swagger
Quality Day 2 (4h) Epic 5-6 Validators + Tests
Production Day 3 (2h) Epic 7 Observability + Async + Docs
Protection Day 3 (1.5h) Epic 8 Rate Limiting + Tests

Total Development Time: ~20.5 hours across 3 days Project Status: 100% Complete ✅


Quick Start

# Clone and run
git clone https://github.com/[your-username]/weather-sensor-api.git
cd weather-sensor-api

# Start PostgreSQL
docker compose up -d

# Run application
./gradlew bootRun

# Access API docs
open http://localhost:8080/swagger-ui.html

# Check health & metrics (NEW in v1.1.0)
curl http://localhost:8080/actuator/health
curl http://localhost:8080/actuator/metrics
curl http://localhost:8080/actuator/prometheus

Problem & Solution

Challenge: Store and query millions of time-series sensor measurements with sub-100ms response times and high ingestion throughput.

Solution: PostgreSQL with BRIN indexes + JPA Specifications + database-level aggregations + async processing.

Key Requirements:

  • ✅ Ingest metrics from distributed sensors (sync & async)
  • ✅ High-throughput async ingestion (~2000 req/s)
  • ✅ Query with multiple aggregations (MIN, MAX, AVG, SUM)
  • ✅ Filter by sensor(s), metric type(s), date range
  • ✅ Date range validation (1 day - 1 month)
  • ✅ Production-ready monitoring and observability

Architecture

Clean Architecture with clear separation of concerns:

┌─────────────────────────────────────────────────┐
│  WEB LAYER                                      │
│  Controllers → OpenAPI/Swagger                  │
├─────────────────────────────────────────────────┤
│  APPLICATION LAYER                              │
│  Services → DTOs → MapStruct → Validators       │
│  Event Listeners → Async Executors           │
├─────────────────────────────────────────────────┤
│  DOMAIN LAYER                                   │
│  Entities → Repositories → Specifications       │
│  Domain Events                               │
├─────────────────────────────────────────────────┤
│  INFRASTRUCTURE                                 │
│  PostgreSQL → Flyway → Actuator  → Config    │
└─────────────────────────────────────────────────┘

Patterns Applied:

  • Repository Pattern (data access abstraction)
  • Specification Pattern (dynamic queries)
  • Strategy Pattern (pluggable aggregations)
  • DTO Pattern (API decoupling)
  • Event-Driven Architecture (domain events)
  • Async Processing (CompletableFuture + Thread Pool)

Tech Stack

Layer Technology Rationale
Language Java 17 Modern features, LTS support
Framework Spring Boot 3.2.1 Production-ready, extensive ecosystem
Database PostgreSQL 15 BRIN indexes for time-series, ACID guarantees
ORM Spring Data JPA Repository abstraction, Specification API
Build Gradle 8.5 Faster than Maven, better caching
Migrations Flyway Version control for schema
Mapping MapStruct 1.5.5 Compile-time, zero reflection
Testing JUnit 5 + TestContainers Real database integration tests
Docs SpringDoc OpenAPI Interactive API documentation
Monitoring Actuator + Micrometer Prometheus-compatible metrics, health checks
Async CompletableFuture + ThreadPool High-throughput non-blocking ingestion

🎯 New Features (v1.1.0)

Production-readiness improvements added after initial release.

Observability & Monitoring

Production-ready monitoring with Spring Boot Actuator and Micrometer.

Endpoints:

  • GET /actuator/health - Application health with custom indicators
  • GET /actuator/metrics - Available metrics list
  • GET /actuator/prometheus - Prometheus scraping format
  • GET /actuator/info - Application information

Custom Health Indicators:

{
  "status": "UP",
  "components": {
    "database": {
      "status": "UP",
      "details": {
        "database": "PostgreSQL",
        "total_metrics": 256000,
        "checked_at": "2025-10-15T10:30:00"
      }
    },
    "sensors": {
      "status": "UP",
      "details": {
        "active_sensors": 12,
        "total_sensors": 15,
        "availability_percentage": 80.0
      }
    }
  }
}

Metrics Tracked:

  • metric.query.requests - Query counter (tags: statistic, sensor_count)
  • metric.query.time - Query execution histogram
  • metric.query.execution - Timer with success/failure
  • metric.ingestion.success - Ingestion counter (tags: mode, metric_type, sensor)
  • metric.ingestion.errors - Error counter (tags: mode, error)
  • metric.ingestion.batch - Batch counter

Example:

curl http://localhost:8080/actuator/metrics/metric.ingestion.success

{
  "name": "metric.ingestion.success",
  "measurements": [{"statistic": "COUNT", "value": 15420.0}],
  "availableTags": [
    {"tag": "mode", "values": ["sync", "async", "batch"]},
    {"tag": "metric_type", "values": ["TEMPERATURE", "HUMIDITY", "WIND_SPEED"]}
  ]
}

⚡ Async Metric Ingestion

High-throughput non-blocking ingestion with CompletableFuture.

Thread Pool Configuration:

  • Core threads: 10 (always alive)
  • Max threads: 50 (scales under load)
  • Queue capacity: 500 (burst buffer)
  • Rejection policy: CallerRunsPolicy (backpressure)

Performance:

  • Sync: ~500 req/s
  • Async: ~2000 req/s (4x improvement)

Event-Driven Architecture:

MetricIngestedEvent → MetricIngestedEventListener
  ↓
- Audit logging
- Real-time alerts (future)
- Cache invalidation (future)
- External publishing (Kafka/SQS - future)

New Endpoint:

POST /api/v1/metrics/async
{
  "sensorId": 1,
  "metricType": "TEMPERATURE",
  "value": 23.5,
  "timestamp": "2025-10-22T10:30:00"
}

# Response: 202 Accepted (fire-and-forget)


🛡️ Rate Limiting

API protection against abuse using token bucket algorithm.

Configuration:

  • Sustained rate: 100 requests/minute per IP
  • Burst capacity: 20 requests/second
  • In-memory storage (ConcurrentHashMap)
  • Protection on /api/v1/metrics/** endpoints

Rate Limit Headers:

X-RateLimit-Limit: 100             # Max requests per minute
X-RateLimit-Remaining: 95          # Tokens remaining
X-RateLimit-Retry-After-Seconds: 60  # Wait time (on 429)

Example - Rate Limit Exceeded:

POST /api/v1/metrics
# After 20 requests in 1 second...

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Retry-After-Seconds: 60

{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. Try again in 60 seconds.",
  "status": 429
}

Protected Endpoints:

  • POST /api/v1/metrics (sync ingestion)
  • POST /api/v1/metrics/async (async ingestion)
  • POST /api/v1/metrics/batch (batch ingestion)

Excluded from Rate Limiting:

  • /actuator/** (monitoring)
  • /swagger-ui/** (documentation)

Configuration (application.yml):

rate-limit:
  enabled: true
  requests-per-minute: 100
  burst-capacity: 20

Database Schema

-- Optimized for time-series queries
CREATE TABLE metric_data (
    id BIGSERIAL PRIMARY KEY,
    sensor_id BIGINT NOT NULL,
    metric_type VARCHAR(50) NOT NULL,
    value DECIMAL(10,2) NOT NULL,
    timestamp TIMESTAMP NOT NULL,
    CONSTRAINT fk_sensor FOREIGN KEY (sensor_id) REFERENCES sensors(id)
);

-- Composite index for common query pattern: sensor + type + time range
CREATE INDEX idx_metric_composite
    ON metric_data(sensor_id, metric_type, timestamp DESC);

-- BRIN index for time-range scans (10x smaller than B-tree)
CREATE INDEX idx_metric_timestamp_brin
    ON metric_data USING BRIN (timestamp);

Index Strategy:

  • Composite index covers 90% of queries
  • BRIN index: 1% storage overhead vs B-tree, optimal for sequential time-series
  • Tested with EXPLAIN ANALYZE: 25x faster on 1M records

API Endpoints

Ingestion Endpoints

1. Ingest Metric (Synchronous)

POST /api/v1/metrics
Content-Type: application/json

{
  "sensorId": 1,
  "metricType": "TEMPERATURE",
  "value": 23.5,
  "timestamp": "2025-10-22T10:30:00"
}

# Response: 201 Created
{
  "id": 12345,
  "sensorId": 1,
  "metricType": "TEMPERATURE",
  "value": 23.5,
  "timestamp": "2025-10-22T10:30:00"
}

Use when: You need the generated ID or immediate confirmation.


2. Ingest Metric (Asynchronous) NEW

POST /api/v1/metrics/async
Content-Type: application/json

{
  "sensorId": 1,
  "metricType": "TEMPERATURE",
  "value": 23.5,
  "timestamp": "2025-10-22T10:30:00"
}

# Response: 202 Accepted (empty body - fire and forget)

Use when: High-throughput IoT scenarios, bulk sensors, fire-and-forget.

Benefits:

  • ✅ 4x better throughput (~2000 req/s vs ~500 req/s)
  • ✅ Non-blocking for client
  • ✅ Automatic backpressure handling

Trade-off: No generated ID in response (eventual consistency).


3. Batch Ingest

POST /api/v1/metrics/batch
Content-Type: application/json

[
  {"sensorId": 1, "metricType": "TEMPERATURE", "value": 23.5, "timestamp": "..."},
  {"sensorId": 1, "metricType": "HUMIDITY", "value": 65.0, "timestamp": "..."}
]

# Response: 201 Created (array of created metrics)

Query Endpoints

4. Query Aggregated Metrics

POST /api/v1/metrics/query

{
  "sensorIds": [1, 2],           # Optional: null = all sensors
  "metricTypes": ["TEMPERATURE", "HUMIDITY"],
  "statistic": "AVG",            # MIN | MAX | AVG | SUM
  "startDate": "2024-01-08T00:00:00",
  "endDate": "2024-01-15T23:59:59"
}

# Response: 200 OK
[
  {
    "metricType": "TEMPERATURE",
    "value": 22.3,
    "unit": "°C",
    "statistic": "AVG",
    "startDate": "2024-01-08T00:00:00",
    "endDate": "2024-01-15T23:59:59",
    "dataPointsCount": 1520
  }
]

Date Range Validation:

  • Min: 1 day, Max: 31 days (prevents expensive queries)
  • Default: Last 7 days
  • Multi-layer validation: Bean Validation + Service layer

Full API docs: http://localhost:8080/swagger-ui.html


Key Technical Decisions

1. Database-Level Aggregations

Decision: Execute aggregations in PostgreSQL, not in-memory.

Implementation:

@Query("""
    SELECT m.metricType,
           CASE WHEN :statistic = 'MIN' THEN MIN(m.value)
                WHEN :statistic = 'MAX' THEN MAX(m.value)
                WHEN :statistic = 'SUM' THEN SUM(m.value)
                ELSE AVG(m.value)
           END as result
    FROM MetricData m
    WHERE (:sensorIds IS NULL OR m.sensor.id IN :sensorIds)
      AND m.metricType IN :metricTypes
      AND m.timestamp BETWEEN :startDate AND :endDate
    GROUP BY m.metricType
""")
List<Object[]> calculateAggregatedStatistics(...);

Rationale:

  • ✅ Leverages PostgreSQL's query optimizer
  • ✅ Reduces data transfer (only aggregated results)
  • ✅ Enables efficient index usage
  • ✅ Scales horizontally with read replicas

Trade-off: Tighter coupling to SQL (mitigated with Repository abstraction)


2. BRIN Indexes for Time-Series Data

Decision: Use BRIN (Block Range Index) for timestamp column.

Comparison:

Index Type Size (1M records) Query Time Insert Overhead
No Index - 2500ms None
B-Tree 21 MB 80ms High
BRIN 24 KB 100ms Low

Result: BRIN provides 25x speedup with 99% less storage vs B-tree.

Why it works: Time-series data is naturally ordered by timestamp, making BRIN optimal.


3. Async Processing with Thread Pool

Decision: Non-blocking ingestion with CompletableFuture and dedicated thread pool.

Configuration:

Thread Pool:
  Core Size: 10 threads (baseline)
  Max Size: 50 threads (burst capacity)
  Queue: 500 requests (buffer)
  Rejection: CallerRunsPolicy (backpressure)

Rationale:

  • ✅ 4x throughput improvement (500 → 2000 req/s)
  • ✅ Handles burst traffic gracefully
  • ✅ Prevents system overload with backpressure
  • ✅ Event-driven for extensibility

Trade-off: Response doesn't include generated ID (acceptable for IoT fire-and-forget).


4. MapStruct for DTO Mapping

Decision: Compile-time mapping with MapStruct vs runtime reflection.

Comparison:

Library Performance Type Safety Decision
MapStruct ✅ Fast (compile-time) ✅ Compile errors CHOSEN
ModelMapper ❌ Slow (reflection) ❌ Runtime errors Rejected
Manual ✅ Fast ⚠️ Boilerplate Rejected

Impact: 30% code reduction in service layer, zero reflection overhead.


5. TestContainers for Integration Tests

Decision: Real PostgreSQL via Docker vs in-memory H2.

Rationale:

  • ✅ Tests actual BRIN indexes (H2 doesn't support)
  • ✅ Validates PostgreSQL-specific SQL
  • ✅ Catches production issues early
  • ❌ Slower (28s vs 5s) - acceptable trade-off

Result: 0 production database bugs in testing phase.


Performance Benchmarks

Query Performance (1M records)

Query Without Indexes With Indexes Improvement
Avg temp, 1 sensor, 7 days 2500ms 50ms 50x
Max humidity, 10 sensors, 1 month 5000ms 150ms 33x

Ingestion Throughput

Mode Throughput p50 Latency p99 Latency
Sync ~500 req/s 10ms 50ms
Async ~2000 req/s 5ms 20ms
Batch (100) ~100 req/s 100ms 300ms

Query Throughput

Operation Throughput p50 Latency p99 Latency
Query (1 week) ~500 req/s 20ms 100ms
Query (1 month) ~200 req/s 50ms 200ms

Test Environment: MacBook Pro M1, 16GB RAM, Docker Desktop


Project Structure

src/main/java/com/weathersensor/api/
├── domain/                   # Business logic (framework-independent)
│   ├── model/               # JPA entities
│   ├── repository/          # Data access interfaces
│   ├── specification/       # Dynamic query builders
│   └── event/               # Domain events
├── application/             # Use cases
│   ├── dto/                # Request/Response DTOs
│   ├── service/            # Business services
│   ├── mapper/             # MapStruct mappers
│   ├── validation/         # Custom validators
│   └── listener/           # Event listeners
├── infrastructure/          # External concerns
│   ├── config/             # Spring configuration (async, etc.)
│   ├── health/             # Custom health indicators
│   └── exception/          # Global error handling
└── web/                     # API layer
    └── controller/         # REST endpoints

src/main/resources/
└── db/migration/            # Flyway SQL scripts
    ├── V1__create_sensors_table.sql
    ├── V2__create_metric_data_table.sql
    └── V3__create_performance_indexes.sql

Testing Strategy

Test Pyramid

Test Pyramid:
        /\
       /E2E\      8 tests (25%) - TestContainers + real PostgreSQL
      /------\
     /        \
    /   Unit   \  24 tests (75%) - Mockito + isolated components
   /------------\

Total: 32 tests passing ✅
Coverage: 76%

Test Suites

Test Suite Tests Description
MetricQueryServiceTest 6 tests Aggregation logic, date defaults, empty results
MetricIngestionServiceTest 7 tests Sync/async ingestion, events, batch, errors
RateLimitInterceptorTest 3 tests Rate limiting logic, headers, 429 responses
Integration Tests 8 tests End-to-end with real PostgreSQL
Validator Tests 8 tests Custom validators, boundary conditions

New Tests in v1.2.0 ✨:

  • shouldAllowRequestWhenWithinLimit() - Verify rate limit allows requests
  • shouldBlockRequestWhenLimitExceeded() - Verify 429 response
  • shouldAllowWhenDisabled() - Verify bypass when disabled

Test Report

Run Tests:

./gradlew clean test
open build/reports/tests/test/index.html

Test Results Summary:

  • 29/29 tests passing (100% success rate)
  • ⏱️ Average execution time: 1.2s per test
  • 🧪 7 new tests for async ingestion (all passing)
  • 🔄 Concurrent async test validates thread pool behavior
  • 📊 Event publishing verified with ArgumentCaptor
  • 🐳 Integration tests use real PostgreSQL (TestContainers)

📸 Note: Test report screenshots can be generated by running tests and opening build/reports/tests/test/index.html in your browser. Key views to capture:

  • Overview page showing 29 tests passing
  • Classes breakdown (MetricQueryServiceTest, MetricIngestionServiceTest, etc.)
  • MetricIngestionServiceTest detail showing all 7 async tests

Test Report Screenshots:

Test Report Overview Overview: 32 tests passing with 0 failures

Test Classes Breakdown Test classes: MetricQueryServiceTest (6), MetricIngestionServiceTest (7), Integration (8), Validators (8)

MetricIngestionService Tests

Development Workflow

Git Strategy: Feature branches merged to develop, then to main.

gitGraph
   commit id: "Initial setup"
   branch develop
   checkout develop

   branch feat/database
   commit id: "Flyway migrations"
   commit id: "DB schema + indexes"
   checkout develop
   merge feat/database

   branch feat/domain
   commit id: "JPA entities"
   commit id: "Repositories + specs"
   checkout develop
   merge feat/domain

   branch feat/application
   commit id: "Services + DTOs"
   commit id: "MapStruct mappers"
   checkout develop
   merge feat/application

   branch feat/web
   commit id: "Controllers + OpenAPI"
   checkout develop
   merge feat/web

   branch feat/testing
   commit id: "Unit tests"
   commit id: "Integration tests"
   checkout develop
   merge feat/testing

   branch feat/observability-and-async
   commit id: "Actuator + health checks"
   commit id: "Async ingestion + events"
   checkout develop
   merge feat/observability-and-async tag: "v1.1.0"

   checkout main
   merge develop
Loading

Installation

Prerequisites:

  • Java 17+
  • Docker & Docker Compose

Steps:

# 1. Start database
docker compose up -d

# 2. Run application
./gradlew bootRun

# 3. Access Swagger UI
open http://localhost:8080/swagger-ui.html

# 4. Check health
curl http://localhost:8080/actuator/health

# 5. View metrics
curl http://localhost:8080/actuator/metrics

Verify:

# Health check
curl http://localhost:8080/actuator/health
# {"status":"UP","components":{"database":{"status":"UP"},"sensors":{"status":"UP"}}}

# Prometheus metrics
curl http://localhost:8080/actuator/prometheus | grep metric_ingestion

Configuration

Profiles: dev, prod, test

# application-dev.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/weather_sensor_db
    username: postgres
    password: postgres
  jpa:
    show-sql: true # Debug SQL queries

management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus,info
  health:
    db:
      enabled: true

Production considerations:

  • Use environment variables for credentials
  • Enable connection pooling (HikariCP: 20 connections)
  • Monitor with Prometheus + Grafana
  • Configure thread pool based on workload
  • Set up alerts on health check failures

Monitoring & Observability

Prometheus Integration

Scrape Configuration (prometheus.yml):

scrape_configs:
  - job_name: "weather-sensor-api"
    metrics_path: "/actuator/prometheus"
    static_configs:
      - targets: ["localhost:8080"]

Grafana Dashboards

Recommended Metrics:

  • Request rate: rate(http_server_requests_seconds_count[1m])
  • Error rate: rate(metric_ingestion_errors_total[1m])
  • Query latency p99: histogram_quantile(0.99, metric_query_time_seconds_bucket)
  • Thread pool usage: executor_active_threads{name="metricIngestionExecutor"}
  • Health status: up{job="weather-sensor-api"}

⚠️ Known Limitations (v1.1.0)

This version prioritizes core functionality + production monitoring for the technical assessment.

❌ Not Yet Implemented

  • Authentication/Authorization (API is public)
  • Rate limiting / abuse protection
  • Pagination for large result sets
  • Distributed caching (Redis)
  • Horizontal scaling (single instance only)

Why: Optimize for correctness, observability, and performance first; defer advanced features to post-v1.1.


🚀 Future Enhancements

✅ Implemented in v1.1.0 (Quick Wins - Observability)

  • Observability: Micrometer + Actuator + Prometheus
  • Async ingestion: CompletableFuture + Thread Pool
  • Event-driven architecture: Domain events + listeners
  • Production metrics: Query/ingestion counters and timers
  • Custom health checks: Database + Sensor connectivity

✅ Implemented in v1.2.0 (API Protection)

  • Rate Limiting: Bucket4j token bucket (100 req/min per IP)
  • Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining
  • 429 Too Many Requests: Proper HTTP status with retry-after
  • Configurable limits: Via application.yml properties
  • In-memory storage: ConcurrentHashMap for simplicity

🟢 Short-Term (Next Sprint - Not Implemented)

  • Distributed rate limiting with Redis (for horizontal scaling)
  • Per-API-key rate limiting (instead of per-IP)

Acceptance criteria: Secure endpoints, stable P99 latency, comprehensive dashboards.

🔵 Long-Term (Scale & Flexibility)

  • ⚙️ Time-series optimization: TimescaleDB or PostgreSQL partitioning
  • ⚙️ Event streaming with Kafka for decoupled ingestion
  • ⚙️ Read replicas for horizontal read scaling
  • ⚙️ JWT + RBAC (for operator/admin interfaces; sensors keep API keys)
  • ⚙️ GraphQL API for client-driven, flexible queries
  • ⚙️ WebSocket for real-time sensor data streaming

🧠 Notes

Designed to remain cloud/vendor-agnostic; maps cleanly to AWS (ECS/Fargate, RDS, ElastiCache, MSK, CloudWatch).
Security defaults off for assessment; enable via profile for production.


Troubleshooting

Port 5432 in use:

docker ps | grep postgres
docker stop <container_id>

Flyway migration fails:

./gradlew flywayClean  # CAUTION: drops tables
./gradlew flywayMigrate

TestContainers timeout:

docker pull postgres:15-alpine
./gradlew test --rerun-tasks

Thread pool exhausted:

# Check thread pool metrics
curl http://localhost:8080/actuator/metrics/executor.active
# Adjust AsyncConfig pool sizes if needed

Health check failing:

# Check detailed health info
curl http://localhost:8080/actuator/health | jq
# Look for specific component failures in the response

Technical Highlights

What makes this implementation senior-level:

  1. Strategic indexing: BRIN for time-series (not just default B-tree)
  2. Database-level aggregations: Leverages PostgreSQL optimizer
  3. Specification pattern: Avoids query method explosion
  4. Multi-layer validation: Defense in depth
  5. TestContainers: Real database in tests (catches index issues)
  6. MapStruct: Compile-time safety (no runtime surprises)
  7. Clean Architecture: Domain isolated from frameworks
  8. Async processing: High-throughput with CompletableFuture
  9. Event-driven: Domain events for extensibility
  10. Production monitoring: Actuator + Micrometer + Prometheus
  11. Custom health indicators: Proactive system monitoring
  12. Justified trade-offs: Every decision documented with rationale
  13. Comprehensive testing: 29 tests with async and concurrent scenarios
  14. Rate limiting: Token bucket algorithm for API protection ✨
  15. Graceful degradation: 429 responses with retry guidance ✨

Resources

External Docs:


Author

Matheus Ferreira Amorim-Trindade

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published