[Flutter] : TDD - 05. Contracts of Data Sources


이제 Repository를 구현해보자.

Repository는 Data Layer의 두뇌와 같다.

외부 혹은 로컬 데이터를 가져와 처리하며, 어느 Data Source에서 가져올지 결정하고, 캐시 정책을 결정하는 곳이다.

우리가 이미 Domain Layer에서 Repository를 Abstract Class로 Interface를 만들어놨다.

그것을 가져와 Data Layer에서 Implemeting을 해주자.

data/repositories/number_trivia_repository_impl.dart

import 'package:number_trivia/features/number_trivia/domain/entities/number_tirivia.dart';
import 'package:number_trivia/core/error/failures.dart';
import 'package:dartz/dartz.dart';
import 'package:number_trivia/features/number_trivia/domain/repositories/number_trivia_repository.dart';

class NumberTriviaRepositoryImpl extends NumberTriviaRepository {
  @override
  Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number) {
    // TODO: implement getConcreteNumberTrivia
    throw UnimplementedError();
  }

  @override
  Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia() {
    // TODO: implement getRandomNumberTrivia
    throw UnimplementedError();
  }

}

내용을 채우기 앞서서

Repository는 DataSource로 부터 데이터를 가져오는 의존성이 있다.

여기서 DataSource의 Contract를 작성해보자.

1. Network


우리는 오프라인에서도 데이터를 무언가 보여주기 위해 캐시를 해야한다.

그럴려면 현재 network 상태를 알아야하므로 network관련 코드를 작성하자.

core/platform/network_info.dart

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

2. Remote DataSource


우리는 이미 NumberTriviaModel에서 간단하게 숫자만 받아서 처리하도록 구현하였다.

네트워크 등으로 생기는 오류는 Exception으로 처리하자.

data/datasources/number_trivia_remote_data_source.dart

import 'package:number_trivia/features/number_trivia/data/models/number_trivia_model.dart';

abstract class NumberTriviaRemoteDataSource {
  // Calls the http://numbersapi.com/{number} endpoint.
  Future<NumberTriviaModel> getConcreteNumberTrivia(int number);

  // Calls the http://numbersapi.com/random endpoint.
  Future<NumberTriviaModel> getRandomNumberTrivia();
}

core/error/exception.dart

class ServerException implements Exception {}

class CacheException implements Exception {}

Repository에서 에러를 잡아서 해결

해주기 때문에 위의 Exception과 맞춰서 Failure코드를 작성해주자.

core/error/failures.dart

import 'package:equatable/equatable.dart';

abstract class Failure extends Equatable {
  // 하위 클래스에 properties가 있으면 생성자를 통해
  // Equatable의 비교가 동작한다.
  List properties;
  Failure([properties = const <dynamic>[]]);

  @override
  List<Object> get props => properties;
}

class ServerFailure extends Failure {}

class CacheFailure extends Failure {}

3. Local Data Source


이제 Cache를 위해 로컬 데이터 구현을 하겠다.

우리의 캐시 정책은 가장 마지막의 데이터를 유지해 보여주는 것이다.

data/datasources/number_trivia_local_data_source.dart

import 'package:number_trivia/features/number_trivia/data/models/number_trivia_model.dart';

abstract class NumberTriviaLocalDataSource {
  //cache 데이터를 꺼내간다.
  Future<NumberTriviaModel> getLastNumberTrivia();

  //cache 데이터를 업데이트 한다.
  Future<void> cacheNumberTrivia(NumberTriviaModel tiriviaToCahce);
}

4. Test


이제 구현하기 전에 테스트 코드를 작성해보자.

test/features/number_trivia/data/repositories/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,
    );
  });
}

자 당연히 에러가 발생한다.

구현되지 않았기 때문이다.

Parameter를 받도록 구현해보자.

data/repositories/number_trivia_repository_impl.dart

class NumberTriviaRepositoryImpl extends 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;
  }
}

자 이제 Parameter받는건 구현하였다.

나머지 구현은 다음장에서 알아보자.