Flutter中非常优秀的分层架构

好的项目架构,对于项目开发过程中的功能交付、维护、拓展等会有重大的影响。特别是在一些较大的项目,好的架构提高交付效率,便于维护。

架构

什么是项目架构?
应用程序架构是我们组织项目的逻辑方式,以及各种组件如何相互交互以满足业务需求。 我们希望遵循标准并轻松识别我们在代码库中开发功能所需的组件。 我们定义这些组件之间的关系和交互方式可以减少或增加我们项目的复杂性,这对团队的生产力有重大影响。

层(Layers)

层是构成架构的组件。 通过为每一层分配特定的职责来定义它们。应该保持这些层简单,同时与其他层保持隔离(isolated),便于单独的维护。

单一职责原则(Single Responsibility Principle)

单一职责是面向对象设计的 SOLID 原则之一。它告诉我们在设计功能时可以坚持的以下原则:
1、类小且功能单一。不是在一个类中包含众多的功能特色,可以参考Flutter中基础Widget
2、更好地隔离组件以进行测试。不应该模拟很多组件来测试特定的功能。
3、维护特定功能而不影响代码的其他部分。 通过解耦代码,可以减少每个块对另一个块的依赖。 这样,当进行更改时,它们对整个代码库的影响就会较小。

职责(Responsibilities)

从实际项目开发经验中总结,通常应用程序有三大职责。
1、数据层(Data layer):该层直接与数据交互,如通过API与server交互,或者通过本地数据(sqlite)API与数据库交互。
2、域层(Domain layer):该层转换或操作 API 提供的数据。
3、表示层(Presentation layer):该层呈现应用程序内容并触发修改应用程序状态的事件。

根据您选择的架构,这些组件可以有不同的名称。 在某些架构中,可能再分多层来处理这些职责的特定方面,但这些都是实现细节。 本质将保持独立于架构。

架构选择(Architectural Choices)

我们将提到在以最佳的方式构建应用程序时,哪些应该和不应该做的是事情,这样就有了构建可扩展和可维护应用的基础。注意,这是一种武断的架构方法,您可以随意实施对您和您的团队有意义的架构。

定义层(Define your layers)

正如我之前所说,您可以创建不同的层来处理应用程序中的三个主要职责。 定义层是一个关键步骤,因为您可能希望在不影响可维护性的情况下使它们尽可能简单。

非常好的架构(Very Good Architecture)

在 VGV,我们遵循一个有四层的架构。
1、数据层:这一层负责与 API 交互。
2、领域层:负责转换来自数据层的数据。

最后,我们想要管理该数据的状态并将其呈现在我们的用户界面上,这就是我们将表示层一分为二的原因:

3、业务逻辑层:该层管理状态(通常使用flutter_bloc)。
4、表示层:基于状态呈现 UI 组件。
Flutter中非常优秀的分层架构_第1张图片

领域层将原始数据转换为业务逻辑层使用的领域特定模型。 业务逻辑层保持存储库提供的域模型的不可变状态。 此外,业务逻辑层对来自 UI 的输入做出反应,并在需要根据状态进行更改时与存储库进行通信。

根据 API 的实现,我们可以在不创建实际 API 包的情况下操作数据,但我们将在下一节中更多地讨论 API 和包。

避免混合责任(Avoid mixed responsibilities)

这是项目混乱的最常见原因。
没有适分开的关注点会引发难以跟踪的错误。 例如,理想情况下位于数据层组件中的某些功能位于表示层组件中,导致乍一看似乎是表示问题,但实际上是数据问题。

import 'package:firebase_auth/firebase_auth.dart';

class StartPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.userChanges(),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return const ProfilePage();
        }

        return const SignInPage();
      },
    );
  }
}

TL;DR:不要在小部件中留下数据或域交互。

始终如一(Be consistent)

遵循您的团队为项目定义的模式。 不要在分层上走捷径。 每个层都应该有一个关系链,以防止不直接相关的层之间的交互。 例如,表示层不应以任何方式直接调用数据层上的 API 或与之交互。

如果我们确定我们的数据层将依赖于我们的领域层,我们必须在我们开发的所有功能中尊重这一点。

命名可能是了解项目事物一种简单有效的方法。 记住命名只是一个约定。 最后,一些开发人员调用数据层基础设施,没关系,只是一个名字。 但是请尝试使该命名与您的架构保持一致。

如果我们不一致,代码库就会变得混乱并且更难以浏览。 如果你不遵守你在代码库中建立的规则,你也可能会冒着让队友感到困惑的风险。

├── lib
|   ├── posts
│   │   ├── bloc
│   │   │   └── post_bloc.dart
|   |   |   └── post_event.dart
|   |   |   └── post_state.dart
|   |   └── models
|   |   |   └── models.dart
|   |   |   └── post.dart
│   │   └── view
│   │   |   ├── posts_page.dart
│   │   |   └── posts_list.dart
|   |   |   └── view.dart
|   |   └── widgets
|   |   |   └── bottom_loader.dart
|   |   |   └── post_list_item.dart
|   |   |   └── widgets.dart
│   │   ├── posts.dart
│   ├── app.dart
│   ├── simple_bloc_observer.dart
│   └── main.dart
├── pubspec.lock
├── pubspec.yaml

构建时考虑到可重用性(Build with reusability in mind)

该建议专门针对数据层。 由于它是一个原始实现,因此可以在许多领域中用于不同目的。 数据层中的组件通常也是开源的优秀候选者,因为它们不依赖于特定的产品/应用程序,并且可以使更大的社区受益。

这就是我们将数据层和域层隔离在包中的原因。 这允许我们将它们导入其他应用程序或在需要时将它们发布到 pub.dev。

有时我们会构建与特定用例相关的这些层,例如,添加本地存储解决方案。 我们可以创建一个通用 API 来存储编码的 JSON 字符串,而不是期望一个特定的模型,因此我们可以将该 API 用作其他域中的本地存储 API。

您可以在 bloc 库的 flutter_todos 示例中看到如何使用抽象声明和具体实现来实现本地存储包。
Flutter中非常优秀的分层架构_第2张图片

使用适量的抽象(Use the right amount of abstraction)

当我们开发一个使用外部包作为数据层 API 的新功能时,我们有两种选择:
1、创建一个将该 API 作为直接依赖项的存储库。
2、为包创建 API 包装器。

如果您将包或插件用作依赖项,我建议您采用第一种方法:
1、有一个干净简单的 API。
2、不需要太多的依赖来测试它。
3、不需要对其用于您拥有的功能的类进行任何配置。
4、解决特定用例,您愿意将其作为 API 的唯一实现。

可以直接与存储库一起使用的简单 API 的一个很好的例子是 package:stream_chat_flutter。 在我们的 chat_location 应用程序中可以看到具有这种依赖关系的完整架构示例,该应用程序也有完整的教程。

否则,最好创建一个包含该依赖项的类的 API 包,以避免在存储库中包含所有实现细节。 您可以在我们的 spacex_demo 应用程序中看到自定义 API 包的示例。

不要过度设计(Don’t over-engineer)

这与之前的选择有关,我们不想抽象得比我们应该做的少,但是过度抽象或过度设计可能会耗费时间并增加复杂性

这方面的一个例子可以是模型。 我们通常为模型创建 JSON 序列化和反序列化功能,但我们知道我们可以更改为具有不同格式的 API。

老实说,我们多久使用 JSON 以外的任何东西来解析数据? 在我们的情况下,很少。 在这种情况下,如果我们从不更改当前格式,那么抽象模型并在以后进行实现,它听起来像是浪费时间。

使用或不使用包(To use or not to use packages)

好的,说到过度工程,这是一个固执己见的看法:

如果你的项目很小,用包来维护它可能有点矫枉过正。 我个人总是使用包,因为我痴迷于组件隔离并且迁移之类的事情变得更容易。

这样,您可以让一个队友同时将会计存储库迁移到 Very Good Analysis 2.4.0,另一个将零售 API 迁移到 Dio 4.0.0,这样您就可以解决修复 lint 和空安全重构问题,而不会相互影响 。
Flutter中非常优秀的分层架构_第3张图片

选择合适的工具(Choose the right tools)

重要的是要有严格的标准来决定你将使用哪个包或工具来解决你的应用程序中的问题。

我建议考虑 GitHub 仓库上的活动、pub.dev 指标、测试覆盖率、维护它的人员以及其他对您和您的团队很重要的因素。

您可以在我们的 Top Flutter 和 Dart 包博客中查看我们最喜欢的工具

看看专家们在做什么

帮助您将项目提升到新水平的一件事是查看社区中杰出开发人员的存储库。 许多 Flutter 社区成员在 GitHub 上有很多公共存储库,您可以学习和批评以从中学习和改进。

我们 VGV 有一些有趣的案例,展示了我们如何构建应用程序的最佳实践,我们愿意回答您关于它们的问题! 请务必查看 bloc 库 repo 中的 I/O Photo Booth(一个在 web 上使用 Flutter 的出色生产应用程序)、chat_location(引导数据提供者和存储库的一个很好的示例)和 flutter_todos(注意架构 这个)。

重构(Refactor)

因为我们是人,所以我们会犯错误。 识别这些错误并从中学习与分层代码一样重要。

你的第一种方法可能不是最好的,你需要分析你所遵循的每一个想法或实践,看看是否有必要

毕竟,请记住,您不需要完美无缺才能保持一致并使您的项目成功。 我坚信我们应该在构建应用程序时尽最大努力,但总有一个好的水平足以帮助你成功。

如果您需要重构,请不要对自己太苛刻!

参考文章:
1、原文

2、SOLID

你可能感兴趣的:(flutter,flutter)