基于 分离 + CodeGen 映射 的最佳实践开发流程,涵盖从领域建模到 DTO、映射、仓库实现、UseCase、ViewModel、UI,再到各层单元/集成测试和 CI 集成。按此流程,既能保持清晰分层,又能用工具最大化减少样板、提高维护效率。
一、领域层(Domain)
定义实体(Entity)和值对象(Value Object)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class MemberEntity { final String name; final String accountId; final String accountRole; final int accountStatus;
MemberEntity({ required this.name, required this.accountId, required this.accountRole, required this.accountStatus, });
bool get isActive => accountStatus == 1; }
|
定义领域请求/响应模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class MemberUpdateItem { final int? accountStatus; final int? resetPassword; MemberUpdateItem({this.accountStatus, this.resetPassword}); }
class ManagerCreationInfo { final String accountId, name; } class SupportCreationInfo { final int createNum; }
|
定义仓库接口
1 2 3 4 5 6 7 8 9 10
| abstract class MemberRepository { Future<List<MemberEntity>> fetchTeamMembers(int teamId); Future<bool> updateMemberStatus(String accountId, MemberUpdateItem item); Future<List<MemberEntity>> createTeamMembers({ required int teamId, ManagerCreationInfo? manager, SupportCreationInfo? support, }); }
|
二、数据层(Data / Infrastructure)
1. 定义 DTO(只做 JSON 序列化)
在 lib/features/members/data/dto/
下,用 @JsonSerializable(fieldRename: FieldRename.snake)
:
1 2 3 4 5 6 7 8 9 10 11
| @JsonSerializable(fieldRename: FieldRename.snake) class MemberDto { final String name; final String accountId; final String accountRole; final int accountStatus;
factory MemberDto.fromJson(Map<String, dynamic> json) => _$MemberDtoFromJson(json); Map<String, dynamic> toJson() => _$MemberDtoToJson(this); }
|
同理定义 MemberUpdateItemDto
, ManagerCreationInfoDto
, SupportCreationInfoDto
, MemberCreationResponseDto
等。
2. 生成序列化代码
1
| flutter pub run build_runner build --delete-conflicting-outputs
|
3. 写 Mapper Extension(DTO → Entity)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| extension MemberDtoX on MemberDto { MemberEntity toEntity() => MemberEntity( name: name, accountId: accountId, accountRole: accountRole, accountStatus: accountStatus, ); }
extension MemberCreationResponseDtoX on MemberCreationResponseDto { List<MemberEntity> toMemberEntities() { final out = <MemberEntity>[]; if (manager != null) out.add(manager!.toMemberEntity()); if (support != null) out.addAll(support!.toMemberEntities()); return out; } }
|
三、仓库实现(RepositoryImpl)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class MemberRepositoryImpl implements MemberRepository { final MCRequestManager _api; MemberRepositoryImpl(this._api);
@override Future<List<MemberEntity>> fetchTeamMembers(int teamId) async { final res = await _api.getTeamMemberListAsync(teamId); final dtos = (res['members'] as List) .map((e) => MemberDto.fromJson(e)).toList(); return dtos.map((d) => d.toEntity()).toList(); }
@override Future<bool> updateMemberStatus(String accountId, MemberUpdateItem item) async { final dto = MemberUpdateItemDto( accountStatus: item.accountStatus, resetPassword: item.resetPassword); await _api.updateTeamMemberAsync(accountId, dto.toJson()); return true; }
@override Future<List<MemberEntity>> createTeamMembers({ required int teamId, ManagerCreationInfo? manager, SupportCreationInfo? support, }) async { final managerDto = manager != null ? ManagerCreationInfoDto(accountId: manager.accountId, name: manager.name) : null; final supportDto = support != null ? SupportCreationInfoDto(createNum: support.createNum) : null;
final data = await _api.createTeamMemberAsync( teamId, manager: managerDto?.toJson(), support: supportDto?.toJson(), ); final respDto = MemberCreationResponseDto.fromJson(data); return respDto.toMemberEntities(); } }
|
四、应用层(Application)
为每个业务场景写 UseCase。
1. LoadMembersUseCase
1 2 3 4 5 6 7
| class LoadMembersUseCase { final MemberRepository _repo; LoadMembersUseCase(this._repo);
Future<List<MemberEntity>> execute(int teamId) => _repo.fetchTeamMembers(teamId); }
|
2. UpdateMemberStatusUseCase
1 2 3 4 5 6 7
| class UpdateMemberStatusUseCase { final MemberRepository _repo; UpdateMemberStatusUseCase(this._repo);
Future<bool> execute(String accountId, int newStatus) => _repo.updateMemberStatus(accountId, MemberUpdateItem(accountStatus: newStatus)); }
|
3. CreateMembersUseCase
1 2 3 4 5 6 7 8 9 10
| class CreateMembersUseCase { final MemberRepository _repo; CreateMembersUseCase(this._repo);
Future<List<MemberEntity>> execute({ required int teamId, ManagerCreationInfo? manager, SupportCreationInfo? support, }) => _repo.createTeamMembers(teamId: teamId, manager: manager, support: support); }
|
五、表现层(Presentation)
1. State + ViewModel
定义状态类和 StateNotifier
,注入 UseCase。
1 2 3 4 5 6 7 8 9 10 11
| class MembersState { }
class MembersViewModel extends StateNotifier<MembersState> { final LoadMembersUseCase _load; final UpdateMemberStatusUseCase _update; final CreateMembersUseCase _create; MembersViewModel(this._load, this._update, this._create) : super(MembersState()); Future<void> load(int teamId) async { } Future<void> update(String id, int status) async { } Future<void> create({…}) async { } }
|
2. Provider 注册
1 2 3 4 5 6
| final loadUseCaseProvider = Provider((ref) => LoadMembersUseCase(ref.read(repo))); final viewModelProvider = StateNotifierProvider.family<MembersViewModel, MembersState, int>((ref, teamId) => MembersViewModel( ref.read(loadUseCaseProvider), ref.read(updateUseCaseProvider), ref.read(createUseCaseProvider), )..load(teamId));
|
1 2 3 4 5 6 7
| class MembersPage extends ConsumerWidget { Widget build(...) { final state = ref.watch(viewModelProvider(teamId)); ref.listen(viewModelProvider(teamId), (_, s) { if (s.isError) showError(...); }); return state.isLoading ? Loading() : CustomTable(items: state.list, ...); } }
|
六、测试策略
1. 单元测试(UseCase/Mapper/ViewModel)
- UseCase:用 Mockito 生成
MockMemberRepository
,测试 execute
成功/失败分支。
- Mapper:给一个 DTO 测
dto.toEntity()
输出符合预期。
- ViewModel:用
ProviderContainer
override UseCase Provider,驱动 state 变化测试。
2. 集成测试(RepositoryImpl)
在专门的测试环境上跑 MemberRepositoryImpl.fetchTeamMembers
等方法,验证真实 API、JSON 解析、映射流程。
3. CI 集成
- 阶段 1:CI 每次提交
flutter test --coverage
,跑单元测试。
- 阶段 2:定时或手动触发少量集成测试,保证后端兼容。
- 阶段 3:可选 E2E/UI 测试,保证整个用户流畅。
七、工具 & 脚本
- CodeGen:
build_runner
+ json_serializable
+(可选)dart_mappable
- Mock:
mockito
+ build_runner
- Lint & CI:确保
build_runner build
无报错,所有测试覆盖率可观。