跳转至正文

数据层

逐步讲解一个实现 MVVM 架构的应用的数据层。

应用的数据层在 MVVM 术语中称为 model,是所有应用数据的单一数据源。作为单一数据源,应用数据只应在此更新。

它负责从各类外部 API 消费数据、向 UI 暴露数据、处理需要更新数据的 UI 事件,并在需要时向外部 API 发送更新请求。

本指南中的数据层有两个主要组件:repositoryservice

A diagram that highlights the data layer components of an application.

  • Repository 是应用数据的单一数据源,包含与该数据相关的逻辑,如响应用户事件更新数据或从 service 轮询数据。 Repository 负责在支持离线能力时同步数据、管理重试逻辑与缓存数据。

  • Service 是无状态 Dart 类,与 HTTP 服务器、平台插件等 API 交互。应用所需且非应用代码内创建的数据都应在 service 类中获取。

定义 service

#

Service 类是架构组件中最明确的一类:无状态,函数无副作用,唯一职责是封装外部 API。通常每个数据源一个 service 类,如面向客户端的 HTTP 服务器或平台插件。

A diagram that shows the inputs and outputs of service objects.

例如 Compass 应用中有 APIClient service,处理面向客户端服务器的 CRUD 调用。

api_client.dart
dart
class ApiClient {
  // Some code omitted for demo purposes.

  Future<Result<List<ContinentApiModel>>> getContinents() async { /* ... */ }

  Future<Result<List<DestinationApiModel>>> getDestinations() async { /* ... */ }

  Future<Result<List<ActivityApiModel>>> getActivityByDestination(String ref) async { /* ... */ }

  Future<Result<List<BookingApiModel>>> getBookings() async { /* ... */ }

  Future<Result<BookingApiModel>> getBooking(int id) async { /* ... */ }

  Future<Result<BookingApiModel>> postBooking(BookingApiModel booking) async { /* ... */ }

  Future<Result<void>> deleteBooking(int id) async { /* ... */ }

  Future<Result<UserApiModel>> getUser() async { /* ... */ }
}

Service 本身是一个类,每个方法封装不同 API 端点并暴露异步响应对象。延续删除已保存预订的示例,deleteBooking 返回 Future<Result<void>>

定义 repository

#

Repository 的唯一职责是管理应用数据。 Repository 是某一类应用数据的单一数据源,且应是唯一能变更该数据类型的地方。 Repository 负责从外部源轮询新数据、处理重试逻辑、管理缓存数据,并将原始数据转换为领域模型。

A diagram that highlights the repository component of an application.

应用中每种不同数据类型应有一个独立 Repository。例如 Compass 有 UserRepositoryBookingRepositoryAuthRepositoryDestinationRepository 等。

以下示例来自 Compass 的 BookingRepository,展示 Repository 的基本结构。

booking_repository_remote.dart
dart
class BookingRepositoryRemote implements BookingRepository {
  BookingRepositoryRemote({
    required ApiClient apiClient,
  }) : _apiClient = apiClient;

  final ApiClient _apiClient;
  List<Destination>? _cachedDestinations;

  Future<Result<void>> createBooking(Booking booking) async {...}
  Future<Result<Booking>> getBooking(int id) async {...}
  Future<Result<List<BookingSummary>>> getBookingsList() async {...}
  Future<Result<void>> delete(int id) async {...}
}

BookingRepositoryApiClient service 为输入,用于从服务器获取与更新原始数据。 Service 应为私有成员,以免 UI 层绕过 Repository 直接调用 service。

借助 ApiClient,Repository 可轮询服务器上用户已保存预订的更新,并通过 POST 请求删除预订。

Repository 转换为应用模型的原始数据可来自多个源与多个 service,因此 repository 与 service 为多对多关系:一个 service 可被任意数量 repository 使用,一个 repository 也可使用多个 service。

A diagram that highlights the data layer components of an application.

领域模型 (Domain Model)

#

BookingRepository 输出 BookingBookingSummary领域模型。所有 Repository 都输出对应的领域模型。这些数据模型与 API 模型的区别在于仅包含应用其余部分所需数据; API 模型含常需过滤、合并或删除才有用的原始数据,Repository 精炼后以领域模型输出。

在示例应用中,领域模型通过 BookingRepository.getBooking 等方法返回值暴露。 getBookingApiClient 获取原始数据并转换为 Booking,通过合并多个 service 端点数据实现。

booking_repository_remote.dart
dart
// This method was edited for brevity.
Future<Result<Booking>> getBooking(int id) async {
  try {
    // Get the booking by ID from server.
    final resultBooking = await _apiClient.getBooking(id);
    if (resultBooking is Error<BookingApiModel>) {
      return Result.error(resultBooking.error);
    }
    final booking = resultBooking.asOk.value;

    final destination = _apiClient.getDestination(booking.destinationRef);
    final activities = _apiClient.getActivitiesForBooking(
            booking.activitiesRef);

    return Result.ok(
      Booking(
        startDate: booking.startDate,
        endDate: booking.endDate,
        destination: destination,
        activity: activities,
      ),
    );
  } on Exception catch (e) {
    return Result.error(e);
  }
}

完成事件循环

#

本页你已看到用户如何删除已保存预订:从在 Dismissible 上滑动的事件开始, view model 将实际数据变更委托给 BookingRepository。以下片段展示 BookingRepository.deleteBooking 方法。

booking_repository_remote.dart
dart
Future<Result<void>> delete(int id) async {
  try {
    return _apiClient.deleteBooking(id);
  } on Exception catch (e) {
    return Result.error(e);
  }
}

Repository 通过 _apiClient.deleteBooking 向 API 客户端发送 POST 请求并返回 ResultHomeViewModel 消费 Result 及其数据,最终调用 notifyListeners,完成循环。

反馈

#

网站本节内容仍在完善中, 欢迎提供反馈