Description
Build a backend client that periodically fetches experiment configurations from the GrowthBook API.
The client should support configurable polling intervals, handle timeouts and retries, and log failures without affecting API availability.
The fetched data should be made available to the experiment configuration service for processing.
Technical Notes
Relevant files:
- config/configuration.js - owns service config and axios defaults, so GrowthBook URL, polling interval, token/secret, and timeout config live here
- app.js - startup/wiring point so where the poller should be initialized
- service/serviceRegistry.js - register the new services
New files:
- service/growthBookClient.js
- data access layer
- how to talk to GrowthBook to fetch data
- a thin, reusable client for making requests to the GrowthBook API
- responsibilities:
- know the endpoint URLs
- handle HTTP requests
- apply auth headers
- handle timeouts / retries (lightweight)
- return raw (or lightly parsed) data
- use by growthBookPoller
- service/growthBookPoller.js
- orchestration layer
- when and how often to fetch from GrowthBook and what to do with the results
- a background process that periodically fetches data using the client and updates the service’s current state
- responsibilities:
- run on an interval
- call growthBookClient.fetchExperiments()
- handle failures gracefully
- maintain latest successful result
- trigger downstream processing (validate > adapt > stitch > store)
- initialized in app.js
- calls into experimentConfigurationService.js on update
Why separation?
- clean unit tests:
- test client with mocked HTTP
- test poller with mocked client
- flexible reuse of the client
How these 2 should work together:
- GrowthBookPoller calls GrowthBookClient
- GrowthBookClient returns raw experiments
- experiments handed to configuration service
- experiments go thru funnel:
- validate
- adapt
- stitch
- store
- API serves result
How should failure be handled?
- If we have not had a successful request from GB, we should not invalidate the local cache. We would want Varnish to preserve its cache until we get a valid update from GB.
Data retrieved from GB should be stored in the TK database as well as in memory. (Note: if there's a reason that storing in the database expands the scope, this requirement can be changed.)
Pseudo Code
class GrowthBookClient { constructor( { baseUrl, apiKey, timeout } ) { this.baseUrl = baseUrl; this.apiKey = apiKey; this.timeout = timeout; } async fetchExperiments() { const response = await axios.get( `${this.baseUrl}/experiments`, { headers: { Authorization: `Bearer ${this.apiKey}` }, timeout: this.timeout } ); return response.data; } } // ... class GrowthBookPoller { constructor( { client, intervalMs, onUpdate, logger } ) { this.client = client; this.intervalMs = intervalMs; this.onUpdate = onUpdate; this.logger = logger; this.timer = null; } start() { this.run(); this.timer = setInterval( () => this.run(), this.intervalMs ); } async run() { try { const data = await this.client.fetchExperiments(); this.onUpdate( data ); // hand off to config service this.logger.info( 'GrowthBook poll succeeded' ); } catch ( e ) { this.logger.error( 'GrowthBook poll failed', e ); } } }
Acceptance Criteria
- Backend can fetch experiment configs from the configured GrowthBook API endpoint.
- Poll interval, timeout, and retry settings are configurable. Default polling interval should start at 1minute.
- Poll failures are logged and do not crash the service.
- Poller can be started from app initialization and cleanly skipped/disabled via config.
- Unit tests cover successful fetch, timeout/failure behavior, and malformed upstream response handling.
- Documentation?