Flutter Arquitectura Limpia [5] – Contratos de fuentes de datos
![Flutter Arquitectura Limpia [5] – Contratos de fuentes de datos 2 flutterclean5](https://rubenjromo.com/wp-content/uploads/2019/11/flutterclean5.png)
[wp_ad_camp_1]
Flutter Arquitectura Limpia [5] – Contratos de fuentes de datos
El repositorio es el cerebro de la capa de datos de una aplicación. Maneja datos de fuentes remotas y locales, decide qué fuente de datos prefiere y también, aquí es donde se decide la política de almacenamiento en caché de datos.
En la parte anterior, hemos repasado la estructura básica de la capa de datos y hoy, es hora de comenzar a implementar la capa de datos directamente desde su núcleo, desde NumberTriviaRepository, mientras creamos contratos para sus dependencias.
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
Implementando el contrato
Interfaces, clases abstractas, ... lo que sea. Cada lenguaje de programación tiene su propia sintaxis. Lo importante es que ya tenemos un contrato que la implementación del repositorio debe cumplir. De esta manera, los casos de uso que se comunican con el repositorio no tienen que saber nada acerca de cómo funciona.
El contrato permite la independencia de la capa de dominio.
Por lo tanto, creemos un nuevo archivo en data / repositories para la función number_trivia, que contendrá una clase concreta NumberTriviaRepositoryImpl.
number_trivia_repository_impl.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/failure.dart';
import '../../domain/entities/number_trivia.dart';
import '../../domain/repositories/number_trivia_repository.dart';
class NumberTriviaRepositoryImpl implements NumberTriviaRepository{
@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number) {
// TODO: implement getConcreteNumberTrivia
return null;
}
@override
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia() {
// TODO: implement getRandomNumberTrivia
return null;
}
}
A riesgo de sonar como un disco rayado, lo diré nuevamente: el Repositorio necesita orígenes de datos de nivel inferior para obtener los datos reales.
[wp_ad_camp_1]
Dependencias del repositorio
En esta parte, solo crearemos contratos para todas las dependencias del repositorio. Esto nos permitirá usar mock en ellos fácilmente sin tener que molestarnos en implementarlos por el momento, eso vendrá en las siguientes partes.
[wp_ad_camp_3]
¿Son suficientes las fuentes de datos? Después de todo, almacenaremos en caché los últimos NumberTrivia localmente para asegurarnos de que el usuario vea algo incluso cuando esté desconectado. Esto significa que también necesitaremos tener una manera de conocer el estado actual de la conexión de red. Dado que queremos mantener nuestro código tan independiente del mundo exterior como sea posible, no solo colocaremos ninguna biblioteca de terceros para la conectividad directamente en el Repositorio. En su lugar, crearemos una clase NetworkInfo.
Network Info
Esta clase vivirá dentro del archivo network_info.dart en la carpeta core / platform. Esto se debe a que se trata de la plataforma subyacente: en Android, el proceso de obtención de información de red puede ser diferente que en iOS. Por supuesto, utilizaremos un paquete de terceros para unificar el proceso, pero por si acaso ...
network_info.dart
abstract class NetworkInfo {
Future<bool> get isConnected;
}
Fuente de datos remota
La interfaz pública de NumberTriviaRemoteDataSource será casi idéntica a la del Repositorio: tendrá métodos getConcreteNumberTrivia y getRandomNumberTrivia. Sin embargo, como discutimos en la parte anterior, el tipo de retorno será diferente.
Estamos en el límite entre el mundo exterior y nuestra aplicación, por lo que queremos mantener esto simple. No habrá Either <Failure, NumberTrivia>, sino que solo devolveremos un simple NumberTriviaModel (convertido de JSON). Los errores se manejarán lanzando Excepciones. Cuidar de estos datos "tontos" y convertirlos al tipo Either será responsabilidad del Repositorio.
number_trivia_remote_data_source.dart
import '../models/number_trivia_model.dart';
abstract class NumberTriviaRemoteDataSource {
/// Calls the http://numbersapi.com/{number} endpoint.
///
/// Throws a [ServerException] for all error codes.
Future<NumberTriviaModel> getConcreteNumberTrivia(int number);
/// Calls the http://numbersapi.com/random endpoint.
///
/// Throws a [ServerException] for all error codes.
Future<NumberTriviaModel> getRandomNumberTrivia();
}
Añadiendo excepciones y fallos
Como puede ver en la documentación, ambos métodos arrojarán una ServerException cuando la respuesta no tenga un código 200 OK, sino, por ejemplo, un código 404 NOT FOUND. La cuestión es que actualmente no tenemos ninguna ServerException, así que creémosla para usarla en las pruebas del repositorio.
La excepción ServerException puede compartirse potencialmente en varias funciones, por lo que la colocaremos dentro del archivo core / error / exception.dart. Mientras estamos en eso, también vamos a crear una CacheException que será lanzada por la Fuente de datos local.
exception.dart
class ServerException implements Exception {}
class CacheException implements Exception {}
Lo mantenemos simple al no tener ningún campo dentro de las Excepciones personalizadas. Si desea transmitir más información sobre el error, siéntase libre de agregar un campo de mensaje a la clase en sus proyectos.
Como estamos lidiando con Excepciones, también eliminemos las Fallas. Recuerde, el Repositorio capturará las Excepciones y las devolverá utilizando el tipo Cualquiera de los Fallos. Por esta razón, los tipos de falla generalmente se asignan exactamente a los tipos de excepción.
failure.dart
import 'package:equatable/equatable.dart';
abstract class Failure extends Equatable {
Failure([List properties = const <dynamic>[]]) : super(properties);
}
// General failures
class ServerFailure extends Failure {}
class CacheFailure extends Failure {}
Fuente de datos local
Hasta ahora, los métodos que creamos siempre fueron para obtener datos, ya sea la Entidad o el Modelo. También se dividieron en obtener trivias de números concretos o aleatorios. Vamos a romper este patrón con NumberTriviaLocalDataSource.
Aquí, también tendremos que poner los datos en el caché y tampoco nos importará si estamos tratando con trivias de números concretos o aleatorios. Esto se debe a que la política de almacenamiento en caché (implementada dentro del Repositorio) será simple: siempre guarde en caché y recupere las últimas curiosidades obtenidas de la Fuente de datos remota.
number_trivia_local_data_source.dart
import '../models/number_trivia_model.dart';
abstract class NumberTriviaLocalDataSource {
/// Gets the cached [NumberTriviaModel] which was gotten the last time
/// the user had an internet connection.
///
/// Throws [NoLocalDataException] if no cached data is present.
Future<NumberTriviaModel> getLastNumberTrivia();
Future<void> cacheNumberTrivia(NumberTriviaModel triviaToCache);
}
Tenga en cuenta que en ninguno de los orígenes de datos habíamos escrito código que parece depender de alguna capa externa de la aplicación.
[wp_ad_camp_4]
La URL para la API tendrá que ser un string, sin embargo, el tipo del parámetro de número para la Fuente de datos remota es un entero ...
Future<NumberTriviaModel> getConcreteNumberTrivia(int number);
Configurando el repositorio
Si bien no vamos a escribir ninguna lógica real de la clase NumberTriviaRepositoryImpl en esta parte (¡eso viene en la próxima!), Al menos lo configuraremos para que esté preparado para funcionar con todas sus dependencias que creamos anteriormente. Como estamos haciendo TDD, también prepararemos el archivo de prueba para la siguiente parte.
Nuevamente, en el espíritu de TDD, escribiremos primero la prueba, aunque realmente no probaremos nada todavía. Como de costumbre, el archivo de prueba entra en la ubicación de "imagen reflejada" del archivo de producción.
Sabemos que el Repositorio debe incluir los orígenes de datos remotos y locales y también un objeto NetworkInfo. Debido a que estamos preparando el archivo de prueba para la siguiente parte, creemos mocks para estas dependencias de inmediato.
number_trivia_repository_impl_test.dart
class MockRemoteDataSource extends Mock
implements NumberTriviaRemoteDataSource {}
class MockLocalDataSource extends Mock implements NumberTriviaLocalDataSource {}
class MockNetworkInfo extends Mock implements NetworkInfo {}
void main() {
NumberTriviaRepositoryImpl repository;
MockRemoteDataSource mockRemoteDataSource;
MockLocalDataSource mockLocalDataSource;
MockNetworkInfo mockNetworkInfo;
setUp(() {
mockRemoteDataSource = MockRemoteDataSource();
mockLocalDataSource = MockLocalDataSource();
mockNetworkInfo = MockNetworkInfo();
repository = NumberTriviaRepositoryImpl(
remoteDataSource: mockRemoteDataSource,
localDataSource: mockLocalDataSource,
networkInfo: mockNetworkInfo,
);
});
}
[wp_ad_camp_5]
¿Que es eso? Por supuesto, nuevamente tenemos un error de compilación. Continuemos y agreguemos todos los campos necesarios y los parámetros del constructor en NumberTriviaRepositoryImpl.
number_trivia_repository_impl.dart
import 'package:dartz/dartz.dart';
import 'package:meta/meta.dart';
import '../../../../core/error/failure.dart';
import '../../../../core/platform/network_info.dart';
import '../../domain/entities/number_trivia.dart';
import '../../domain/repositories/number_trivia_repository.dart';
import '../datasources/number_trivia_local_data_source.dart';
import '../datasources/number_trivia_remote_data_source.dart';
class NumberTriviaRepositoryImpl implements NumberTriviaRepository {
final NumberTriviaRemoteDataSource remoteDataSource;
final NumberTriviaLocalDataSource localDataSource;
final NetworkInfo networkInfo;
NumberTriviaRepositoryImpl({
@required this.remoteDataSource,
@required this.localDataSource,
@required this.networkInfo,
});
@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number) {
// TODO: implement getConcreteNumberTrivia
return null;
}
@override
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia() {
// TODO: implement getRandomNumberTrivia
return null;
}
}
¿Qué sigue?
Ya hay mucho que digerir de esta parte. Hemos creado 3 contratos para las dependencias del repositorio. Debido a que siempre implementamos cosas "desde adentro hacia afuera", la siguiente parte tendrá que ver con hacer que la implementación del Repositorio haga su trabajo. Haremos esto en estilo TDD, por supuesto.
Aquí les comparto también el video.
Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.