3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

わたしのFlutterお勉強用Advent Calendar 2020

Day 19

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

Last updated at Posted at 2020-12-19

前回までの振り返り

前回までの流れ

  • 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クラスを作り、ダミーデータでコーディングできる
3
3
0

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
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?