Flutter Firebase & DDD(3)

김형태·2024년 1월 28일
0

Flutter

목록 보기
4/5

데이터를 검증하고 illegal한 상태가 나타날 수 없게 만드는 것은 이전 부분에 이어 두 번째 특성(nature)입니다. 그러나 알다시피 이러한 멋진 ValueObject를 자체적으로 갖는 것은 의미가 없습니다. UI에서 인증 백엔드로 raw 이메일 및 비밀번호 String을 가져오는 역할을 담당하는 애플리케이션 계층을 구현하는 단계를 시작해 보겠습니다.

Domain abstraction

애플리케이션 계층을 탐색하기 전에 도메인 계층에서 FirebaseAuthGoogleSignIn에 대한 추상화를 만들어야 합니다. 왜냐구요? 우선, 우리는 Firebase를 구현 세부사항으로 만들기 위해 최선을 다하고 싶습니다. 둘째, 애플리케이션 계층인프라 계층의 클래스에 의존할 수 없는 DDD 사양을 충족하기 위해 이 작업을 수행하고 있습니다.

전체 아키텍처가 어떻게 생겼는지 빠르게 확인하고 싶다면 다음을 참조하세요.

인증 백엔드에서 어떤 작업을 수행해야 합니까? 메소드로 변환할 로직이 세 가지가 있습니다.

  1. 이메일 & 비밀번호로 가입하기
  2. 이메일 & 비밀번호로 로그인하기
  3. 구글로 로그인하기

이러한 메서드를 실제로 구현하려면 아직 멀었지만 지금 당장 추상화를 만들고 나중에 구현을 연기할 수 있습니다. 이를 통해 우리는 최하위 인프라 계층에서 Firebase의 모든 것을 구현하기 전에 애플리케이션 계층의 BLoC에 집중할 수 있습니다.

따라서 domain/auth 아래에 i_auth_facade.dart를 생성해 보겠습니다. 해당 메서드는 이전 글에서 만든 EmailAddressPassword value object를 사용합니다. "i"는 interface를 의미합니다. Dart는 구현이 있거나 없을 수 있는 abstract class만 제공하지만 강력한 명명 규칙을 사용하면 이러한 언어 설계를 극복할 수 있습니다.

Facade는 이상한(weird) 인터페이스들을 가진 두 개 이상의 클래스를 하나의 단순화된 인터페이스로 연결하기 위한 디자인 패턴입니다. 우리의 경우에는 FirebaseAuthGoogleSignIn을 연결합니다.

abstract class IAuthFacade {
  Future<void> registerWithEmailAndPassword({
     EmailAddress emailAddress,
     Password password,
  });
  Future<void> signInWithEmailAndPassword({
     EmailAddress emailAddress,
     Password password,
  });
  Future<void> signInWithGoogle();
}

Facade는 계층 다이어그램의 저장소와 동일한 수준에 있습니다. 여전히 데이터 소스원시(raw) 데이터를 처리하며 포함된 클래스의 인터페이스를 단순화하는 추가적인 역할도 있습니다.

위의 코드가 완벽하게 괜찮다고 생각할 수도 있지만, 저는 이것이 매우 혼란스럽다고 생각합니다. 예외는 어떻게 처리하는 걸까요? 위 다이어그램에서 볼 수 있듯이 저장소 수준의 클래스가 모든 Exception포착하고 이를 Failure로 반환하기를 원합니다.

AuthFailure

우리는 이미 Failure를 만드는 방법을 알고 있습니다. 현재 유효하지 않은 데이터를 나타내기 위해 EmailAddressPassword value obejct 내에서 사용되는 두 개의 ValueFailures가 있습니다. AuthFailurefreezed union일 것이고 IAuthFacade의 문제를 나타낼 것입니다.

인증 중에 발생할 수 있는 실패에 대해 생각해 봅시다. 물론, FirebaseAuth에서 발생하는 exception에 대해 최소한 조금이라도 알고 있으면 도움이 될 수 있지만 이러한 실패가 별도로 발생할 수도 있습니다. 성공적인 인증을 금지하는 사항은 다음과 같습니다.

  1. 사용자가 제3자 로그인 흐름에서 'taps out'하는 경우(우리 앱에서는 Google)
  2. 인증 서버에 오류가 있는 경우
  3. 사용자가 이미 사용 중인 이메일로 등록하는 경우
  4. 사용자가 잘못된 이메일과 비밀번호 조합하는 경우

domain/auth/auth_failure.dart 내에 이 union을 만들어 보겠습니다.

part 'auth_failure.freezed.dart';


abstract class AuthFailure with _$AuthFailure {
  const factory AuthFailure.cancelledByUser() = CancelledByUser;
  // Serves as a "catch all" failure if we don't know what exactly went wrong
  const factory AuthFailure.serverError() = ServerError;
  const factory AuthFailure.emailAlreadyInUse() = EmailAlreadyInUse;
  const factory AuthFailure.invalidEmailAndPasswordCombination() =
      InvalidEmailAndPasswordCombination;
}

Returning failures

IAuthFacade에서 이러한 failures를 어떻게 반환할 수 있나요? 나이브한 방법은 메서드에서 Future<AuthFailure>를 반환하는 것입니다. 그러나 이렇게 하면 실패가 발생하지 않을 때마다 null을 사용하게 됩니다. 아시다시피 null은 악취가 납니다!

내부적으로 Left 에서 failure를 전달하는 신뢰할 수 있는 Either를 사용하는 것이 훨씬 낫습니다. 그런데 Right에는 무엇을 넣을 건가요? Either<AuthFailure, void> 같은 것을 만들 수 있나요?

아닙니다. Dart의 void는 타입이 아닌 키워드일 뿐이며 제네릭 유형 매개변수로 사용할 수 없습니다. Swift의 Void는 실제로 타입이므로 제네릭에서 사용할 수 있습니다. Kotlin은 역시 일반 유형인 Unit을 사용합니다. 고맙게도 dartz 패키지는 Dart 프로그래머들에게도Unit 유형을 제공합니다!

Unit에 대해 할 말이 많지만 우리의 목적에 따라 Dart의 멍청한 void 키워드와 기능적으로 동등한 것으로 생각할 수 있습니다. 당신이 범주론 수학자라면 진정하세요.

IAuthFacade 인터페이스를 업데이트해 보겠습니다.

abstract class IAuthFacade {
  Future<Either<AuthFailure, Unit>> registerWithEmailAndPassword({
     EmailAddress emailAddress,
     Password password,
  });
  Future<Either<AuthFailure, Unit>> signInWithEmailAndPassword({
     EmailAddress emailAddress,
     Password password,
  });
  Future<Either<AuthFailure, Unit>> signInWithGoogle();
}

이 반환 유형은 값을 반환하는 메서드에도 쉽게 사용할 수 있기 때문에 완벽합니다. 예를 들어 메소드가 void(즉, Unit)를 반환하지 않으면 Either<AuthFailure, SomeType>을 반환합니다.

Ready for the sign-in form

우리는 FirebaseAuth 종속성 없이 애플리케이션 로직을 구현할 수 있도록 도메인 계층 내부에 인터페이스를 만들었습니다. 또한 사용 중인 인증 백엔드에 관계없이 발생할 수 있는 다양한 AuthFailures에 대해서도 생각해 보았습니다. 마지막으로, 우리는 Either union 내에서 "nothing"을 반환할 수 있는 Unit 타입을 발견했습니다.

이 모든 것은 이제 로그인 form을 시작할 준비가 되었음을 의미합니다. 아직 버튼과 텍스트 필드를 만들지는 않을 것입니다. 대신, SignInFormBloc 작업을 위해 애플리케이션 계층으로 이동하겠습니다.

profile
steady

0개의 댓글