Skip to content

feat: implement IDR rate aggregator#197

Closed
joniheri wants to merge 9 commits intoallobankdev:mainfrom
joniheri:feat/joniheri-idr-rate-aggregator
Closed

feat: implement IDR rate aggregator#197
joniheri wants to merge 9 commits intoallobankdev:mainfrom
joniheri:feat/joniheri-idr-rate-aggregator

Conversation

@joniheri
Copy link
Copy Markdown

@joniheri joniheri commented Mar 26, 2026

feat: Implement IDR Rate Aggregator Backend Solution

Overview

This PR implements a complete Spring Boot solution for the Allo Bank Backend Developer take-home test, featuring a polymorphic REST API endpoint that aggregates data from the Frankfurter Exchange Rate API. The implementation demonstrates production-ready architecture with clean code, advanced Spring concepts, and comprehensive testing.

Key Features Implemented

1. Polymorphic API Endpoint

  • Endpoint: GET /api/finance/data/{resourceType}
  • Supported Resource Types:
    • latest_idr_rates: Latest exchange rates with personalized IDR spread calculation
    • historical_idr_usd: Historical IDR/USD rates for date range 2024-01-01..2024-01-05
    • supported_currencies: Complete list of supported currency symbols

2. Strategy Pattern Implementation

  • Interface: IDRDataFetcher with resourceType() and fetch() methods
  • Concrete Strategies:
    • LatestIdrRatesFetcher: Handles latest rates with spread calculation
    • HistoricalIdrUsdFetcher: Processes historical time series data
    • SupportedCurrenciesFetcher: Fetches currency symbols
  • Registry: IDRDataFetcherRegistry provides Spring-injected map-based lookup

3. Startup Data Preloading

  • Component: FinanceDataPreloadRunner (implements ApplicationRunner)
  • Behavior: Fetches all three resources exactly once at application startup
  • Resilience: Continues with fallback payload if upstream API fails (configurable fail-fast mode)
  • Storage: Thread-safe in-memory store using AtomicReference for immutability

4. Client Factory Pattern

  • FactoryBean: RestTemplateFactoryBean creates and configures RestTemplate
  • Configuration: Externalizes base URL, timeouts via @ConfigurationProperties
  • Benefits: Centralized client creation, explicit lifecycle control, separation of concerns

5. Personalization & Business Logic

  • GitHub Username: joniheri
  • ASCII Sum Calculation: joniheri → j(106) + o(111) + n(110) + i(105) + h(104) + e(101) + r(114) + i(105) = 856
  • Spread Factor Formula: (856 % 1000) / 100000.0 = 0.00856
  • USD_BuySpread_IDR Calculation: (1 / Rate_USD) * (1 + 0.00856)

6. Production Readiness Features

  • Error Handling: Global exception handler with appropriate HTTP status codes
  • Configuration: Externalized properties in application.yml
  • Documentation: Swagger/OpenAPI integration with UI at /swagger-ui/index.html
  • Testing: Unit tests for all strategies, integration tests for preload runner
  • Code Coverage: JaCoCo reporting (target/site/jacoco/index.html)
  • Postman Collection: Pre-configured requests for all endpoints

Available Endpoints

Core API Endpoints (Testable via Browser/Postman/cURL)

  1. Home Endpoint

    • Route: GET /
    • Description: Returns basic app status and main endpoint info
    • Example Response:
      {
        "app": "allo-backend-test",
        "message": "Service is running",
        "endpoints": {
          "finance_data": "/api/finance/data/{resourceType}"
        }
      }
    • Test Command: curl http://localhost:8080/
  2. Finance Data Endpoint (Polymorphic)

    • Route: GET /api/finance/data/{resourceType}
    • Path Parameter: resourceType (string) - One of: latest_idr_rates, historical_idr_usd, supported_currencies
    • Description: Returns preloaded finance data based on resource type
    • Example Requests:
      • curl http://localhost:8080/api/finance/data/latest_idr_rates
      • curl http://localhost:8080/api/finance/data/historical_idr_usd
      • curl http://localhost:8080/api/finance/data/supported_currencies
    • Error Response (for invalid resourceType):
      {
        "error": "Bad Request",
        "message": "Unsupported resourceType: invalid_type",
        "supportedResourceTypes": [
          "latest_idr_rates",
          "historical_idr_usd",
          "supported_currencies"
        ]
      }

Documentation & Testing Endpoints

  1. Swagger UI

    • Route: GET /swagger-ui/index.html
    • Description: Interactive API documentation with try-it-out functionality
    • Usage: Open in browser to explore and test all endpoints interactively
    • Access: http://localhost:8080/swagger-ui/index.html
  2. OpenAPI JSON Specification

    • Route: GET /v3/api-docs
    • Description: Raw OpenAPI 3.0 JSON specification
    • Usage: For integration with external API tools or documentation generators
    • Test Command: curl http://localhost:8080/v3/api-docs
  3. JaCoCo Test Coverage Report

    • Route: GET /jacoco/ (served from target/site/jacoco/index.html)
    • Description: HTML report showing code coverage metrics for all classes
    • Usage: Open in browser to view detailed coverage statistics
    • Access: After running mvn test jacoco:report, open target/site/jacoco/index.html in browser

Architectural Rationale

1. Polymorphism Justification

The Strategy Pattern was chosen over conditional logic in the service layer to isolate each resource-fetching behavior into dedicated classes. This approach prevents the growth of complex if/else chains in the controller, improves extensibility (adding new resource types requires only new strategy classes), and maintains clean separation of concerns. Each strategy encapsulates its own data transformation logic, making the codebase more maintainable and testable.

2. Client Factory Benefits

Using FactoryBean<RestTemplate> centralizes client creation and configuration in a dedicated component, providing explicit control over the client's lifecycle. This approach cleanly separates the concern of client construction from business logic classes and satisfies the requirement to avoid simple @Bean methods for client creation.

3. Startup Runner Choice

ApplicationRunner was selected over @PostConstruct because it executes after full Spring context initialization and dependency wiring is complete. This ensures safer startup orchestration, provides clearer operational intent, and enables easier integration testing by allowing verification of data loading before the application context is fully ready.

Testing & Validation

Automated Tests

  • Unit Tests: FinanceDataServiceTest, FinanceDataStoreTest, strategy implementations
  • Integration Tests: AlloBankTestApplicationTests (context loading)
  • Coverage: JaCoCo generates HTML reports in target/site/jacoco/

Manual Testing Steps

  1. Start the Application:

    mvn spring-boot:run

    Wait for "Started AlloBankTestApplication" in logs (data preload completes here).

  2. Test Core Endpoints:

    # Home endpoint
    curl http://localhost:8080/
    
    # Finance data endpoints
    curl http://localhost:8080/api/finance/data/latest_idr_rates
    curl http://localhost:8080/api/finance/data/historical_idr_usd
    curl http://localhost:8080/api/finance/data/supported_currencies
    
    # Test invalid resource type (should return 400)
    curl http://localhost:8080/api/finance/data/invalid_type
  3. View API Documentation:

  4. Check Test Coverage:

    mvn test jacoco:report

    Then open target/site/jacoco/index.html in browser to view coverage report.

  5. Import Postman Collection:

    • Import postman/allo-backend-test.postman_collection.json
    • Update baseUrl variable to http://localhost:8080
    • Run all requests to verify functionality

Expected Response Examples

  • latest_idr_rates:

    {
      "resourceType": "latest_idr_rates",
      "data": {
        "base": "EUR",
        "EUR": 1,
        "date": "2024-01-05",
        "rates": {
          "IDR": 17000.0,
          "USD": 1.08,
          "USD_BuySpread_IDR": 0.008568
        }
      }
    }
  • historical_idr_usd:

    {
      "resourceType": "historical_idr_usd",
      "data": {
        "base": "USD",
        "USD": 1,
        "quote": "IDR",
        "rates": [
          { "date": "2024-01-01", "idr_per_usd": 15500.0 },
          { "date": "2024-01-02", "idr_per_usd": 15550.0 }
        ]
      }
    }

Configuration

frankfurter:
  api:
    base-url: https://api.frankfurter.app
    historical-range: 2024-01-01..2024-01-05
    github-username: joniheri
    connect-timeout-millis: 15000
    read-timeout-millis: 20000

finance:
  preload:
    enabled: true
    fail-fast: false

Files Changed

  • New Files: 35+ Java classes, configuration files, tests, documentation
  • Modified Files: README.md (simplified), application.yml
  • Architecture: Clean layered architecture with proper separation of concerns
  • Dependencies: Spring Boot 3.3.5, SpringDoc OpenAPI, JaCoCo, JUnit 5

Checklist

  • Polymorphic endpoint implemented with 3 resource types
  • Strategy pattern correctly implemented
  • Data preloaded exactly once at startup
  • Thread-safe in-memory storage
  • FactoryBean used for client creation
  • Personalized spread factor calculation
  • Comprehensive error handling
  • Unit and integration tests
  • Swagger documentation
  • Code coverage reporting
  • Postman collection for testing
  • Clean commit history with descriptive messages

This implementation demonstrates advanced Spring Boot concepts, clean architecture, and production-ready code quality suitable for enterprise applications.

@joniheri joniheri closed this by deleting the head repository Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant