Flutter riverpod应用程序架构介绍
这篇文章可以当作概述,相关文章阅读:
如果您需要帮助为您的 Flutter 应用程序选择最适合的项目结构,可以查看:
如果您想探索其他流行架构(如 MVP、MVVM 或 Clean Architecture)并了解它们如何与此处提出的架构相比较,可以阅读以下内容:
要了解 Riverpod 架构中每个层的更多信息,请阅读本系列中的其他文章:
- Flutter riverpod应用程序架构介绍
- Flutter 应用程序架构:存储库模式(Repository Pattern)
- Flutter 应用程序架构:域模型(Domain Mode)
- Flutter 应用程序架构:演示层(Presentation Layer)
- Flutter 应用程序架构:应用层(Application Layer)
- 如何使用 Riverpod 架构获取数据并执行数据变更
- 案例: 使用 Riverpod (TMDB API) 的 Flutter 电影应用程序 使用 Flutter 和 Firebase 的时间跟踪应用程序 flutter 电商
构建复杂应用程序时,选择正确的应用程序架构至关重要,因为它可以帮助您构建代码结构,并支持代码库的持续增长。
良好的架构应该帮助您处理复杂性而不妨碍。但要做到恰到好处并不容易:
- “不够” 的架构会导致代码组织混乱,缺乏明确的约定
- “过多” 的架构会导致过度工程化,使得即使是简单的更改也变得困难
在实践中,事情可能相当微妙,很难取得平衡。
因此,在本文中,我分享了一种新的参考架构,您可以使用它来实现以下目标:
- 在您的 UI 代码(1)、业务逻辑(2)和数据访问逻辑(3)之间实现良好的关注点分离
- 使用最少的样板代码轻松获取和缓存数据
- 在处理 UI 状态(数据、加载、错误)时执行数据变更的可预测方式
- 编写可测试的代码并轻松模拟依赖关系
这种架构严重依赖于 Riverpod 软件包,充分利用其反应式缓存和数据绑定功能。
但是,为什么我们首先需要一个参考架构呢?
Riverpod 不太主观
Riverpod 很棒。我在所有我的应用程序中都使用它,并写了许多关于它的教程 - 但官方文档没有提供任何关于如何构建代码结构的指导。
因此,当涉及到诸如以下重要决策时,您就处于孤立无援的境地:
- 如何将 UI 代码与业务逻辑和数据访问逻辑分离?
- 您应该编写哪些类?它们的职责是什么?它们如何相互通信?
- 如何使您的代码更具扩展性 - 以便不同的团队成员可以独立地处理多个功能?
- 您应该如何组织您的文件?
- 错误处理怎么样?它应该发生在哪里?错误应该如何传播到 UI?
- 您应该使用哪些提供者,它们应该在哪里声明?
这些决定很重要。如果您随意处理它们,就会导致性能问题、错误和影响开发速度的持续维护问题。
这就是拥有非常主观的应用程序架构可以产生的全部差异。 👇
Riverpod + 参考应用程序架构 👌
在构建各种复杂性的 Flutter 应用程序时,我对应用程序架构进行了大量实验,并深入了解了什么有效,什么无效。
结果是一个参考架构,我在所有最新项目中都使用过。
没有更好的名称,我将其称为 Riverpod 架构 - 请记住,这只是我的看法,并不是 Remi Rousselet(Riverpod 的作者)认可的“官方”架构。
这种架构包括四个层(数据、领域、应用和表示)。
这是一个预览:
应用程序架构使用数据、领域、应用和演示层。箭头显示层之间的依赖关系。每个层都有自己的职责,并且在跨界限通信的方式上有明确的协议。
因此,让我们更详细地看看每个层。 👇
由于需要涵盖的内容很多,本文仅包括对四个层的概念性高级概述。下面您会找到链接,可以查看更详细的各层文章。
演示层
这通常被称为 UI 层。关于 Android 应用程序架构的指南 很好地描述了它:
UI 的角色是在屏幕上显示应用程序数据,同时也作为用户交互的主要点。每当数据发生变化时,无论是由用户交互(例如按按钮)还是外部输入(例如网络响应)引起的,UI 都应更新以反映这些变化。实际上,UI 是从数据层检索的应用程序状态的可视化表示。
在我们的架构中,演示层包含两种主要组件:
- Widget,它是要在屏幕上显示的数据的表示。
- 控制器,它执行异步数据变更并管理小部件状态。
Flutter 应用程序架构:演示层。箭头显示层之间的依赖关系。这些控制器本身通常被表示为 AsyncNotifier
子类
领域层
领域层的主要作用是定义代表来自数据层的特定于应用程序的模型类。
模型类是简单的数据类,具有以下要求:
- 它们始终是不可变的。
- 它们包含序列化逻辑(例如
fromJson
和toJson
方法)。 - 它们实现了
==
运算符和hashCode
方法。
以电子商务应用程序为例,我们可以将以下实体识别并表示为模型类:
电子商务应用程序:实体及其关系模型类可能依赖于其他模型类(例如 ShoppingCart
类可能包含产品列表)。但它们不知道从哪里获取数据,也没有其他依赖项。因此,模型类可以在应用程序的任何其他地方(小部件、控制器、服务、存储库)导入和使用。
为了更轻松地定义数据类中的属性和方法,您可以使用诸如 Freezed 和 Equatable 这样的软件包,或者使用 Dart Data Class Generator 等工具。
数据层
数据层包含三种类型的类:
- 数据源,用于与外部世界通信的第三方 API(例如远程数据库、REST API 客户端、推送通知系统、蓝牙接口)。
- 数据传输对象(DTO),由数据源返回。在通过网络发送数据时,DTO通常表示为非结构化数据(例如 JSON)。
- 仓库,用于从各种源(例如后端 API)访问 DTO,并将其作为类型安全的模型类(也称为实体)提供给应用程序的其他部分。
Flutter 应用程序架构:数据层。箭头显示层之间的依赖关系。请注意,数据源和DTO都是外部依赖项。要使用它们,您需要将其导入到应用程序中并使用其 API。
另一方面,仓库是存在于代码库中的类,因此您的工作是实现它们并设计其 API。
如果您的 Flutter 应用程序与本地或远程数据库进行通信,那么该数据库是数据的唯一真相来源。就您的应用程序而言,仓库是访问这些真相来源的入口。该应用程序架构通过从数据层到 UI 实现单向数据流来解决这个问题。
应用层
在构建复杂应用程序时,我们可能会发现自己编写的逻辑:
- 依赖于多个数据源或仓库
- 需要被多个小部件使用(共享)
在这种情况下,将该逻辑放入我们已有的类中(控制器或仓库)是很诱人的。
但这会导致关注点分离不良,使我们的代码难以阅读、维护和测试。
为了解决这个问题,我们可以引入一个新的、可选的层,称为应用层。在其中,我们可以添加服务类,作为控制器(仅管理小部件状态)和仓库(与不同数据源通信)之间的中间人。
下面是一个示例,展示了一个 CartService
类,用于在控制器和仓库之间进行调解:
总结
到目前为止,我们已经了解了我提出的 Riverpod 架构中的四个主要层以及其中的类(小部件、控制器、模型、服务、仓库、数据源)。
但所有这些不同的类是如何相互交互的,以及我们如何使用它们来构建应用程序中的工作功能呢?
这就是 Riverpod 及其所有有用的提供者发挥作用的地方。在构建移动应用程序时,我们所做的大部分工作归结为两件事:
- 获取数据并在 UI 中显示
- 响应输入事件执行数据变更