Flutter Clean Architecture -与领域实体和数据模型冲突

dffbzjpn  于 2022-11-25  发布在  Flutter
关注(0)|答案(3)|浏览(195)

我是flutter的初学者,已经学习flutter 6个月左右了。我目前正在开发expense manager应用程序。我发现clean architecture模式非常有趣,尽管它需要更多的东西(!)编码比需要的,否则。我可以说这是真实的,因为我几乎做了一个功能应用程序,现在试图从头开始在干净的架构。我没有完全遵循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 => [];
}

使用case一次获取所有内容

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

过帐事务处理方法正在使用从域实体创建数据模型

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步中的仓库会抱怨它与域仓库冲突
'使用者交易储存库实作.postTransaction'('动态函数(使用者交易模型)')不是'使用者交易储存库.postTransaction'('动态函数(使用者交易实体)')的有效覆写。
如果我尝试在本地数据源中将其更新为域实体,那么它最终会与第二步中发布数据模型的方法冲突。如果我将所有内容都更改为实体,它会正常工作,但数据模型会变得无用,我无法创建方法来重新Map从DB中提取的数据-例如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);
  }
}

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

因此,为了结束这么长的一篇文章,我的问题是:
1.当我们创建扩展实体的数据模型时,在UI中应该调用什么-数据模型还是域实体?我可以让它同时工作,但是数据模型变得毫无用处,特别是当我们使用实体发布数据并检索相同的东西时。而且,这需要实体有合适的方法来MapSQLite的数据。如果我改为Firebase,格式化数据的域逻辑需要改变。这打破了核心原则-对编辑关闭,最重要的是数据层影响域层。
1.数据模型是否仅在数据从外部获取并需要额外工作以使其看起来像实体数据时使用?或者换句话说,如果我只是发布一个事务并在应用程序内读取,实体是否足够?
1.如果两者都要用,应该怎么做?
1.最后,请帮助我了解哪里出了问题。

yvfmudvl

yvfmudvl1#

只是想和你分享一下我对为什么我们需要这两层数据表示的理解,以及将模型转换为实体,反之亦然。
假设您有多个后端,每个后端都有自己的数据结构,但在您的应用程序中,它由单个数据类表示。例如,您从不同的源获取电子货币数据-在此基础上,您有自己的数据源。因此,您有2个以上的DTO,这些DTOMap到您的模型,稍后在UI表示中使用。

70gysomp

70gysomp2#

模型应该作为一个数据Map,它的唯一任务是将数据从数据源(远程或本地)Map到仓库。模型应该只在"数据层"使用。在领域层和表示层,应该使用实体。将来,如果需要修改模型中的字段,可以只修改模型文件或扩展一个新的模型文件,而领域层和表示层不做任何改变。

nlejzf6q

nlejzf6q3#

希望我能弄清楚这一点。我把问题和答案加在一起是为了方便。
问题:
因此,为了结束这么长的一篇文章,我的问题是:
1.[问题]当我们创建扩展实体的数据模型时,在UI中应该调用什么-数据模型还是域实体?我可以让它同时工作,但数据模型变得无用,特别是当我们使用实体发布数据并检索相同的东西时。此外,这需要实体有适当的方法来MapSQLite的数据。如果我改为Firebase,格式化数据的域逻辑需要更改。这打破了核心原则-对编辑关闭,最重要的是数据层影响域层。
【答案】:应该是实体,数据模型的职责是将外部数据转换为app的实体模型,同时还承担了将实体反转换为外层格式的额外职责2.【问题】数据模型是不是只有当数据是从外部取来的,需要做额外的工作才能让它看起来像实体数据?或者说,如果我只是发布一个事务并在应用程序中读取,实体是否足够?
【答】:数据模型是为了解析外部数据,实现外部数据与实体之间的转换而创建的,所以处理这些数据的类都使用数据模型。
下面是最近写的一篇关于干净架构实现的文章。
Flutter clean architecture with Riverpod

相关问题