DDD Value Objects
A collection of reusable Value Objects for Domain-Driven Design (DDD) in Flutter/Dart applications.
Features
This package provides ready-to-use Value Objects with built-in validation, immutability, and proper equality:
- EmailAddress - Email validation with RFC 5322 compliance
- Password - Configurable password validation (strong, medium, weak presets)
- Name - FirstName, LastName, and FullName with proper capitalization
- Age - Age validation with customizable constraints
Core Features
- Internationalization - Built-in support for English and French error messages
- Immutability - All Value Objects are immutable by design
- Functional approach - Uses
Eitherfor validation results - Type-safe - Leverage Dart's type system for domain modeling
- Well-tested - Comprehensive test coverage (159+ tests)
Installation
Add this to your pubspec.yaml:
dependencies:
ddd_value_objects: ^0.1.0
Then run:
flutter pub get
Usage
EmailAddress
import 'package:ddd_value_objects/ddd_value_objects.dart';
// Valid email
final email = EmailAddress('user@example.com');
if (email.isValid) {
print(email.getOrCrash()); // "user@example.com"
print(email.domain); // "example.com"
print(email.localPart); // "user"
}
// Invalid email
final invalidEmail = EmailAddress('not-an-email');
print(invalidEmail.isValid); // false
print(invalidEmail.failureOrNull?.message); // "Invalid email address"
Password
// Strong password (default - requires uppercase, lowercase, digit, special char, min 8)
final strongPass = Password('MyP@ssw0rd123');
print(strongPass.isValid); // true
print(strongPass.strength); // 85 (0-100 scale)
print(strongPass.strengthLevel); // "strong"
// Medium password (min 6, no special char required)
final mediumPass = Password('MyPass123', config: PasswordValidationConfig.medium);
// Weak password (min 4, only lowercase required)
final weakPass = Password('pass', config: PasswordValidationConfig.weak);
// Custom configuration
final customPass = Password('test1234', config: PasswordValidationConfig(
minLength: 8,
requireUpperCase: false,
requireSpecialCharacter: false,
));
// Check password characteristics
print(strongPass.hasUpperCase); // true
print(strongPass.hasDigit); // true
print(strongPass.hasSpecialCharacter); // true
Name
// FirstName - automatically capitalizes
final firstName = FirstName('joshua');
print(firstName.getOrCrash()); // "Joshua"
// LastName - converts to uppercase
final lastName = LastName('dalton');
print(lastName.getOrCrash()); // "DALTON"
// FullName - from string
final fullName = FullName('joshua dalton');
print(fullName.getOrCrash()); // "Joshua DALTON"
print(fullName.extractedFirstName); // "Joshua"
print(fullName.extractedLastName); // "DALTON"
// FullName - from parts
final fullNameFromParts = FullName.fromParts(
firstName: FirstName('Marie'),
lastName: LastName('Martin'),
);
print(fullNameFromParts.getOrCrash()); // "Marie MARTIN"
// Supports international characters
final accentedName = FirstName('josé');
print(accentedName.getOrCrash()); // "José"
// Supports hyphens and apostrophes
final hyphenatedName = FirstName("marie-claire");
print(hyphenatedName.getOrCrash()); // "Marie-claire"
Age
// Standard age validation (0-150)
final age = Age(25);
print(age.isValid); // true
print(age.isAdult); // true
print(age.isMinor); // false
print(age.isSenior); // false
print(age.category); // "adult"
print(age.birthYear()); // Calculates birth year
// Custom age constraints
final adultOnly = Age(17, minAge: 18); // Invalid
final childAge = Age(8, maxAge: 12); // Valid
// Age categories
final infant = Age(1);
print(infant.category); // "infant" (< 2)
final child = Age(8);
print(child.category); // "child" (2-12)
final teenager = Age(15);
print(teenager.category); // "teenager" (13-17)
final adult = Age(30);
print(adult.category); // "adult" (18-64)
final senior = Age(70);
print(senior.category); // "senior" (>= 65)
Internationalization (i18n)
By default, error messages are in English. You can switch to French or add your own languages:
import 'package:ddd_value_objects/ddd_value_objects.dart';
// Set language to French
FailureMessages.setLanguage(Language.fr);
final email = EmailAddress('invalid');
print(email.failureOrNull?.message); // "Adresse email invalide"
// Set back to English
FailureMessages.setLanguage(Language.en);
print(email.failureOrNull?.message); // "Invalid email address"
Error Handling
All Value Objects use the Either type from dartz for functional error handling:
final email = EmailAddress('test@example.com');
// Pattern 1: Check validity first
if (email.isValid) {
final value = email.getOrCrash();
// Use value safely
} else {
final error = email.failureOrNull;
print(error?.message);
}
// Pattern 2: Provide default value
final emailString = email.getOrElse('default@example.com');
// Pattern 3: Fold (functional approach)
email.value.fold(
(failure) => print('Error: ${failure.message}'),
(validEmail) => print('Valid: $validEmail'),
);
Common Failures
The package includes these common validation failures:
EmptyValue- Value is emptyTooShort- Value is too shortTooLong- Value is too longInvalidFormat- Invalid formatInvalidEmail- Invalid email formatNoUpperCase- Missing uppercase letterNoLowerCase- Missing lowercase letterNoDigit- Missing digitNoSpecialCharacter- Missing special characterInvalidCharacters- Contains invalid charactersContainsDigits- Contains digits (for names)NegativeValue- Value is negativeBelowMinimum- Below minimum valueExceedsMaximum- Exceeds maximum value
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
Joshua - Flutter Developer
Acknowledgments
- Built with dartz for functional programming
- Inspired by Domain-Driven Design principles