데이터를 검증하고 illegal한 상태가 나타날 수 없게 만드는 것은 이전 부분에 이어 두 번째 특성(nature)입니다. 그러나 알다시피 이러한 멋진 ValueObject
를 자체적으로 갖는 것은 의미가 없습니다. UI에서 인증 백엔드로 raw 이메일 및 비밀번호 String
을 가져오는 역할을 담당하는 애플리케이션 계층을 구현하는 단계를 시작해 보겠습니다.
애플리케이션 계층을 탐색하기 전에 도메인 계층에서 FirebaseAuth
및 GoogleSignIn
에 대한 추상화를 만들어야 합니다. 왜냐구요? 우선, 우리는 Firebase를 구현 세부사항으로 만들기 위해 최선을 다하고 싶습니다. 둘째, 애플리케이션 계층이 인프라 계층의 클래스에 의존할 수 없는 DDD 사양을 충족하기 위해 이 작업을 수행하고 있습니다.
전체 아키텍처가 어떻게 생겼는지 빠르게 확인하고 싶다면 다음을 참조하세요.
인증 백엔드에서 어떤 작업을 수행해야 합니까? 메소드로 변환할 로직이 세 가지가 있습니다.
이러한 메서드를 실제로 구현하려면 아직 멀었지만 지금 당장 추상화를 만들고 나중에 구현을 연기할 수 있습니다. 이를 통해 우리는 최하위 인프라 계층에서 Firebase의 모든 것을 구현하기 전에 애플리케이션 계층의 BLoC에 집중할 수 있습니다.
따라서 domain/auth 아래에 i_auth_facade.dart를 생성해 보겠습니다. 해당 메서드는 이전 글에서 만든 EmailAddress
및 Password
value object를 사용합니다. "i"는 interface를 의미합니다. Dart는 구현이 있거나 없을 수 있는 abstract class
만 제공하지만 강력한 명명 규칙을 사용하면 이러한 언어 설계를 극복할 수 있습니다.
Facade는 이상한(weird) 인터페이스들을 가진 두 개 이상의 클래스를 하나의 단순화된 인터페이스로 연결하기 위한 디자인 패턴입니다. 우리의 경우에는
FirebaseAuth
와GoogleSignIn
을 연결합니다.
abstract class IAuthFacade {
Future<void> registerWithEmailAndPassword({
EmailAddress emailAddress,
Password password,
});
Future<void> signInWithEmailAndPassword({
EmailAddress emailAddress,
Password password,
});
Future<void> signInWithGoogle();
}
Facade는 계층 다이어그램의 저장소와 동일한 수준에 있습니다. 여전히 데이터 소스의 원시(raw) 데이터를 처리하며 포함된 클래스의 인터페이스를 단순화하는 추가적인 역할도 있습니다.
위의 코드가 완벽하게 괜찮다고 생각할 수도 있지만, 저는 이것이 매우 혼란스럽다고 생각합니다. 예외는 어떻게 처리하는 걸까요? 위 다이어그램에서 볼 수 있듯이 저장소 수준의 클래스가 모든 Exception
을 포착하고 이를 Failure
로 반환하기를 원합니다.
우리는 이미 Failure
를 만드는 방법을 알고 있습니다. 현재 유효하지 않은 데이터를 나타내기 위해 EmailAddress
및 Password
value obejct 내에서 사용되는 두 개의 ValueFailures
가 있습니다. AuthFailure
도 freezed union일 것이고 IAuthFacade
의 문제를 나타낼 것입니다.
인증 중에 발생할 수 있는 실패에 대해 생각해 봅시다. 물론, FirebaseAuth
에서 발생하는 exception에 대해 최소한 조금이라도 알고 있으면 도움이 될 수 있지만 이러한 실패가 별도로 발생할 수도 있습니다. 성공적인 인증을 금지하는 사항은 다음과 같습니다.
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;
}
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>
을 반환합니다.
우리는 FirebaseAuth
종속성 없이 애플리케이션 로직을 구현할 수 있도록 도메인 계층 내부에 인터페이스를 만들었습니다. 또한 사용 중인 인증 백엔드에 관계없이 발생할 수 있는 다양한 AuthFailures
에 대해서도 생각해 보았습니다. 마지막으로, 우리는 Either
union 내에서 "nothing"을 반환할 수 있는 Unit
타입을 발견했습니다.
이 모든 것은 이제 로그인 form을 시작할 준비가 되었음을 의미합니다. 아직 버튼과 텍스트 필드를 만들지는 않을 것입니다. 대신, SignInFormBloc
작업을 위해 애플리케이션 계층으로 이동하겠습니다.