search
LoginSignup
3

More than 1 year has passed since last update.

posted at

updated at

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

前回までの振り返り

前回までの流れ

  • 1: 概要
    • CleanArchitectureの説明
  • 2: EntityとUseCaseの作成
    • Entityの作成
    • UseCaseの作成
      • インターフェイスの作成
      • Test作成
      • 実装の作成
  • 3: Domain層のリファクタ
    • UseCaseのインターフェイスの作成
    • もう1つのUseCaseの作成
  • 4: Data層とModel
    • Modelの作成

図による解説

  • 次の同心円の内側から外側という順でコーディングしています。

CleanArchitecture.jpg

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

Clean-Architecture-Flutter-Diagram.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>を返す
    • だからFailureExceptionに応じたものになる
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クラスを作り、ダミーデータでコーディングできる

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3