Skip to content

Feat/idr-rate-aggregator-rafi#170

Open
MRafi68 wants to merge 4 commits intoallobankdev:mainfrom
MRafi68:feat/idr-rate-aggregator
Open

Feat/idr-rate-aggregator-rafi#170
MRafi68 wants to merge 4 commits intoallobankdev:mainfrom
MRafi68:feat/idr-rate-aggregator

Conversation

@MRafi68
Copy link

@MRafi68 MRafi68 commented Feb 26, 2026

Architectural Rationale

i. Polymorphism Justification — Why Strategy Pattern?

The three resource types (latest_idr_rates, historical_idr_usd, supported_currencies) each require distinct fetch logic, URL construction, and data transformation. A naive implementation would use a cascading if/else or switch block in the service or controller layer.

The Strategy Pattern was chosen for two key reasons:

Extensibility (Open/Closed Principle): Adding a fourth resource type requires only creating a new class that implements IDRDataFetcher — no existing code is modified. The controller automatically discovers it via Spring's dependency injection of all IDRDataFetcher beans into a Map<String, IDRDataFetcher>. This means the routing logic scales to N resources with zero code changes to the controller or service.

Maintainability: Each strategy is a self-contained, independently testable unit. A bug in the historical rates transformation cannot affect the currency list logic. Each class has a single reason to change. The getResourceType() method serves as a self-registering key, eliminating the need for any external mapping configuration.


ii. Client Factory Bean — Why FactoryBean<WebClient> over @Bean?

Spring's FactoryBean<T> interface is a dedicated creational contract for complex object construction. It was chosen over a simple @Bean method in a @Configuration class for the following reasons:

A @Bean method in @Configuration is a general-purpose object provider with no lifecycle semantics beyond creation. A FactoryBean makes the construction intent explicit — the class is a factory, not a configuration class that happens to produce a bean among many others. This enforces Single Responsibility: WebClientFactoryBean has exactly one job.

FactoryBean also provides getObjectType() and isSingleton() as first-class methods, giving Spring richer metadata about the produced object for proxy generation and context management. Additionally, it encapsulates all WebClient configuration (base URL, headers, codec setup, timeouts) in one place, keeping @Configuration classes clean and free of infrastructure concerns.


iii. Startup Runner Choice — Why ApplicationRunner over @PostConstruct?

Both ApplicationRunner and @PostConstruct run code after bean creation, but they differ in timing and context:

@PostConstruct runs during bean initialisation, before the full application context is ready. This means other beans that the data-loading logic depends on (such as the WebClient or all IDRDataFetcher strategies) may not yet be fully initialised, creating subtle ordering bugs.

ApplicationRunner runs after the entire Spring context is fully refreshed and ready, guaranteeing that every bean, configuration, and dependency is available. This is the correct hook for application-level startup tasks like pre-loading data.

Furthermore, ApplicationRunner receives ApplicationArguments, making it easy to add command-line-driven behaviour in the future (e.g., --refresh-data flag). @PostConstruct has no such extension point.

The .block() call in the runner is intentional and correct here: the application must not begin serving requests until the in-memory store is populated, and blocking at startup (not at request time) achieves this without any concurrency risk.

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