Flutter Arquitectura Limpia [5] – Contratos de fuentes de datos

Flutter Arquitectura Limpia [5] – Contratos de fuentes de datos

flutterclean5

 

001 online learning

 

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

001 draw
Esta publicación es solo una parte de una serie de tutoriales. ¡Vea todas las otras partes y aprenda a diseñar sus aplicaciones Flutter!

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.

domain layer diagram

 

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.

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.

002 business and finance

¿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 file

network_info.dart

abstract class NetworkInfo {
  Future<bool> get isConnected;
}

Fuente de datos remota

003 sync

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

004 ui

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 file

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

005 files and folders

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.

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);
Esta práctica de codificación le permite intercambiar el paquete http de bajo nivel por algo como chopper sin problemas importantes.

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.

repository test file

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

¿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.

Previous

Análisis y Disputa de Datos – Conceptos y ejemplos básicos en Python

Análisis de Datos Exploratorios con Python

Next

Deja un comentario