資料層
對實現 MVVM 架構的應用的資料層進行詳細介紹。
應用程式的資料層(在 MVVM 術語中稱為模型)是所有應用程式資料的單一事實來源。作為事實來源,它是更新應用程式資料的唯一場所。
它負責從各種外部 API 獲取資料,將資料暴露給 UI,處理來自 UI 需要更新資料的事件,並根據需要向這些外部 API 傳送更新請求。
本指南中的資料層有兩個主要元件:倉庫 (repositories) 和 服務 (services)。
- 倉庫是應用程式資料的事實來源,包含與這些資料相關的邏輯,例如響應新的使用者事件來更新資料,或從服務中輪詢資料。在支援離線功能時,倉庫負責同步資料、管理重試邏輯以及快取資料。
- 服務是無狀態的 Dart 類,用於與 API(如 HTTP 伺服器和平臺外掛)進行互動。應用程式所需的任何非自身程式碼建立的資料都應從服務類中獲取。
定義服務
#服務類是所有架構元件中最明確的一個。它是無狀態的,且其函式沒有副作用。它的唯一工作是封裝外部 API。通常每個資料來源對應一個服務類,例如客戶端 HTTP 伺服器或平臺外掛。
例如,在 Compass 應用中,有一個 APIClient 服務,負責處理對客戶端伺服器的 CRUD 呼叫。
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>>。
定義倉庫
#倉庫的唯一職責是管理應用程式資料。倉庫是單一型別應用程式資料的事實來源,它應該是該資料型別被變更的唯一場所。倉庫負責從外部源輪詢新資料、處理重試邏輯、管理快取資料以及將原始資料轉換為領域模型。
你應該為應用程式中每種不同型別的資料分別建立一個倉庫。例如,Compass 應用擁有名為 UserRepository、BookingRepository、AuthRepository、DestinationRepository 等的倉庫。
以下示例是 Compass 應用中的 BookingRepository,展示了倉庫的基本結構。
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 請求以刪除已儲存的預訂。
倉庫轉換為應用模型所需的原始資料可以來自多個源和多個服務,因此倉庫和服務之間是多對多關係。一個服務可以被任意數量的倉庫使用,而一個倉庫也可以使用多個服務。
領域模型
#BookingRepository 輸出 Booking 和 BookingSummary 物件,這些是領域模型。所有倉庫都輸出相應的領域模型。這些資料模型與 API 模型不同,它們只包含應用程式其餘部分所需的資料。API 模型包含原始資料,通常需要過濾、組合或刪除才能對應用的檢視模型有用。倉庫會對原始資料進行精煉,並將其作為領域模型輸出。
在示例應用中,領域模型是透過諸如 BookingRepository.getBooking 等方法的返回值暴露出來的。getBooking 方法負責從 ApiClient 服務獲取原始資料,並將其轉換為 Booking 物件。它透過組合來自多個服務端點的資料來實現這一點。
// 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 元件上滑動)來刪除已儲存的預訂。檢視模型透過將實際的資料變更委託給 BookingRepository 來處理該事件。以下程式碼片段顯示了 BookingRepository.deleteBooking 方法。
Future<Result<void>> delete(int id) async {
try {
return _apiClient.deleteBooking(id);
} on Exception catch (e) {
return Result.error(e);
}
}
倉庫使用 _apiClient.deleteBooking 方法向 API 客戶端傳送 POST 請求,並返回一個 Result。HomeViewModel 消費該 Result 及其包含的資料,最後呼叫 notifyListeners,完成整個週期。
反饋
#由於網站的此部分內容在不斷演進,我們歡迎你的反饋!