跳转至正文

数据层

实现 MVVM 架构的应用数据层 walkthrough。

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

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

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

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

  • Repositories are the source of the truth for application data, and contain logic that relates to that data, like updating the data in response to new user events or polling for data from services. Repositories are responsible for synchronizing the data when offline capabilities are supported, managing retry logic, and caching data.
  • 仓库 是应用数据的单一数据源,包含与该数据相关的逻辑,如响应用户事件更新数据或从 service 轮询数据。仓库负责在支持离线能力时同步数据、管理重试逻辑与缓存数据。
  • Services are stateless Dart classes that interact with APIs, like HTTP servers and platform plugins. Any data that your application needs that isn't created inside the application code itself should be fetched from within service classes.
  • 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>>

定义仓库

#

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

A diagram that highlights the repository component of an application.

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

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

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 层绕过仓库直接调用 service。

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

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

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

领域模型

#

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

在示例应用中,领域模型通过 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);
  }
}

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

反馈

#

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