Skip to content

Refactor db_client Architecture for Database Abstraction #191

@naipaka

Description

@naipaka

Overview

Refactor the db_client package to decouple domain models from Drift implementation, enabling database technology swap (e.g., Drift → Isar/Hive) while maintaining OSS project maintainability. Use @UseRowClass pattern to eliminate conversion overhead while keeping domain models as pure Dart classes.

Requirements

Domain Model Definition

  • Create pure Dart domain model classes independent of database implementation
    • Diary class with id, content, date fields
    • DiaryImage class with id, diaryId, photoId fields
  • Place models in lib/src/models/ directory
  • Implement ==, hashCode, toString for each model
  • Use immutable design with const constructors
  • No database-specific annotations or dependencies

Abstract Interface

  • Define abstract DbClient interface for database operations
  • Return domain models (Diary, DiaryImage) instead of Drift-generated classes
  • Support all current database operations:
    • CRUD operations for diaries and diary images
    • Date range queries with JOIN support
    • Full-text search
    • Backup/restore functionality
  • Place interface in lib/src/db_client.dart

Drift Implementation with @UseRowClass

  • Apply @UseRowClass(Diary) to Diaries table definition
  • Apply @UseRowClass(DiaryImage) to DiaryImages table definition
  • Rename current DbClient class to DriftDbClient
  • Make DriftDbClient implement abstract DbClient interface
  • Eliminate conversion code between Drift entities and domain models
  • Organize Drift-specific code under lib/src/drift/ directory

Package Structure

packages/core/db_client/
  lib/
    src/
      models/              # Pure domain models (DB-agnostic)
        diary.dart
        diary_image.dart

      db_client.dart       # Abstract interface

      drift/               # Drift implementation (internal)
        drift_db_client.dart
        tables.dart
        db_client.steps.dart
        connection/

    db_client.dart         # Public API exports

Public API Design

  • Export domain models from db_client package
  • Export abstract DbClient interface
  • Export DriftDbClient concrete implementation
  • Hide Drift-specific implementation details (tables, migrations)
  • Follow API client pattern: one model class, multiple implementations

Migration Steps

Phase 1: Create Domain Models

  • Create lib/src/models/diary.dart
  • Create lib/src/models/diary_image.dart
  • Define as pure Dart classes

Phase 2: Create Abstract Interface

  • Create abstract DbClient interface
  • Rename existing DbClient to DriftDbClient
  • Make DriftDbClient implement DbClient

Phase 3: Apply @UseRowClass

  • Add @UseRowClass(Diary) to Diaries table
  • Add @UseRowClass(DiaryImage) to DiaryImages table
  • Run dart run build_runner build

Phase 4: Update Implementation

  • Verify Drift generates code using domain models
  • Remove conversion/mapping code
  • Update method signatures to use domain models

Phase 5: Update Dependent Packages

  • Verify features/diary works without changes
  • Verify app package works without changes
  • Run all tests

Phase 6: Clean Up Public API

  • Update lib/db_client.dart exports
  • Remove unnecessary exports

Testing Requirements

  • Existing tests continue to work without modification
  • Add tests for domain model equality and toString
  • Add interface contract tests
  • Add integration tests for Drift implementation
  • Maintain 100% test coverage for db_client package

Future Database Swap Support

  • Design allows adding new implementations (e.g., IsarDbClient)
  • Conversion code only needed within new implementation
  • No changes required in features/ or app/ packages
  • Domain models remain unchanged across implementations

Benefits

Immediate Benefits

  • No conversion overhead (via @UseRowClass)
  • Cleaner codebase with separated concerns
  • Domain models are pure Dart classes
  • Easier to understand for OSS contributors

Long-term Benefits

  • Database technology can be swapped
  • Conversion code isolated to implementation layer
  • Features/app packages unchanged during DB migration
  • Tests remain valid across implementations
  • Gradual migration possible (old and new implementations can coexist)

Technical Constraints

@UseRowClass Requirements

  • Class fields must match table columns
  • Constructor must accept all fields (positional or named)
  • Not an issue for simple CRUD operations

When Conversion May Be Needed

  • If future requirements need complex mapping between normalized DB schema and denormalized domain models
  • Can add conversion layer incrementally when needed

References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions