Validating data and making illegal states unrepresentable is our second nature after the previous part. But, you know, having these nice ValueObject
s by themselves is quite pointless. Let's start taking steps toward implementing the application layer responsible for taking raw email and password String
s from the UI to the authentication backend.
Domain abstraction
Before we can venture out into the application layer, we have to create an abstraction for FirebaseAuth
and GoogleSignIn
in the domain layer. Why? First of all, we want to do everything we can to make Firebase into an implementation detail. Secondly, we're doing this to fulfill the DDD spec where the application layer cannot depend on classes from the infrastructure layer.
If you need a quick refresher to how the overall architecture looks like, here you go:
Which actions do we need to perform on the authentication backend? There are three things, which we will translate into methods.
- Register with email and password
- Sign in with email and password
- Sign in with Google
Although we're a long way from actually implementing these methods, we can create an abstraction right now and postpone its implementation for later. This will allow us to focus on the BLoCs in the application layer before jumping to implement all things Firebase in the bottom-most infrastructure layer.
Let us, therefore, create i_auth_facade.dart under domain/auth. Its methods will take in EmailAddress
and Password
value objects we created in the previous part. The "i" means interface - although Dart offers only abstract class
es which may or may not have an implementation, we can overcome this language design with strong naming conventions.
FirebaseAuth
and GoogleSignIn
.i_auth_facade.dart
abstract class IAuthFacade {
Future<void> registerWithEmailAndPassword({
@required EmailAddress emailAddress,
@required Password password,
});
Future<void> signInWithEmailAndPassword({
@required EmailAddress emailAddress,
@required Password password,
});
Future<void> signInWithGoogle();
}
Although you may think that the code above is perfectly fine, I find it deeply disturbing. How are we going to handle exceptions? As you can see on the diagram above, we want the classes on the repository level to catch all Exception
s and return them as Failure
s.
AuthFailure
We already know how to create Failure
s. We currently have two ValueFailure
s which are used within the EmailAddress
and Password
value objects to indicate invalid data. AuthFailure
will also be a freezed union and it will indicate issues from the IAuthFacade
.
Let's think about the possible failures which can occur during authentication. Granted, knowing at least a bit about the exceptions thrown by FirebaseAuth
may be helpful here but it's also possible to come up with these failures separately. Things that prohibit successful authentication include:
- User "taps out" of the 3rd party sign-in flow (Google in our case)
- There is an error on the auth server
- User wants to register with an email which is already in use
- User enters an invalid combination of email and password
Let's create this union inside domain/auth/auth_failure.dart.
auth_failure.dart
part 'auth_failure.freezed.dart';
@freezed
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
How can these failures be returned from the IAuthFacade
? The naive way would be to return Future<AuthFailure>
from the methods. This, however, would force us to use null
whenever a failure doesn't occur. As you know, nulls stink!
It's much better to use our trusted friend called Either
where we pass around failures inside the Left
side of it. But what are we going to put into the Right
side? Can we create something like Either<AuthFailure, void>
?
No. Dart's void
is only a keyword, not a type, and it cannot be used as a generic type parameter. Swift's Void
is actually a type, so you can use it in generics. Kotlin uses Unit
which is also a regular type. Thankfully, the dartz package provides us, Dart programmers, with a Unit
type too!
Unit
but for our purposes, we can think of it as of a functional equivalent to Dart's dumb void
keyword. If you're a category theory mathematician, please, calm down ?Let's update the IAuthFacade
interface.
i_auth_facade.dart
abstract class IAuthFacade {
Future<Either<AuthFailure, Unit>> registerWithEmailAndPassword({
@required EmailAddress emailAddress,
@required Password password,
});
Future<Either<AuthFailure, Unit>> signInWithEmailAndPassword({
@required EmailAddress emailAddress,
@required Password password,
});
Future<Either<AuthFailure, Unit>> signInWithGoogle();
}
This return type is perfect because we can easily use it even for methods which do return a value. If, for example, a method doesn't return void
(a.k.a. Unit
), we'd return Either<AuthFailure, SomeType>
.
Ready for the sign-in form
We have created an interface inside the domain layer which will allow us to implement the application logic without any FirebaseAuth
dependencies. We've also thought through the different AuthFailure
s that can occur regardless of the authentication backend we're using. Lastly, we've discovered the Unit
type which allows us to return "nothing" inside the Either
union.
All of this means that we're now ready to start implementing the sign-in form. We're not going to create any buttons and text fields just yet, instead, we'll hop into the application layer to work on the SignInFormBloc
.
nice video!
why don’t you us Equatable in this course??
There’s no need for it. We’ll use Freezed.
Great stuff!
It would be good to include the build runner command in the written tutorials 2 and 3. Right now they are mentioned only in the videos for those two tutorials and I’m sure that some people don’t use the video or (me) start with the written tutorial then watch. Plus, a cut and paste of the command would be nice!
Thanks, and keep up the good work!
Wayne
Instead of Either, couldn’t Option be used?
The generics have been stripped by formatting, i meant use Option(T) instead of Either(T, Unit)
Technically yes, but I’d rather keep the failure on the left side. In short, this will allow us to use higher-order functions such as map and flatMap later on.
Thank you for this tutorial…but you said you will update the code in future, and you haven’t…
using dartz plugin in the domain doesn’t make it already dependent on an external package?