# Data Layer

> Data sources, DTOs, and mapping them onto the domain models.

- Source: https://engineering.verygood.ventures/architecture/ffca/data/

---

The data layer of a feature is responsible for providing a concrete implementation of the domain layer's repositories. How it does that is completely up to the data layer itself. It may use a [Drift](https://pub.dev/packages/drift) database for local storage and an http api as a remote source. It may introduce an in-memory cache if necessary. Those are implementation details handled by the data layer.

## Data sources

The different storage mechanisms are known as _data sources_. The repository is responsible for mixing these data sources to fulfil the contract, or interface, defined by the domain layer.

For example, to enable some offline browsing, our online store app may have a Drift database data source and a Shopify http api data source. These live within the `data` package of a feature.

The mealify feature-first app does exactly this. Its `meals_data` package holds two data sources side by side: a [Drift database](https://github.com/VGVentures/mealify_feature_first/tree/main/features/meals/meals_data/lib/src/data_sources/meals_database) for local storage, and an [http client](https://github.com/VGVentures/mealify_feature_first/tree/main/features/meals/meals_data/lib/src/data_sources/mealdb_api_client) for the remote API. Neither is visible outside the package: the [repository](https://github.com/VGVentures/mealify_feature_first/blob/main/features/meals/meals_data/lib/src/repositories/meals_repository.dart) combines them, and the domain layer only ever sees a `Meal`.

## DTOs and mappers

Each data source may return a DTO. For example, a `ProductDatabase` class might return a `DbProduct` generated by Drift. The `DbProduct` class knows a lot of information about the database, and is tightly coupled to Drift. We do not want to leak these classes to our domain layer, and eventually to our presentation layer.

Therefore, the data layer is responsible for converting DTOs from each data source into the appropriate domain model, such as a `Product`. This can be achieved in a variety of ways:

- Hand or AI-written extension methods, such as `dbProduct.toDomain()`.
- Hand or AI-written `Converter` classes, such as `class DbToDomainProduct extends Converter<DbProduct, Product>`.
- Libraries that generate the mapping for you using `build_runner`, such as [auto_mappr](https://pub.dev/packages/auto_mappr).

A `Converter` keeps the mapping in one named, testable place, and it reads well at the call site:

```dart
// product_data/lib/src/mappers/db_to_domain_product_converter.dart

/// Converts the Drift row into the domain model.
class DbToDomainProductConverter extends Converter<DbProduct, Product> {
  /// Construct a converter from database products to domain products.
  const DbToDomainProductConverter();

  @override
  Product convert(DbProduct dbProduct) {
    return Product(
      id: dbProduct.id,
      title: dbProduct.title,
      description: dbProduct.description,
      // The database stores cents as an integer. The domain works in whole
      // currency units, so the conversion belongs here, not in a Bloc.
      price: dbProduct.priceInCents / 100,
    );
  }
}
```

The repository then applies it as data comes out of the source, so a `DbProduct` never escapes the package:

```dart
// product_data/lib/src/repositories/products_repository.dart

@override
Future<Product?> getProductById(String id) async {
  final dbProduct = await _database.getProduct(id);
  return dbProduct == null ? null : _dbToDomainConverter.convert(dbProduct);
}
```

One converter per source and direction. A feature reading from both a database and an API has a `DbToDomain...` and an `ApiToDomain...`, which is what [meals_data](https://github.com/VGVentures/mealify_feature_first/tree/main/features/meals/meals_data/lib/src/mappers) does.

For now, we do not believe it makes sense to define an abstract DTO for in-memory, local, and remote storage options. In general, it adds extra, unnecessary mapping to objects that are usually generated by Drift, Swagger, or other libraries.

Next, the [presentation layer](/architecture/ffca/presentation/) builds the UI on top of the domain.
