We have the big picture of Domain-Driven Design already in our minds so now it’s time to get coding. You might think that since we are building a Firebase app, we will need to worry about using the Firestore and FirebaseAuth classes right from the start. That’s not true at all with DDD. Let’s start in the most important layer of them all - the domain layer. Namely, we are going to tackle authentication.

Email & password

How can we sign in using email and password? The usual way would be to have a sign in form that would validate the inputted Strings. You know, email addresses must have the ‘@’ sign and passwords must be at least six characters long. We would then pass these Strings to the authentication service, in our case, Firebase Auth.
Sure, this is perfectly doable but we have to realize one important fact! Let’s imagine we have a function which accepts two parameters.

unsuspecting_function.dart

Future<void> signIn({
  @required String email,
  @required String password,
}) async {
  // Sign in the user
}

Is it reasonable to call this function with the following arguments?

function_call.dart

signIn(email: 'pazzwrd', password: '[email protected]');

Of course, it isn’t. But what stops us from passing an email address to a parameter expecting a password? They’re all Strings, after all.

Type-safety evolution

The first thing we can do is to create simple classes for EmailAddress and Password. Let’s focus on the former, so that we don’t have to deal with two classes for now. By the way, we are going to be mostly inside the domain/auth folder. Check out the GitHub repository whenever you’re unsure.

auth/email_address.dart

import 'package:meta/meta.dart';

@immutable
class EmailAddress {
  final String value;

  const EmailAddress(this.value) : assert(value != null);

  @override
  String toString() => 'EmailAddress($value)';

  @override
  bool operator ==(Object o) {
    if (identical(this, o)) return true;
    return o is EmailAddress && o.value == value;
  }

  @override
  int get hashCode => value.hashCode;
}

This is much more expressive than a plain String plus we get an immediate non-null check. We also override the equality operator to perform value equality and also the toString() method to have a reasonable output.

A class like this is surely not ideal though. Yes, as soon as we have an EmailAddress instance, we cannot mistakenly pass it to a function expecting a Password. They’re two different types. What we can do now though is the following.

instantiation.dart

void f() {
  const email = EmailAddress('pazzwrd');
  // Happily use the email address 
}

As you can see, we’ve escaped one problem only to get another one. Instances of EmailAddress happily accept any String into its constructor and then pretend like nothing happened if it doesn’t fulfill the “contract” of what the EmailAddress represents. That’s why we’re going to create validatedvalue objects.

Validating at instantiation

You are probably used to validating Strings in a TextFormField. (If not and you’re still here, this series is not for you. Please, come back after you learn the basics.) Unless the TextFormField holds a valid value, you’re not going to be able to save the Form and proceed with the invalid value.

We will take this principle and take it to a whole another level. You see, not all validation is equal. We’re about to perform the safest validation of them all - we’re going to make illegal states unrepresentable. In other words, we will make it impossible for a class like EmailAddress to hold an invalid value not just while it’s in the TextFormField but throughout its whole lifespan.

The most straightforward way of validating at instantiation is to create a factory constructor which will perform the validation logic by throwing Exceptions if something doesn’t play right and then finally instantiate an EmailAddress by calling a private constructor.

auth/email_address.dart

@immutable
class EmailAddress {
  final String value;

  factory EmailAddress(String input) {
    assert(input != null);
    return EmailAddress._(
      validateEmailAddress(input),
    );
  }

  const EmailAddress._(this.value);

  // toString, equals, hashCode...
}

String validateEmailAddress(String input) {
  // Maybe not the most robust way of email validation but it's good enough
  const emailRegex =
      r"""^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~][email protected][a-zA-Z0-9]+\.[a-zA-Z]+""";
  if (RegExp(emailRegex).hasMatch(input)) {
    return input;
  } else {
    throw InvalidEmailException(failedValue: input);
  }
}

class InvalidEmailException implements Exception {
  final String failedValue;

  InvalidEmailException({@required this.failedValue});
}

We’re definitely getting somewhere. Passing an invalid email string to the EmailAddress public factory will result in an InvalidEmailException being thrown. So yes, we do make illegal states unrepresentable.

To be honest though, if throwing exceptions were the only way we could prevent invalid values from being held inside validated value objects, you wouldn’t be even reading this post because this series would never have happened. Why? Let’s see what we have to do to instantiate just one EmailAddress

instantiation.dart

void f() {
  try {
  final email = EmailAddress('pazzwrd');
  } on InvalidEmailException catch (e) {
    // Do some exception handling here
  }
  // If you have multiple validators, remember to catch their exceptions too
}

Yeah, this is not the way to go. Creating this monstrosity everywhere you instantiate a validated value object would quickly become a painful and unmaintainable experience.

Either a failure or a value

Our current troubles stem from the fact that the EmailAddress class holds only a single field of type String. What if, instead of throwing an InvalidEmailException, we would instead somehow store it inside the class? And because we don’t want to use Exceptions in an unconventional way, we’d create a plain old InvalidEmail**Failure** class.

This will allow us to not litter our codebase with try and catch statements at the time of instantiation. We will still need to handle the invalid value at the time of using the EmailAddress. We have to handle it somewhere, right?

However, we don’t want to create a second class field called, for example, failure. I mean, would you remember to write the following everywhere you used an EmailAddress? And more importantly, would you even bother writing this code if it wasn’t enforced on you?

usage_of_email_address.dart

void insideTheUI() {
  EmailAddress emailAddress;
  // ...
  if (emailAddress.failure == null) {
    // Display the valid email address
  } else {
    // Show an error Snackbar
  }
}

The code above is frankly horrible. It relies on nulls to represent missing values - this is a recipe for a disaster. What if we joined the value and failure fields into one by using a union type? And not just any sort of a union - we’re going to use Either.
Either is a union type from the dartz package specifically suited to handle what we call “failures”. It is a union of two values, commonly called Left and Right. The left side holds Failures and the right side holds the correct values, for example, Strings.

Additionally, we’ll want to introduce a union type even for Failures. Although we currently have only one “ValueFailure” representing an invalid email address, we are going to have a bunch more of them throughout this series. Even here, unions will help us not to forget about any possible “case” of a ValueFailure.

So, we’re going to use dartz for Either but what about the regular unions? There are multiple options to choose from until Dart introduces algebraic data types into the language itself. The best option is to use the freezed package. Let’s add them to **pubspec.yaml **and since freezed uses code generation, we’ll also add a bunch of other dependencies.

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  dartz: ^0.9.0-dev.6
  freezed_annotation: ^0.7.1

dev_dependencies:
  build_runner:
  freezed: ^0.9.2

ValueFailure union

Before jumping back into the EmailAddress class, let’s first ditch the InvalidEmailException in favor of the aforementioned union. We’ll group all failures from validated value objects into one such union - ValueFailure. Since this is something common across features, we’ll create the **failures.dart **file inside the domain/core folder. While we’re at it, let’s also create a “short password” failure.
To learn about all the other things freezed can do, check out its official documentation

core/failures.dart

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'failures.freezed.dart';

@freezed
abstract class ValueFailure<T> with _$ValueFailure<T> {
  const factory ValueFailure.invalidEmail({
    @required T failedValue,
  }) = InvalidEmail<T>;
  const factory ValueFailure.shortPassword({
    @required T failedValue,
  }) = ShortPassword<T>;
}

We made the class generic because we will also validate values other than

The value which can be held inside an EmailAddress will no longer be just a String. Instead, it will be Either<ValueFailure<String>, String>. The same will also be the return type of the validateEmailAddress function. Then, instead of throwing an exception, we’re going to return the left side of Either.

auth/email_address.dart

@immutable
class EmailAddress {
  final Either<ValueFailure<String>, String> value;

  factory EmailAddress(String input) {
    assert(input != null);
    return EmailAddress._(
      validateEmailAddress(input),
    );
  }

  const EmailAddress._(this.value);

  // toString, equals, hashCode...
}

Either<ValueFailure<String>, String> validateEmailAddress(String input) {
  const emailRegex =
      r"""^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~][email protected][a-zA-Z0-9]+\.[a-zA-Z]+""";
  if (RegExp(emailRegex).hasMatch(input)) {
    return right(input);
  } else {
    return left(ValueFailure.invalidEmail(failedValue: input));
  }
}

Displaying the value held inside an EmailAddress object now doesn’t leave any room for doubts. We simply have to handle the possible ValueFailure whether we feel like it or not.

some_widget.dart

void showingTheEmailAddressOrFailure(EmailAddress emailAddress) {
  // Longer to write but we can get the failure instance
  final emailText1 = emailAddress.value.fold(
    (left) => 'Failure happened, more precisely: $left',
    (right) => right,
  );

  // Shorter to write but we cannot get the failure instance
  final emailText2 =
      emailAddress.value.getOrElse(() => 'Some failure happened');
}

Password

EmailAddress is implemented and it contains a lot of boilerplate code for toString, ==, and hashCode overrides. We surely don’t want to duplicate all of this into a Password class. This is a perfect opportunity to create a super class.

Abstract ValueObject

This abstract class will extend specific value objects across multiple features. We’re going to create it under domain/core. All it does is just extracting boilerplate into one place. Of course, we heavily rely on generics to allow the value to be of any type.

core/value_objects.dart

@immutable
abstract class ValueObject<T> {
  const ValueObject();
  Either<ValueFailure<T>, T> get value;

  @override
  bool operator ==(Object o) {
    if (identical(this, o)) return true;
    return o is ValueObject<T> && o.value == value;
  }

  @override
  int get hashCode => value.hashCode;

  @override
  String toString() => 'Value($value)';
}

We can now extend this class from EmailAddress. Not so bad now, huh?

auth/email_address.dart

class EmailAddress extends ValueObject<String> {
  @override
  final Either<ValueFailure<String>, String> value;

  factory EmailAddress(String input) {
    assert(input != null);
    return EmailAddress._(
      validateEmailAddress(input),
    );
  }

  const EmailAddress._(this.value);
}

Creating and organizing

Let’s first bring order to our files before we go ahead to create yet another class and validation function. Feature-specific value objects will live inside their domain feature folders. In case of EmailAddress and Password, that’s domain/auth.
As for the validation functions, I like to put all of them into a single file under domain/core.

The validation logic for a Password is extremely simple in our case. Just check the length of the input.

core/value_validators.dart

Either<ValueFailure<String>, String> validateEmailAddress(String input) {
  // Already implemented
}

Either<ValueFailure<String>, String> validatePassword(String input) {
  // You can also add some advanced password checks (uppercase/lowercase, at least 1 number, ...)
  if (input.length >= 6) {
    return right(input);
  } else {
    return left(ValueFailure.shortPassword(failedValue: input));
  }
}

The Password class will be almost identical to EmailAddress - except for the validation.

auth/value_objects.dart

class EmailAddress extends ValueObject<String> {
  // Already implemented
}

class Password extends ValueObject<String> {
  @override
  final Either<ValueFailure<String>, String> value;

  factory Password(String input) {
    assert(input != null);
    return Password._(
      validatePassword(input),
    );
  }

  const Password._(this.value);
}

Fundamentals are important

It took quite a long time to validate just two value objects, didn’t it? Not quite because I actually took you through the whole process of coming up with the best solution of “making illegal states unrepresentable” in Dart. Once you have the ValueObject super class in place and you know what you’re doing, creating something like a validated TodoName won’t take more than a couple of minutes.

The best thing about having these specific value objects in places that would otherwise be just plain Strings is that you cannot possibly mess up, no matter how hard you try. We’re using the Dart type system to guide us.

In the next part, we’re going to write code in the application layer responsible for gluing together the UI with the authentication backend. Why didn’t I say Firebase Auth? As you can imagine, we’re going to use abstractions!

#flutter #firebase #security #mobileapps

Flutter Firebase & DDD Course [2] – Authentication Value Objects
16.15 GEEK