Flutter Clean Architecture (Part 3) – Tái cấu trúc Domain Layer

Trần Đình Quý

Updated on:

Flutter Clean Architecture

Hiện tại thì app của chúng ta đang được xây dựng khác mượt mà =)).
Trong những phần trước, chúng ta đã tạo Entity, Repository contract và Use case đầu tiên – GetConcreteNumberTrivia. Hôm nay chúng ta sẽ tiếp tục thêm 1 Use case khác mà nó sẽ mở ra 1 cơ hội cho chúng ta refactor code.

Class có thể gọi được

Bạn có biết rằng ở trong Dart, method call có thể chạy khi gọi object.call() hoặc chỉ cần gọi object(). Đó là một method tuyệt vời để chúng ta có thể dùng trong Use case. Sau tất cả thì các Use case là động từ như là GetConcreteNumberTrivia cho nên là dùng cái method giả đó phù hợp một cách tuyệt zời.

Chúng ta cảo thể điều chỉnh GetConcreteNumberTrivia ngay lập tức.

Future<Either<Failure, NumberTrivia>> call(...

Thêm một Usecase mới

Bên cạnh việc lấy số trivia cho 1 số cụ thể thì app chúng ta sẽ có thể lấy từ số ngẫu nhiên. Điều này có nghĩa là chúng ta sẽ có 1 Use case mới – GetRandomNumberTrivia. Number API chúng ta cũng có 1 endpoint khác để cho số random nên chúng ta không cần phải lo cho nó nhiều, nó tự tạo. Còn nếu không thì việc tạo random number được thực hiện ở trong domain layer, và chính là GetRandomNumberTrivia. Tạo số ngẫu nhiên cũng là 1 business logic sau tất cả.

Use case Base Class

Ta thấy mọi Use case đều có 1 phương thức chung nên có đó là call. Không quan trọng là logic bên trong Use case là lấy number trivia hay đi nhậu thì interface luôn sẽ là giống nhau để tránh gây ra bất kì một khó khăn nào.

Một cách khác để ngăn chặn 1 class có phương thức call và các phương thức thực thi khác đó là cung cấp 1 explicit interface (trong Dart chỉ có abstract class) mà ta không thể quên implement được như là Use case base class.

Code phía dưới sẽ ở core/usecases, bởi vì class này có thể chia sẻ ở nhiều tính năng của app.

import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';

import '../error/failure.dart';

// Parameters have to be put into a container object so that they can be
// included in this abstract base class method definition.
abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}

// This will be used by the code calling the use case whenever the use case
// doesn't accept any parameters.
class NoParams extends Equatable {
  @override
  List<Object?> get props => <String>[];
}

Kế thừa Base Class

Như chúng ta thấy thì UseCase có 2 loại tham số. 1 loại là loại không lỗi (bất cứ cái gì không phải lỗi), và cái thứ 2 là Params.

Mỗi UseCase được extend sẽ định nghĩa tham số sẽ được truyền vào method call, và phân chia chia class trong cùng 1 file. Tất nhiên nó vẫn sẽ ở chỗ cũ – domain layer

get_concrete_number_trivia.dart

import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';

import '../../../../core/error/failure.dart';
import '../../../../core/usecase/usecase.dart';
import '../entities/number_trivia.dart';
import '../repositories/number_trivia_repository.dart';

class GetConcreteNumberTrivia extends UseCase<NumberTrivia, Params> {

  GetConcreteNumberTrivia(this.repository);
  final NumberTriviaRepository repository;

  @override
  Future<Either<Failure, NumberTrivia>> call(Params params) {
    return repository.getConcreteNumberTrivia(params.number);
  }
}

class Params extends Equatable {
  const Params({required this.number});

  final int number;

  @override
  List<Object?> get props => <int>[number];
}

Chúng ta biết rằng method call sẽ phải nhận Params object, thay vì truyền interger trực tiếp. Đó là lí do mà chúng ta tạo ra class Params dành riêng cho mỗi GetConcreteNumberTrivia như trên.

Tương tự như thế, chúng ta sẽ có được:

get_random_number_trivia.dart

import 'package:dartz/dartz.dart';

import '../../../../core/error/failure.dart';
import '../../../../core/usecase/usecase.dart';
import '../entities/number_trivia.dart';
import '../repositories/number_trivia_repository.dart';

class GetRandomNumberTrivia extends UseCase<NumberTrivia, NoParams> {
  GetRandomNumberTrivia(this.repository);

  final NumberTriviaRepository repository;

  @override
  Future<Either<Failure, NumberTrivia>> call(NoParams params) {
    return repository.getRandomNumberTrivia();
  }
}

Phần trước: https://dev.tora-tech.com/flutter-ca-entity-use-case/

Phần tiếp theo: https://dev.tora-tech.com/flutter-ca-tong-quan-data-layer-model/

Viết một bình luận