Open
Conversation
Set up pom.xml with Spring Boot 3.3.6, Java 17, WebFlux (WebClient), and test dependencies (MockWebServer). Add main application class and application.yml with Frankfurter API base URL.
Add ApiResponse wrapper, LatestRateItem (with USD_BuySpread_IDR field), HistoricalRateItem, and CurrencyItem as Java records.
Implement FactoryBean<WebClient> to construct and configure WebClient with externalized base URL, response timeout, and JSON accept header.
Define strategy interface with getResourceType() and fetchAndTransform(). Implement spread factor calculation: (ASCII_SUM % 1000) / 100000.0 yielding 0.00333 for GitHub username "andhikakrstn3".
- LatestIDRRatesFetcher: fetches /latest?base=IDR, calculates USD_BuySpread_IDR - HistoricalIDRUSDFetcher: fetches /2024-01-01..2024-01-05?from=IDR&to=USD - SupportedCurrenciesFetcher: fetches /currencies
Use volatile Map + AtomicBoolean to ensure one-time initialization and thread-safe read access. Data is stored as unmodifiable map.
Implement ApplicationRunner that iterates all IDRDataFetcher strategies, collects data, and initializes DataStoreService once at startup.
Expose GET /api/finance/data/{resourceType} endpoint that reads from
DataStoreService without if/else branching. Handle 404, 503, and 500
errors via @ControllerAdvice.
Test each IDRDataFetcher with MockWebServer: - LatestIDRRatesFetcherTest: verify USD_BuySpread_IDR calculation - HistoricalIDRUSDFetcherTest: verify date sorting and rate parsing - SupportedCurrenciesFetcherTest: verify code/name parsing and sort order
Verify DataStoreService is initialized after ApplicationRunner, all three resource type endpoints return 200, and invalid resource returns 404. Uses MockWebServer to stub Frankfurter API.
Add build/run instructions, cURL examples, personalization note (spread factor 0.00333), and answers to the three architectural rationale questions (Strategy Pattern, FactoryBean, ApplicationRunner).
…up in controller - Add @JsonProperty("USD_BuySpread_IDR") to match spec field naming - Inject Map<String, IDRDataFetcher> via Spring List in controller for map-based strategy lookup without if/else, as required by spec
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GET /api/finance/data/{resourceType}aggregating 3Frankfurter API resources
IDRDataFetcherinterface and 3 concrete implementationsFactoryBean<WebClient>for API client constructionApplicationRunner, store in thread-safe immutableDataStoreServiceUSD_BuySpread_IDR(spread factor: 0.00333)Architectural Rationale
1. Polymorphism Justification (Strategy Pattern)
The Strategy Pattern was chosen over a simpler
if/elseorswitchblock because each of thethree data resources requires fundamentally different API endpoints, response parsing, and
transformation logic. By encapsulating each variation behind a common
IDRDataFetcherinterface, we achieve:
a new
IDRDataFetcherimplementation annotated with@Component. No existing code in thecontroller, runner, or other strategies needs to change.
fix or logic change in one strategy has zero risk of impacting others.
resourceTypestring, with no manual branching.2. Client Factory (FactoryBean)
FrankfurterWebClientFactoryBeanimplementsFactoryBean<WebClient>to encapsulate the fullconstruction and configuration of the
WebClientinstance within Spring's bean lifecycle.Benefits over a standard
@Beanmethod:FactoryBeanpattern clearly separates the"how to build the client" concern into its own dedicated class.
getObject()as the actualbean. Consumers inject
WebClientdirectly without knowing a factory is involved.isSingleton()method ensures Spring caches and reuses thesame
WebClientinstance across all strategy classes.3. Startup Runner Choice (ApplicationRunner)
DataInitializationRunnerimplementsApplicationRunnerto fetch and cache all exchange ratedata at startup. Advantages over
@PostConstruct:ApplicationRunnerexecutes after the entire applicationcontext is fully initialized.
@PostConstructruns during bean initialization, where dependentbeans may not yet be ready.
immediately at startup with a clear error.
API request is served instantly from memory.
ApplicationArguments: Enables future flexibility such as passing customparameters via command-line.
Personalization
andhikakrstn3(1333 % 1000) / 100000.0 = 0.00333Test Plan
mvn verifypasses (11 tests total)