Generic use case
Domain Layer
Summary
This lesson introduces the concept of a generic UseCase interface to standardize all use cases across the application. By defining a base callable class with generic types for the expected return data and the required parameters, it ensures a consistent contract within the domain layer. This prevents boilerplate and enforces the rule that every use case must return either a failure or the expected type using the Either construct. The lesson also introduces a NoParam class to elegantly handle use cases that do not require any input arguments.
Key Ideas
- Generic UseCase acts as a unified base contract for all use cases in the domain layer.
- Generics (Type and Param) allow the base class to be highly flexible while maintaining strict type safety.
- call method is used instead of arbitrary method names (like execute or fetch) to allow the class instance to be invoked like a function.
- Either<Failure, Type> from the dartz package ensures standardized error handling without relying on thrown exceptions.
- NoParam class acts as a clean placeholder parameter for use cases that don't need any inputs.
Code Snippets
import 'package:dartz/dartz.dart';
import '../errors/failures.dart';
abstract class UseCase<Type, Param> {
Future<Either<Failure, Type>> call(Param param);
}
class NoParam {}Architecture Insight
The generic UseCase<Type, Params> base is a pattern, not a requirement. Its value is uniformity: every use case has the same call(params) signature, so the presentation layer (and any middleware like logging) treats them interchangeably.
The cost is one extra type parameter and a NoParams sentinel for parameterless cases. Worth it on a team where consistency wins arguments; skippable on a solo project where the extra ceremony just gets in the way.
My Notes
The core of this lesson is utilizing Dart's call() method to make use case instances callable like regular functions (e.g., calling fetchBooksUseCase(param) instead of fetchBooksUseCase.execute(param)).
- Generics matter: By forcing a Type and Param at the interface level, developers cannot accidentally forget to implement the exact signature required for a new use case.
- [Editor's note] The instructor places the UseCase interface in a shared location. It is a strict best practice to put this in a shared core/use_cases directory (rather than inside a specific feature) because it is a fundamental building block that will be shared across every feature in the app.
- [Editor's note] You will often see the Equatable package used in conjunction with NoParam (e.g., class NoParam extends Equatable) in production codebases to ensure that multiple instances of NoParam evaluate as strictly equal during testing.