Flutter TDD Clean Architecture Course その5 - Data SourceのContracts

- [こちらのチュートリアル](https://resocoder.com/2019/09/12/flutter-tdd-clean-architecture-course-5-contracts-of-data-sources/)の解説です
- チュートリアルを読むのが早いと思いますが、[概要をまとめた](https://qiita.com/maria_mari/items/8fdd92af13a36c663360)のでこちらもご参照ください。


# 前回までの振り返り
## 前回までの流れ
- 1: 概要
    - CleanArchitectureの説明
- 2: EntityとUseCaseの作成
    - Entityの作成
    - UseCaseの作成
        - インターフェイスの作成
        - Test作成
        - 実装の作成
- 3: Domain層のリファクタ
    - UseCaseのインターフェイスの作成
    - もう１つのUseCaseの作成
- 4: Data層とModel
    - Modelの作成

## 図による解説
- 次の同心円の内側から外側という順でコーディングしています。

![CleanArchitecture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779879/e0b8a952-31ea-64e1-5f56-6f7ca59662e5.jpeg)

- 以下の図でいうと、中央部のDomainからコーディングを初めて、次に下のData層のコーディングに移る最中です。
    - 前回までは図の緑色の部分に着手しています。
        - RepositoryのContractを作成しました
        - Modelを作成しました


![Clean-Architecture-Flutter-Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779879/04f3b87b-d149-6611-0379-dea95e6e34b0.png)



# 今回やること
- DataSourcesのContractの作成
- Repositoryの実装ファイルの作成（途中まで）

--- 

- RepositoryはData層の頭脳のようなもので、RemoteとLocalのDataSourceからのデータを処理する
- 今回はData層の核であるRepositoryの実装に着手する

# Contractの作成
- インターフェイスやabstractクラスがContractにあたる
    - Contractを用いることでDomain層の独立が保てる
- Repositoryは実際のデータを供給するためDataSourceを使用する

```number_trivia_repository_impl.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;
  }
}
```

# Repositoryの独立を保つ
- RepositoryがDataSourceに依存しなくて済むように、DataSourceのContractを作成する
    - DataSourceのContractのMockを利用してRepositoryをTDDできる

## NetworkInfo
- アプリケーションは最新のNumberTriviaをlocalにキャッシュして、オフライン時に呼び出す仕様となる
    - ネットワーク状態を表すclassが必要となる

```network_info.dart
abstract class NetworkInfo {
  Future<bool> get isConnected;
}
```

## RemoteDataSource
- DataSourceは作成アプリケーションと外部データの境界にあり、ここをシンプルに保ちたい
    - `Either<Failure, NumberTrivia>` ではなく、`NumberTrivia`モデルをシンプルに返すことにする（Jsonから変換されたもの）
    - Error発生時は例外を投げる
        - この例外の有無をRepositoryが処理してEither型にする

```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();
}
```

```exception.dart
class ServerException implements Exception {}

class CacheException implements Exception {}
```

- Repositoryは`Exception`をcatchして`Either<Failure, NumberTrivia>`を返す
    - だから`Failure`は`Exception`に応じたものになる

```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 {}
```

## LocalDataSource
- LocalDataSourceでは、以下の2つの処理をする
    - 取得した最新のModelをcacheする
    - cacheを取り出す

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

# Repositoryのコーディング
- これまでにDataSourceとNetworkInfoのContractを書いたので、Repository作成に着手できる
    - DataSourceとNetworkInfoのMockを作成することでTestを作成する

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

```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;
  }
}
```

# 学んだこと
## 例外の扱い
- DataSourceでは以下のような場合に例外を投げる
    - 404 NOT FOUND
    - キャッシュが見つからない
- Repositoryは例外をcatchしてUseCaseに`Either<Failure, NumberTrivia>`を渡す
- これは、DataSourceが外部との境界線にいて、外部のエラーをシンプルに返すようにしたいからである
    - Repositoryより内側の世界では基本的にはコントロールされており、バグ以外のエラーは発生しない前提ということだと思う

## 疑問: Eitherを使わない場合はどうするか
- このチュートリアルではEitherを使ってFailureを渡す
    - Eitherを使わない場合は、都度例外を投げてcatchするということになるのだと思う
        - その場合、CleanArchitectureのように多層構造ではthrow-catchが多重で発生するため煩雑になるということだと思う
            - だからEitherを使って単純化している

## Contractを使う理由は依存性逆転
- CleanArchitectureでは内側から外側にのみ依存するが、RepositoryとDataSourceの部分では依存性逆転をしなければならない
    - そのために、RepositoryとDataSourceはContract(インターフェイス)を用いて開発することになる

### TDDかFakeか
- 内側から実装をすすめる際に外側のインターフェイスを利用して、以下のようにコーディングができる
    - TDD: Contractのmockを作ることで、未実装の外側のインスタンスを利用してコーディングできる
    - Fake: ContractからFakeクラスを作り、ダミーデータでコーディングできる