Right now, all validation methods are synchronous, and there is no way to make them async.
@Modddel(
// ...
)
class Age extends SingleValueObject<InvalidAge, ValidAge> with _$Age {
Age._();
factory Age(int value) {
return _$Age._create(
value: value,
);
}
// ⚠️ This can't be async
@override
Option<AgeLegalFailure> validateLegal(age) {
if (age.value < 18) {
return some(const AgeLegalFailure.minor());
}
return none();
}
}
Given that factory constructors can't be async, I only see three options :
Option 1 : Use the builder pattern
We generate a "Builder" that you should instantiate and then call an async method (for example runValidations) to get your modddel instance.
final builder = AgeBuilder(20);
final age = await builder.runValidations();
- Advantages :
- Ability to lazily validate a modddel (Although this can be easily accomplished by the user using a package like lazy_evaluation or any other way).
- Disadvantages :
- Boilerplate code +++
- Different syntax for instantiating a modddel with sync validations versus async validations
Option 2 : Initialize the modddel manually
We add an init method to the modddel that the user should call, and its returned value is the initialized modddel.
final age = await Age(20).init();
- Advantages :
- Disadvantages :
- ⚠️ RUNTIME ERRORS : If the user forgets to call
init or accidentally uses the instance created with the factory constructor (Age(20)) , there will be runtime errors that may be hard to debug
- Again : different syntax for instantiating a sync modddel versus async
Option 3 : Ditch the factory constructors for async methods
We replace the factory constructors with static methods :
// Instead of a factory constructor :
factory Age(int value) {
return _$Age._create(
value: value,
);
}
// We use a static method
static Age create(int value) {
// To accompany this change, this static method is no longer private
// (it never needed to be private since the mixin `_$Age` is private)
return _$Age.create(
value: value,
);
}
The name of the method should be create for solo modddels, and create{UnionCaseName} for unions (ex for a Weather union : createSunny, createRainy...).
Then, these static methods can easily be made async :
// Change the return type to a future, and optionally add the async keyword if needed
static Future<Age> create(int value) {
return _$Age.create(
value: value,
);
}
And then for making an instance of the modddel :
// If sync :
final age = Age.create(20);
// If async :
final age = await Age.create(20);
- Advantages :
- Disadvantages :
- BREAKING CHANGE
- Static methods can't be unnamed like factory constructors (
Age.create(20) vs Age(20)).
- Static methods don't preserve generic type information (so if you have generics, you need to forward them in a verbose way)
- Usually, you create an instance with a constructor, and not a static method, so this might be a little less elegant
Right now, all validation methods are synchronous, and there is no way to make them async.
Given that factory constructors can't be async, I only see three options :
Option 1 : Use the builder pattern
We generate a "Builder" that you should instantiate and then call an async method (for example
runValidations) to get your modddel instance.Option 2 : Initialize the modddel manually
We add an
initmethod to the modddel that the user should call, and its returned value is the initialized modddel.initor accidentally uses the instance created with the factory constructor (Age(20)) , there will be runtime errors that may be hard to debugOption 3 : Ditch the factory constructors for async methods
We replace the factory constructors with static methods :
The name of the method should be
createfor solo modddels, andcreate{UnionCaseName}for unions (ex for aWeatherunion :createSunny,createRainy...).Then, these static methods can easily be made async :
And then for making an instance of the modddel :
_$Age.create) has the same name as the static methodAge.create(20)vsAge(20)).