Flutter Arquitectura Limpia [3] - Refactorización de capa de dominio
[wp_ad_camp_1]
Flutter Arquitectura Limpia [3] - Refactorización de capa de dominio
Nuestra aplicación Number Trivia se está moviendo muy bien. En la parte anterior, creamos una entidad, un contrato de repositorio y el primer caso de uso: GetConcreteNumberTrivia utilizando un desarrollo basado en pruebas. Hoy, agregaremos otro caso de uso que descubrirá una buena oportunidad para refactorizar el código.
Debo aclarar que el contenido original es de Resocoder, lo que he hecho es una traducción al español del contenido. Al final de este artículo está el video en inglés para que vayan ilustrándose mejor y ya tengan una claridad de lo expuesto.
Curso TDD Arquitectura Limpia
Clases invocables
¿Sabías que en Dart, un método llamado call que se puede ejecutar llamando a object.call () pero también a object ()? ¡Ese es el método perfecto para usar en los casos de uso! Después de todo, sus nombres de clase ya son verbos como GetConcreteNumberTrivia, por lo que usarlos como "métodos falsos" encaja perfectamente.
En el espíritu de TDD, primero modificaremos la prueba (get_concrete_number_test.dart) para que ya no llame al método de ejecución:
final result = await usecase(number: tNumber);
Y como el código ni siquiera se compila, podemos modificar la clase GetConcreteNumberTrivia de inmediato:
[wp_ad_camp_2]
And since the code doesn't even compile, we can modify the GetConcreteNumberTrivia class immediately:
Future<Either<Failure, NumberTrivia>> call({ ...
El código de prueba ahora debería pasar.
Añadiendo otro caso de uso
Además de obtener trivia para un número concreto, nuestra aplicación también obtendrá trivia para un número aleatorio. Esto significa que necesitamos otro caso de uso: GetRandomNumberTrivia. La API de números que estamos utilizando tiene en realidad un punto final de API diferente para números concretos y aleatorios, por lo que no generaremos el número nosotros mismos. De lo contrario, el código de generación de números se ejecutaría dentro de la capa de dominio precisamente en el caso de uso GetRandomNumberTrivia. Generar números es una lógica de negocios, después de todo.
Clase base de caso de uso
Como codificadores limpios, seguramente nos gusta cuando nuestro código tiene una interfaz predecible. Los métodos públicos y las propiedades de las clases que hacen básicamente lo mismo deberían tener nombres estandarizados.
Cuando se trata de casos de uso, cada uno de ellos debe tener un método de llamada. No importa si la lógica dentro del caso de uso nos da un NumberTrivia o envía un transbordador espacial a la Luna, la interfaz debe ser la misma para evitar cualquier confusión.
Una forma de imponer una interfaz estable de una clase es confiar en la "palabra del programador" implícita. Lamentablemente, los programadores no son conocidos por recordar cosas. ¡Diablos, incluso veo algunos de mis antiguos tutoriales porque ya me he olvidado de cómo hacer ciertas cosas que ya sabía!
Otra forma de evitar que una clase tenga un método de llamada y otra un método de ejecución es proporcionar una interfaz explícita (en el caso de Dart, una clase abstracta), de la cual es inolvidable derivar. Como una clase base de caso de uso, por ejemplo.
El siguiente código irá al núcleo / casos de uso, ya que esta clase se puede compartir en múltiples funciones de una aplicación. Y, por supuesto, no tiene sentido probar una clase abstracta, por lo que podemos escribirla de inmediato.
usecase.dart
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 {}
Extendiendo la clase base
Como puede ver, agregamos dos parámetros de tipo a la clase UseCase. Uno es para el tipo de retorno "sin error", que en el caso de nuestra aplicación será la entidad NumberTrivia. El otro parámetro de tipo, Params, causará algunos cambios menores en el código en el caso de uso GetConcreteNumberTrivia ya presente.
[wp_ad_camp_3]
Cada clase de extensión UseCase definirá los parámetros que se pasan al método de llamada como una clase separada dentro del mismo archivo. Mientras estamos en eso, extendamos también UseCase. Otros cambios relacionados con el funcionamiento de la clase se producirán solo después de actualizar la prueba: ¡estamos haciendo TDD!
get_concrete_number_trivia.dart
class GetConcreteNumberTrivia extends UseCase<NumberTrivia, Params> {
...
}
class Params extends Equatable {
final int number;
Params({@required this.number}) : super([number]);
}
Ahora sabemos que la llamada debe incluir un objeto Params, en lugar de que el entero sea el parámetro directamente. Entonces, debido a que escribimos una prueba en la parte anterior, podemos usarla para confiar en nuestro código. Todo lo que tenemos que hacer es:
[wp_ad_camp_1]
- Actualice la prueba para usar los parámetros.
- No se compilará.
get_concrete_number_trivia_test.dart
...
test(
'should get trivia for the number from the repository',
() async {
// arrange
when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
.thenAnswer((_) async => Right(tNumberTrivia));
// act
final result = await usecase(Params(number: tNumber));
// assert
expect(result, Right(tNumberTrivia));
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
verifyNoMoreInteractions(mockNumberTriviaRepository);
},
);
- Actualiza el código de producción. Utilice los parámetros como parámetro para el método de llamada.
- Ejecute la prueba: debe pasar, la confianza del código se va por todo lo alto.
get_concrete_number_trivia.dart
class GetConcreteNumberTrivia extends UseCase<NumberTrivia, Params> {
...
@override
Future<Either<Failure, NumberTrivia>> call(Params params) async {
return await repository.getConcreteNumberTrivia(params.number);
}
}
...
GetRandomNumberTrivia
Agregar este nuevo caso de uso ahora es muy simple: hemos acordado una interfaz que cada UseCase debe tener. Además, debido a la naturaleza simple de la aplicación Number Trivia, este nuevo caso de uso solo obtendrá datos del repositorio.
Una vez más, comenzamos escribiendo la prueba: cree un nuevo archivo en la carpeta "prueba /.../ usecases". La mayor parte del código se copia de la prueba para el caso de uso anterior.
get_random_number_trivia_test.dart
import 'package:clean_architecture_tdd_prep/core/usecase/usecase.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
void main() {
GetRandomNumberTrivia usecase;
MockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetRandomNumberTrivia(mockNumberTriviaRepository);
});
final tNumberTrivia = NumberTrivia(number: 1, text: 'test');
test(
'should get trivia from the repository',
() async {
// arrange
when(mockNumberTriviaRepository.getRandomNumberTrivia())
.thenAnswer((_) async => Right(tNumberTrivia));
// act
// Since random number doesn't require any parameters, we pass in NoParams.
final result = await usecase(NoParams());
// assert
expect(result, Right(tNumberTrivia));
verify(mockNumberTriviaRepository.getRandomNumberTrivia());
verifyNoMoreInteractions(mockNumberTriviaRepository);
},
);
}
Of course, this test fails and the implementation of the GetRandomNumberTrivia is as follows:
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> {
final NumberTriviaRepository repository;
GetRandomNumberTrivia(this.repository);
@override
Future<Either<Failure, NumberTrivia>> call(NoParams params) async {
return await repository.getRandomNumberTrivia();
}
}
[wp_ad_camp_5]
La prueba ahora pasará y con eso, acabamos de implementar completamente la capa de dominio de la aplicación Number Trivia. En la siguiente parte, comenzaremos a trabajar en la capa de datos que contiene la implementación del Repositorio y las Fuentes de datos.
Aquí les comparto también el video.
Si quieres conocer otros artículos parecidos a Flutter Arquitectura Limpia [3] - Refactorización de capa de dominio puedes visitar la categoría 📱 Flutter.
Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.