98. Base Service Pattern for Indicators
Status: Accepted Date: 2025-07-06
Context
The Minerva module will contain services for dozens of different technical indicators (RSI, MACD, Bollinger Bands, etc.). While the calculations are different, the way we fetch, store, and retrieve their data should be consistent. If we allow each indicator service to be implemented in a completely different way, the module will quickly become a collection of inconsistent, hard-to-maintain one-offs.
Decision
We will implement a Base Indicator Service design pattern. This will consist of a generic, abstract base class that all specific indicator services must extend.
The BaseIndicatorService<TEntity, TResponse> will define:
- A common constructor that injects shared dependencies (like the
mercury-taclient and the database repository). - A standard public interface with common methods like
getHistory(symbol, timeframe, limit). - The core workflow for fetching data:
- Check the Redis cache for fresh data.
- If the cache is a miss, call the
mercury-taPython service to perform the calculation. - Store the new result in the PostgreSQL database and update the Redis cache.
- Return the data.
- An
abstractproperty (e.g.,endpoint) that concrete classes must implement to specify whichmercury-taAPI endpoint to call.
A concrete RsiService, for example, would extend BaseIndicatorService and simply provide the specific entity type and the endpoint name (/ta/rsi). All the common caching and fetching logic would be inherited.
Consequences
Positive:
- Consistency and Standardization: Enforces a consistent implementation for all indicators. Every indicator service will have the same methods and follow the same logic, making the module highly predictable and easy to maintain.
- DRY (Don't Repeat Yourself): The common logic for caching, data fetching, and database interaction is written only once in the base class, avoiding a huge amount of boilerplate code.
- Rapid Development of New Indicators: Adding a new indicator becomes extremely fast. A developer simply needs to create a new class that extends the base class and implement one or two required properties.
Negative:
- Less Flexibility for Atypical Indicators: If we encounter a new indicator that has a very different data model or fetching logic that doesn't fit the pattern, it might be awkward to force it into the base class structure.
- "Magic" of Inheritance: The logic is hidden away in a parent class, which can sometimes make it harder for a new developer to understand the exact workflow without looking at the base class definition.
Mitigation:
- Provide an Escape Hatch: The pattern will be the standard, but we will allow for a fully custom implementation if an indicator is truly a special case. This should be a rare exception, however, and would require justification.
- Clear Documentation: The
BaseIndicatorServicewill be well-documented, explaining the common logic it provides. Developers will be instructed to familiarize themselves with the base class as part of their onboarding to theMinervamodule. The abstraction is simple enough that it should not be a major hurdle.