【问题标题】:Flutter Clean Architecture - Conflict with domain entity and data modelFlutter Clean Architecture - 与域实体和数据模型冲突
【发布时间】:2021-06-08 17:30:58
【问题描述】:

我是 Flutter 的初学者,已经学习了 Flutter 6 个月左右。我目前正在开发费用管理器应用程序。我发现干净的架构模式非常有趣,尽管它需要比其他方式更多的(!)编码。我可以这么说,因为我几乎制作了一个功能性应用程序,现在尝试从头开始在干净的架构中。我没有完全遵循https://pub.dev/packages/flutter_clean_architecture 中的指导方针,而是尝试遵循干净的概念 - Domain -> Data -> 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});
}

  1. 创建了域存储库(接口?)
///UserTransaction Interface
abstract class UserTransactionRepository {
  Future<List<UserTransactionEntity>> getTransactionList();
  postTransaction(UserTransactionEntity transaction);
}

  1. 在域中创建了单独的用例

基本用例

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

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

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

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();
  }
}

发布交易的第二个用例


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

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

数据层

  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!');
    }
  }
}
  1. 为 SQFlite 创建了数据库提供程序以及发布和获取事务的方法
  getTransactionList() async {
    final _db = await _dbProvider.database;
    final result = await _db!.query(kTransactionTable);
    return result;
  } 

Post Transaction 方法使用从域实体创建的数据模型。

  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();
    }
  }
  1. 已创建本地数据源。
abstract class LocalDataSource {
  postTransaction(UserTransactionModel transaction);
  updateTransaction();
  deleteTransaction(int transactionID);
  getIncomeTransactionList();
  getExpenseTransactionList();
  getTransactionByDate(String date);
  Future<List<TransactionCategoryEntity>> getCategoryList();
  Future<List<UserTransactionEntity>> getTransactionList();
}

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

    Future<List<UserTransactionEntity>> getTransactionList();

以下方法尝试在数据存储库中实现时抛出错误。

    postTransaction(UserTransactionModel transaction);

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

'UserTransactionRepositoryImpl.postTransaction' ('动态 Function(UserTransactionModel)') 不是有效的覆盖 'UserTransactionRepository.postTransaction' ('动态 函数(UserTransactionEntity)')。

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

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

让我们继续下一步。

  1. 创建了一个数据存储库,从域层实现存储库。
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);
  }
}

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

问题:

所以总结这么长的文章,我的问题是;

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

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

  3. 如果两者都用,应该怎么做?

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

【问题讨论】:

    标签: flutter architecture clean-architecture


    【解决方案1】:

    只是为了与您分享我对为什么我们需要这 2 层数据表示的理解,并将模型转换为实体,反之亦然。

    假设您有多个后端,每个后端都有自己的数据结构,但在您的应用程序中,它由单个数据类表示。例如,您从不同来源获取电子货币数据——除此之外,您还拥有自己的数据来源。所以你有 2+ 个 DTO,这些映射到你的模型,稍后在 UI 演示中使用。

    【讨论】:

      猜你喜欢
      • 2021-06-19
      • 2020-06-18
      • 1970-01-01
      • 1970-01-01
      • 2021-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多