Data Layer: Repositories & Data Sources
Relevant source files
The following files were used as context for generating this wiki page:- data/src/main/java/com/vocabri/data/repository/word/WordRepositoryImpl.kt
- data/src/main/java/com/vocabri/data/datasource/word/WordLocalDataSourceImpl.kt
- data/src/main/java/com/vocabri/data/datasource/word/WordDataSource.kt
- domain/src/main/java/com/vocabri/domain/repository/WordRepository.kt
- data/src/main/java/com/vocabri/data/di/DataModule.kt
- data/src/test/java/com/vocabri/data/WordRepositoryImplTest.kt
The data layer in Vocabri is responsible for all data operations, providing a clean API for the domain layer to access and manipulate application data. It implements the repository pattern, abstracting the origin of the data (local database or remote server) from the rest of the app. This design supports an offline-first architecture, where the local database is the primary source of truth, with a remote source for optional synchronization.
The core components are WordRepository, which defines the contract for data operations, and its implementation WordRepositoryImpl, which orchestrates one or more data sources. The primary data source is WordLocalDataSourceImpl, which interacts directly with the SQLDelight database. A stubbed remote data source (WordRemoteDataSourceStub) is also in place for future cloud features.
Sources: README.md, data/src/main/java/com/vocabri/data/repository/word/WordRepositoryImpl.kt:32-35
Architecture Overview
The data layer follows a standard layered architecture. The domain layer defines repository interfaces, and the data layer provides their concrete implementations. These implementations coordinate between local and remote data sources to fulfill data requests.
This diagram illustrates the relationship between the key components involved in data management.
Key Components
| Component | Description | File |
|---|---|---|
WordRepository | An interface in the domain layer defining the contract for all word-related data operations like observing, getting, inserting, and deleting words. | domain/src/main/java/com/vocabri/domain/repository/WordRepository.kt |
WordRepositoryImpl | The implementation of WordRepository. It orchestrates data flow between local and remote data sources. It contains logic for checking for duplicate words before insertion. | data/src/main/java/com/vocabri/data/repository/word/WordRepositoryImpl.kt |
WordDataSource | An interface defining the contract for a local word data source. It handles direct interaction with the local persistence layer. | data/src/main/java/com/vocabri/data/datasource/word/WordDataSource.kt |
WordLocalDataSourceImpl | The SQLDelight-based implementation of WordDataSource. It executes SQL queries to perform CRUD operations on the vocabri.db database. | data/src/main/java/com/vocabri/data/datasource/word/WordLocalDataSourceImpl.kt |
RemoteWordDataSource | An interface for a remote data source. Currently, a stub implementation is used. | data/src/main/java/com/vocabri/data/datasource/word/WordDataSource.kt |
VocabriDatabase | The SQLDelight-generated database class that provides type-safe queries. It is configured with an AndroidSqliteDriver. | data/src/main/java/com/vocabri/data/di/DataModule.kt:32-38 |
Data Flows
Inserting a Word
When a new word is added, WordRepositoryImpl first checks for duplicates. If none exist, it attempts to save the word to the remote data source. Regardless of the remote operation's success, it proceeds to save the word in the local SQLDelight database, which acts as the single source of truth.
Observing and Fetching Words
Word observation is reactive. WordRepositoryImpl delegates directly to WordLocalDataSourceImpl, which returns a Flow from a SQLDelight query. When the underlying data changes, the flow emits a new list of words. The local data source is responsible for querying all related tables (base word, part-of-speech specifics, translations, examples) and mapping the results into the correct Word domain models.
Deleting a Word
Similar to insertion, deletion is attempted first on the remote source and then executed on the local source. The local database uses ON DELETE CASCADE constraints, so deleting a word from the WordBaseEntity table automatically removes all its associated data (translations, examples, etc.).
Dependency Injection
The components of the data layer are wired together using Koin. The dataModule is responsible for providing singleton instances of the database, data sources, and repositories.
The module defines:
- A singleton for
VocabriDatabase, configured with the Android-specificAndroidSqliteDriver. - Singleton bindings for
WordLocalDataSourceImplto theWordDataSourceinterface. - A singleton binding for
WordRemoteDataSourceStubto theRemoteWordDataSourceinterface. - A singleton binding for
WordRepositoryImplto theWordRepositoryinterface.
// Source: data/src/main/java/com/vocabri/data/di/DataModule.kt
val dataModule = module {
single {
VocabriDatabase(
driver = AndroidSqliteDriver(
schema = VocabriDatabase.Schema.synchronous(),
context = androidContext(),
name = "vocabri.db",
),
)
}
single<HttpClient> { VocabriHttpClientFactory().create() }
singleOf(::WordLocalDataSourceImpl) { bind<WordDataSource>() }
singleOf(::WordRemoteDataSourceStub) { bind<RemoteWordDataSource>() }
singleOf(::DebugDataSourceImpl) { bind<DebugDataSource>() }
singleOf(::WordRepositoryImpl) { bind<WordRepository>() }
singleOf(::DebugRepositoryImpl) { bind<DebugRepository>() }
singleOf(::UuidIdGenerator) { bind<IdGenerator>() }
}
Sources: data/src/main/java/com/vocabri/data/di/DataModule.kt:30-51