Repository Pattern for Data Access in Fincept Terminal: Architecture and Implementation

Fincept Terminal implements a generic repository pattern built on a thread-safe SQLite wrapper, combining a templated BaseRepository class for shared CRUD operations with singleton concrete repositories that expose type-safe, domain-specific APIs.

The storage layer in FinceptTerminal provides a robust data access architecture designed for maintainability and type safety in a Qt-based environment. At its core lies a repository pattern implementation that separates low-level SQL execution from business logic through a sophisticated template-based inheritance hierarchy. This design enables consistent SQLite interactions across the financial terminal while keeping entity-specific code clean and testable.

Core Components of the Storage Layer

Fincept’s data access stack consists of three coordinated layers that work together to provide type-safe database operations.

The Database Singleton Wrapper

At the foundation sits the Database singleton located in fincept-qt/src/storage/sqlite/Database.h. This class provides a thread-safe entry point through Database::instance() that manages the SQLite connection lifecycle, executes raw statements, and handles transaction boundaries. The public API exposes methods like execute, exec, and begin_transaction, which every repository in the system utilizes for actual database communication. By encapsulating SQLite details within this single utility, the architecture ensures that connection pooling and error handling remain centralized.

The BaseRepository Template

Building atop the Database wrapper is the header-only BaseRepository<Entity> template found in fincept-qt/src/storage/repositories/BaseRepository.h. This generic base class supplies common CRUD helpers including query_list_as, query_list, query_one, query_optional, exec_write, exec_insert, and exec_raw. Because it is templated on the entity type, each concrete repository inherits these low-level operations without code duplication. The implementation also integrates failure logging through the shared Logger, ensuring that database errors are captured consistently across the storage layer.

Concrete Repository Implementations

Every table in the schema has a dedicated repository class that inherits from BaseRepository<Entity>. These concrete implementations follow a strict pattern:

  • Singleton Access: Each exposes static XRepository& instance() to guarantee a single entry point per entity type.
  • Domain-Specific Operations: Methods like SettingsRepository::set, WatchlistRepository::add_stock, and InstrumentRepository::search provide business-logic APIs while internally reusing BaseRepository helpers.
  • Row Mapping: Static map_row functions convert QSqlQuery rows into C++ struct instances, enabling type-safe data retrieval.

Implementing Domain-Specific Repositories

Concrete repositories extend the base template to provide strongly-typed interfaces for specific entities. The SettingsRepository in fincept-qt/src/storage/repositories/SettingsRepository.h and its corresponding .cpp implementation demonstrate this pattern clearly.

For write operations, repositories delegate to inherited helpers:

// From fincept-qt/src/storage/repositories/SettingsRepository.cpp
Result<void> SettingsRepository::set(const QString& key,
                                     const QString& value,
                                     const QString& category) {
    // exec_write comes from BaseRepository<Setting>
    return exec_write(
        "INSERT OR REPLACE INTO settings (key, value, category) VALUES (?, ?, ?)",
        {key, value, category});
}

For queries that return collections, the repository passes a mapping function to the generic query_list method:

// Generic query that returns typed results
Result<QVector<Setting>> SettingsRepository::get_by_category(const QString& cat) {
    // query_list is defined in BaseRepository
    return query_list(
        "SELECT key, value, category, updated_at FROM settings WHERE category = ? ORDER BY key",
        {cat},
        map_row);   // map_row converts QSqlQuery → Setting
}

The map_row function is defined as a static member that constructs entity structs from query results, ensuring the conversion logic lives alongside the repository but remains reusable by the base class templates.

Working with Entity Structs

Entity objects are defined as plain-old-data structs (e.g., Setting, Watchlist, Instrument) that live adjacent to their repositories. These structs contain fields corresponding directly to database columns, with no inheritance requirements or complex constructors. The repository’s map_row function serves as the bridge between the relational database and these C++ objects, maintaining a clean separation between storage representation and application logic.

Practical Usage Examples

Client code interacts with the storage layer exclusively through concrete repository singletons, never touching SQL directly:

// Retrieve a setting with default fallback using SettingsRepository
auto val = fincept::SettingsRepository::instance()
               .get("theme", "light")
               .unwrap();               // Result<QString>
qDebug() << "Current theme:" << val;

// Complex entity creation with WatchlistRepository
auto w = fincept::WatchlistRepository::instance()
             .create("Tech", "#00AEEF")
             .unwrap();               // Result<Watchlist>

fincept::WatchlistRepository::instance()
    .add_stock(w.id, "AAPL", "Apple Inc.", "NASDAQ")
    .unwrap();

The InstrumentRepository in fincept-qt/src/trading/instruments/InstrumentRepository.h demonstrates the pattern applied to high-frequency trading data, offering methods for instrument lookup, search, and bulk replace operations while relying on BaseRepository for actual database interaction.

Summary

FinceptTerminal’s repository pattern implementation delivers a type-safe, reusable, and testable data access architecture:

  • Generic BaseRepository template provides shared query and execution logic across all entity types.
  • Singleton concrete repositories offer domain-specific APIs with guaranteed single-instance access.
  • Database wrapper encapsulates SQLite specifics and transaction management in one location.
  • Static row mapping connects SQL results to C++ structs without runtime overhead.

This structure ensures that data-access concerns remain isolated from business logic, making the codebase maintainable and the storage layer easily extensible for new financial entities.

Frequently Asked Questions

How does the BaseRepository template handle different entity types?

The BaseRepository<Entity> class uses C++ templates to accept any entity struct as its type parameter. This allows the base class to provide generic methods like query_list and exec_write while returning strongly-typed results specific to the entity. Each concrete repository inherits from BaseRepository<TheirEntityType>, gaining access to shared database logic without sacrificing type safety.

Why are concrete repositories implemented as singletons?

Concrete repositories use the singleton pattern (static XRepository& instance()) to ensure a single, global access point for each entity's data operations. This eliminates the need to pass repository instances through constructors, simplifies the API for client code, and guarantees that all database operations for a specific entity route through the same configured instance that inherits the base class's connection management.

Where is the SQL logic located in this architecture?

SQL statements reside within the concrete repository implementation files, such as SettingsRepository.cpp or InstrumentRepository.cpp. While string literals containing queries are defined in these specific repositories, the actual execution logic—parameter binding, error handling, and result iteration—is handled by the inherited methods from BaseRepository and the underlying Database singleton in fincept-qt/src/storage/sqlite/Database.h.

How does the repository handle data mapping from SQLite to C++ objects?

The pattern uses static map_row functions defined in each concrete repository. These functions accept a QSqlQuery reference and return a populated entity struct. When calling base methods like query_list or query_one, the repository passes map_row as a callback pointer, enabling the generic base class to perform the iteration while the repository-specific code handles the column-to-field mapping.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →