Flutter Clean 架构 - 与领域实体和数据模型的冲突

Viv*_*vek 3 architecture flutter clean-architecture

我是flutter的初学者,学习flutter已经有6个月左右的时间了。我目前正在开发费用管理应用程序。我发现干净的架构模式非常有趣,尽管它需要比其他方式更多(!)的编码。我可以真实地说出这一点,因为我几乎制作了一个功能性应用程序,现在尝试从头开始使用干净的架构。我并不完全遵循https://pub.dev/packages/flutter_clean_architecture中的指南,而是尝试遵循干净的概念 - 域 -> 数据 -> UI

这是我到目前为止所做的:

领域

  1. 在域内创建实体。让我尝试将问题与其中一个实体(用户事务)隔离开来
abstract class UserTransactionEntity {
  final int? transactionID;
  final String merchant;
  final amount;
  final String category;
  final String paymentType;
  final String paymentAccount;
  final String transactionDate;
  final String? transactionNotes;
  final String transactionType;

  ///User Transaction Entity Class

  UserTransactionEntity(
      {required this.transactionType,
      required this.merchant,
      this.amount,
      required this.category,
      required this.paymentType,
      required this.paymentAccount,
      required this.transactionDate,
      this.transactionNotes,
      this.transactionID});
}

Run Code Online (Sandbox Code Playgroud)
  1. 创建了域存储库(界面?)
///UserTransaction Interface
abstract class UserTransactionRepository {
  Future<List<UserTransactionEntity>> getTransactionList();
  postTransaction(UserTransactionEntity transaction);
}

Run Code Online (Sandbox Code Playgroud)
  1. 在域中创建单独的用例

基本用例

abstract class UseCase<Type, Params> {
  Future<Type> call(Params params);
}

class NoParams extends Equatable {
  @override
  List<Object?> get props => [];
}

Run Code Online (Sandbox Code Playgroud)

一次性获取所有内容的用例

class GetAllTransactions
    extends UseCase<List<UserTransactionEntity>, NoParams> {
  final UserTransactionRepository repository;

  GetAllTransactions(this.repository);

  @override
  Future<List<UserTransactionEntity>> call(NoParams params) async {
    print('calling GetAll Transactions UseCase');
    return await repository.getTransactionList();
  }
}
Run Code Online (Sandbox Code Playgroud)

发布交易的第二个用例


class PostTransaction extends UseCase<dynamic, UserTransactionEntity> {
  final UserTransactionRepository _userTransactionRepository;
  PostTransaction(this._userTransactionRepository);

  @override
  call(UserTransactionEntity transaction) async {
    return await _userTransactionRepository.postTransaction(transaction);
  }
}

Run Code Online (Sandbox Code Playgroud)

数据层

  1. 创建扩展域实体的数据模型。我正在使用 SQFLite,因此在创建模型时考虑到了这一点。我保持实体干净,没有任何方法,因为它会根据外层的不同而变化。
class UserTransactionModel extends UserTransactionEntity {
  UserTransactionModel(
      {int? transactionID,
      required String transactionType,
      required String merchant,
      required num amount,
      required String category,
      required String paymentType,
      required String paymentAccount,
      required String transactionDate,
      String? transactionNotes})
      : super(
            merchant: merchant,
            transactionType: transactionType,
            amount: amount,
            category: category,
            paymentType: paymentType,
            paymentAccount: paymentAccount,
            transactionDate: transactionDate,
            transactionNotes: transactionNotes,
            transactionID: transactionID);

  factory UserTransactionModel.fromMap(Map<String, dynamic> map) {
    return UserTransactionModel(
      merchant: map[kMerchant],
      transactionType: map[kTransactionType],
      amount: map[kTransactionAmount],
      category: map[kCategoryName],
      paymentType: map[kPaymentType],
      paymentAccount: map[kPaymentAccount],
      transactionDate: map[kTransactionDate],
      transactionNotes: map[kTransactionNotes],
      transactionID: map[kTransactionID],
    );
  }

  Map<String, dynamic> toMap() {
    _validation();
    return {
      kMerchant: merchant,
      kTransactionAmount: amount,
      kCategoryName: category,
      kPaymentType: paymentType,
      kPaymentAccount: paymentAccount,
      kTransactionDate: transactionDate,
      kTransactionNotes: transactionNotes,
      kTransactionType: transactionType
    };
  }

  @override
  String toString() {
    return "User Transaction {$transactionType Amount: $amount Merchant:$merchant TransactionDate: $transactionDate}";
  }

  void _validation() {
    if (merchant == '') {
      throw NullMerchantNameException(message: 'Merchant should have a name!');
    }
  }
}
Run Code Online (Sandbox Code Playgroud)
  1. 为 SQFlite 创建数据库提供程序以及发布和获取事务的方法
  getTransactionList() async {
    final _db = await _dbProvider.database;
    final result = await _db!.query(kTransactionTable);
    return result;
  } 

Run Code Online (Sandbox Code Playgroud)

事务后方法使用从域实体创建的数据模型。

  Future<int> postTransaction(UserTransactionModel userTransaction) async {
    var _resultRow;
    final _db = await _dbProvider.database;

    try {
      _resultRow = _db!.insert(
        kTransactionTable,
        userTransaction.toMap(),
      );
      return _resultRow;
    } catch (exception) {
      //TODO
      throw UnimplementedError();
    }
  }
Run Code Online (Sandbox Code Playgroud)
  1. 创建本地数据源。
abstract class LocalDataSource {
  postTransaction(UserTransactionModel transaction);
  updateTransaction();
  deleteTransaction(int transactionID);
  getIncomeTransactionList();
  getExpenseTransactionList();
  getTransactionByDate(String date);
  Future<List<TransactionCategoryEntity>> getCategoryList();
  Future<List<UserTransactionEntity>> getTransactionList();
}
Run Code Online (Sandbox Code Playgroud)

这就是我的问题开始的地方。 当在存储库中实现时,以下方法在第四步中运行良好。请注意,我正在从数据库中获取实体。

    Future<List<UserTransactionEntity>> getTransactionList();
Run Code Online (Sandbox Code Playgroud)

当尝试在数据存储库中实现以下方法时,会引发错误。

    postTransaction(UserTransactionModel transaction);
Run Code Online (Sandbox Code Playgroud)

如果我尝试发布此模型,第四步中的存储库会抱怨它与域存储库冲突

'UserTransactionRepositoryImpl.postTransaction' ('dynamic Function(UserTransactionModel)') 不是 'UserTransactionRepository.postTransaction' ('dynamic Function(UserTransactionEntity)') 的有效覆盖。

如果我尝试在本地数据源中将其更新为域实体,那么它最终将与第二步中发布数据模型的方法发生冲突。如果我将所有内容更改为实体,它工作正常,但数据模型变得无用,并且我无法创建方法来重新映射从数据库获取的数据,例如 toMAP()、FromMap() 等。

我不确定我在这里缺少什么。我在软件编程方面完全是新手,但有一些 VBA 经验。

让我们继续下一步。

  1. 创建了一个数据存储库,实现了 doamin 层的存储库。
class UserTransactionRepositoryImpl implements UserTransactionRepository {
  final LocalDataSource _dataSource;
  UserTransactionRepositoryImpl(this._dataSource);

  @override
  Future<List<UserTransactionEntity>> getTransactionList() async {
    try {
      final List<UserTransactionEntity> transactionList =
          await _dataSource.getTransactionList();
      return transactionList;
    } on Exception {
      throw Exception;
    }
  }

  @override
  postTransaction(UserTransactionEntity transaction) async {
    await _dataSource.postTransaction(transaction);
  }
}

Run Code Online (Sandbox Code Playgroud)

表示层 最后,我还创建了表示层,并在 Provider 的帮助下将 UI 与数据层隔离。与之前的实验不同,我将 UI 部分留到了最后阶段。

问题:

因此,为了总结这么长的文章,我的问题是:

  1. 当我们创建扩展实体的数据模型时,在 UI 中应该称为什么——数据模型或领域实体?我可以让它同时使用两者,但是数据模型变得毫无用处,特别是当我们使用实体发布数据并检索相同的东西时。此外,这还要求实体具有正确的方法来映射 SQLite 数据。如果我更改为 Firebase,则需要更改格式化数据的域逻辑,这打破了核心原则 - 关闭编辑,最重要的是数据层影响域层。

  2. 数据模型是否仅在从外部获取数据并且需要额外的工作使其看起来像实体数据时才使用?或者换句话说,如果我只是发布交易并在应用程序内读入,实体就足够了吗?

  3. 如果两者都需要使用的话,应该怎么做呢?

  4. 最后,帮助我了解出了什么问题。

Anh*_*t21 5

模型应该用作数据映射。它的唯一任务是将数据从数据源(远程或本地)映射到存储库。并且模型应该仅在“数据层”中使用。在领域层和表示层中,您应该使用实体。以后如果需要修改模型中的字段,只需修改模型文件或扩展一个新的模型文件即可,领域层和表示层不会发生任何变化。