Reference location:
.claude/references/patterns/repository-patterns.md
Repository abstracts data sources and handles data access from the Domain Layer.
Pattern Comparison#
| Pattern | Pros | Cons | Recommended For |
|---|---|---|---|
| Mixin Pattern | Network logic separation, reusability | Increased structural complexity | Multiple data sources, complex APIs |
| Direct Implementation | Simple and clear | Difficult logic reuse | Simple CRUD |
Repository Interface (Domain Layer)#
// domain/repository/i_user_repository.dart
import 'package:dependencies/dependencies.dart';
abstract interface class IUserRepository {
Future<Either<Failure, User>> getUser(int id);
Future<Either<Failure, List<User>>> getUsers();
Future<Either<Failure, User>> createUser(CreateUserParams params);
Future<Either<Failure, User>> updateUser(UpdateUserParams params);
Future<Either<Failure, Unit>> deleteUser(int id);
// SWR í¨í´ěŠ Stream (ěşě ě ëľě /client-cache skill reference)
Stream<Either<Failure, List<User>>> getUsersAsStream();
}
Pattern A: Mixin Pattern â Recommended#
Repository Implementation#
// data/repository/user_repository.dart
import 'package:dependencies/dependencies.dart';
@LazySingleton(as: IUserRepository)
class UserRepository
with UserServerpodMixin // ë¤í¸ěíŹ ëĄě§ ëśëŚŹ
implements IUserRepository {
UserRepository(this._serverpodService);
final ServerpodService _serverpodService;
@override
ServerpodClient get client => _serverpodService.client;
}
Serverpod Mixin (Network Logic)#
// data/repository/mixins/user_serverpod_mixin.dart
import 'package:serverpod_service/serverpod_service.dart' as serverpod;
mixin UserServerpodMixin implements IUserRepository {
serverpod.ServerpodClient get client;
@override
Future<Either<Failure, User>> getUser(int id) async {
try {
final dto = await client.users.getUser(id);
if (dto == null) {
return left(NotFoundFailure('User not found: $id'));
}
return right(dto.toEntity());
} on serverpod.ServerpodClientException catch (e, st) {
return left(ServerFailure(e.message, error: e, stackTrace: st));
}
}
@override
Future<Either<Failure, List<User>>> getUsers() async {
try {
final dtos = await client.users.getUsers();
return right(dtos.map((dto) => dto.toEntity()).toList());
} on serverpod.ServerpodClientException catch (e, st) {
return left(ServerFailure(e.message, error: e, stackTrace: st));
}
}
@override
Future<Either<Failure, User>> createUser(CreateUserParams params) async {
try {
final dto = await client.users.createUser(
name: params.name,
email: params.email,
);
return right(dto.toEntity());
} on serverpod.ServerpodClientException catch (e, st) {
return left(ServerFailure(e.message, error: e, stackTrace: st));
}
}
}
Reason for Using Namespaces#
- Prevents name collisions between Domain Entity and API DTO (User, Category, etc.)
-
Clearly distinguished with
serverpod.namespace to maintain Clean Architecture principles
Pattern B: Direct Implementation Pattern#
// data/repository/user_repository.dart
import 'package:dependencies/dependencies.dart';
@LazySingleton(as: IUserRepository)
class UserRepository implements IUserRepository {
UserRepository(this._serverpodService);
final ServerpodService _serverpodService;
@override
Future<Either<Failure, User>> getUser(int id) async {
try {
final response = await _serverpodService.client.users.getUser(id);
if (response == null) {
return left(NotFoundFailure('User not found: $id'));
}
return right(response.toEntity());
} on Exception catch (e, st) {
return left(ServerFailure(e.toString(), error: e, stackTrace: st));
}
}
@override
Future<Either<Failure, List<User>>> getUsers() async {
try {
final response = await _serverpodService.client.users.getUsers();
return right(response.map((dto) => dto.toEntity()).toList());
} on Exception catch (e, st) {
return left(ServerFailure(e.toString(), error: e, stackTrace: st));
}
}
}
Structure#
data/repository/
âââ user_repository.dart # Repository 꾏í체
âââ mixins/
âââ user_serverpod_mixin.dart # ë¤í¸ěíŹ ëĄě§ (Mixin í¨í´)
DTO to Entity Conversion#
// data/mapper/user_mapper.dart (or extension)
extension UserDtoMapper on serverpod.User {
User toEntity() {
return User(
id: id!,
name: name,
email: email,
avatarUrl: avatarUrl,
createdAt: createdAt,
);
}
}
Selection Guide#
Need network logic reuse? âYesâ Mixin í¨í´ (ęśěĽ)
âNoâ Multiple data source combination? âYesâ Mixin í¨í´
âNoâ Direct Implementation
Checklist#
- Define Repository Interface (Domain Layer)
- Create Repository implementation (Data Layer)
- Implement Serverpod Mixin (when Mixin pattern selected)
- Implement DTO to Entity conversion logic
- Return Either<Failure, Success>
- Exception handling (try-catch)
- @LazySingleton annotation
Referencing Agents#
/feature:data- Data Layer Generation/feature:domain- Repository Interface Definition/serverpod:endpoint- Serverpod Endpoint ě°ë