跳至主要內容

資料層

應用程式的資料層(在 MVVM 術語中稱為模型)是所有應用程式資料的來源。作為資料的來源,它是唯一應更新應用程式資料的位置。

它負責從各種外部 API 擷取資料,將資料公開給 UI,處理 UI 中需要更新資料的事件,並在需要時將更新請求傳送給這些外部 API。

本指南中的資料層有兩個主要元件:儲存庫服務

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

  • 儲存庫是應用程式資料的來源,其中包含與該資料相關的邏輯,例如回應新的使用者事件或從服務輪詢資料來更新資料。儲存庫負責在支援離線功能時同步資料、管理重試邏輯和快取資料。
  • 服務是與 API 互動的無狀態 Dart 類別,例如 HTTP 伺服器和平台外掛程式。您的應用程式需要的任何不在應用程式程式碼中建立的資料,都應從服務類別中擷取。

定義服務

#

服務類別是所有架構元件中最不含糊的類別。它是無狀態的,而且其函數沒有副作用。它的唯一工作是封裝外部 API。通常,每個資料來源都有一個服務類別,例如用戶端 HTTP 伺服器或平台外掛程式。

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

例如,在 Compass 應用程式中,有一個 APIClient 服務,用於處理對面向用戶端的伺服器的 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 { /* ... */ }
}

服務本身是一個類別,其中每個方法都會封裝不同的 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 {...}
}

BookingRepository 會將 ApiClient 服務當作輸入,它會使用該服務從伺服器擷取和更新原始資料。服務是私有成員很重要,這樣 UI 層就不能繞過儲存庫並直接呼叫服務。

使用 ApiClient 服務,儲存庫可以輪詢使用者已儲存的預訂在伺服器上發生的更新,並傳送 POST 請求來刪除已儲存的預訂。

儲存庫轉換為應用程式模型的原始資料可能來自多個來源和多個服務,因此儲存庫和服務具有多對多關係。服務可由任意數量的儲存庫使用,而儲存庫可使用多個服務。

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

領域模型

#

BookingRepository 會輸出 BookingBookingSummary 物件,它們是領域模型。所有儲存庫都會輸出對應的領域模型。這些資料模型與 API 模型不同之處在於,它們只包含應用程式其餘部分所需的資料。API 模型包含通常需要篩選、組合或刪除的原始資料,才能對應用程式的 ViewModels 有用。儲存庫會精簡原始資料,並將其輸出為領域模型。

在範例應用程式中,領域模型會透過 BookingRepository.getBooking 等方法上的傳回值公開。getBooking 方法負責從 ApiClient 服務擷取原始資料,並將其轉換為 Booking 物件。它是透過組合來自多個服務端點的資料來完成此作業的。

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 小工具上滑動。ViewModel 會處理該事件,方法是將實際的資料變更委派給 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 方法將 POST 請求傳送至 API 用戶端,並傳回 ResultHomeViewModel 會使用 Result 及其包含的資料,並最終呼叫 notifyListeners 來完成循環。

意見回饋

#

由於本網站的此部分正在發展,我們歡迎您的意見回饋