8.4 KiB
Data Connect Connectors Pattern
Warning
This document describes the legacy V1 Data Connect connector pattern. For current backend work, use the V2 unified API docs under
docs/BACKEND/API_GUIDES/V2/.
Overview
This document describes the Data Connect Connectors pattern implemented in the KROW mobile app. This pattern centralizes all backend query logic by mirroring backend connector structure in the mobile data layer.
Problem Statement
Without Connectors Pattern:
- Each feature creates its own repository implementation
- Multiple features query the same backend connector → duplication
- When backend queries change, updates needed in multiple places
- No reusability across features
Example Problem:
staff_main/
└── data/repositories/profile_completion_repository_impl.dart ← queries staff connector
profile/
└── data/repositories/profile_repository_impl.dart ← also queries staff connector
onboarding/
└── data/repositories/personal_info_repository_impl.dart ← also queries staff connector
Solution: Connectors in Data Connect Package
All backend connector queries are implemented once in a centralized location, following the backend structure.
Structure
apps/mobile/packages/data_connect/lib/src/connectors/
├── staff/
│ ├── domain/
│ │ ├── repositories/
│ │ │ └── staff_connector_repository.dart (interface)
│ │ └── usecases/
│ │ └── get_profile_completion_usecase.dart
│ └── data/
│ └── repositories/
│ └── staff_connector_repository_impl.dart (implementation)
├── order/
├── user/
├── emergency_contact/
└── ...
Maps to legacy backend structure:
legacy/dataconnect-v1/connector/
├── staff/
├── order/
├── user/
├── emergency_contact/
└── ...
Clean Architecture Layers
Each connector follows Clean Architecture with three layers:
Domain Layer (connectors/{name}/domain/)
Repository Interface:
// staff_connector_repository.dart
abstract interface class StaffConnectorRepository {
Future<bool> getProfileCompletion();
Future<Staff> getStaffById(String id);
// ... more queries
}
Use Cases:
// get_profile_completion_usecase.dart
class GetProfileCompletionUseCase {
GetProfileCompletionUseCase({required StaffConnectorRepository repository});
Future<bool> call() => _repository.getProfileCompletion();
}
Characteristics:
- Pure Dart, no framework dependencies
- Stable, business-focused contracts
- One interface per connector
- One use case per query or related query group
Data Layer (connectors/{name}/data/)
Repository Implementation:
// staff_connector_repository_impl.dart
class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
final DataConnectService _service;
@override
Future<bool> getProfileCompletion() async {
return _service.run(() async {
final staffId = await _service.getStaffId();
final response = await _service.connector
.getStaffProfileCompletion(id: staffId)
.execute();
return _isProfileComplete(response);
});
}
}
Characteristics:
- Implements domain repository interface
- Uses
DataConnectServiceto execute queries - Maps backend response types to domain models
- Contains mapping/transformation logic only
- Handles type safety with generated Data Connect types
Integration Pattern
Step 1: Feature Needs Data
Feature (e.g., staff_main) needs profile completion status.
Step 2: Use Connector Repository
Instead of creating a local repository, feature uses the connector:
// staff_main_module.dart
class StaffMainModule extends Module {
@override
void binds(Injector i) {
// Register connector repository from data_connect
i.addSingleton<StaffConnectorRepository>(
StaffConnectorRepositoryImpl.new,
);
// Feature creates its own use case wrapper if needed
i.addSingleton(
() => GetProfileCompletionUseCase(
repository: i.get<StaffConnectorRepository>(),
),
);
// BLoC uses the use case
i.addSingleton(
() => StaffMainCubit(
getProfileCompletionUsecase: i.get<GetProfileCompletionUseCase>(),
),
);
}
}
Step 3: BLoC Uses It
class StaffMainCubit extends Cubit<StaffMainState> {
StaffMainCubit({required GetProfileCompletionUseCase usecase}) {
_loadProfileCompletion();
}
Future<void> _loadProfileCompletion() async {
final isComplete = await _getProfileCompletionUsecase();
emit(state.copyWith(isProfileComplete: isComplete));
}
}
Export Pattern
Connectors are exported from krow_data_connect for easy access:
// lib/krow_data_connect.dart
export 'src/connectors/staff/domain/repositories/staff_connector_repository.dart';
export 'src/connectors/staff/domain/usecases/get_profile_completion_usecase.dart';
export 'src/connectors/staff/data/repositories/staff_connector_repository_impl.dart';
Features import:
import 'package:krow_data_connect/krow_data_connect.dart';
Adding New Queries to Existing Connector
When backend adds getStaffById() query to staff connector:
-
Add to interface:
abstract interface class StaffConnectorRepository { Future<Staff> getStaffById(String id); } -
Implement in repository:
@override Future<Staff> getStaffById(String id) async { return _service.run(() async { final response = await _service.connector .getStaffById(id: id) .execute(); return _mapToStaff(response.data.staff); }); } -
Use in features:
// Any feature can now use it final staff = await i.get<StaffConnectorRepository>().getStaffById(id);
Adding New Connector
When backend adds new connector (e.g., order):
-
Create directory:
apps/mobile/packages/data_connect/lib/src/connectors/order/ -
Create domain layer with repository interface and use cases
-
Create data layer with repository implementation
-
Export from
krow_data_connect.dart -
Features can immediately start using it
Benefits
✅ No Duplication - Query implemented once, used by many features
✅ Single Source of Truth - Backend change → update one place
✅ Clean Separation - Connector logic separate from feature logic
✅ Reusability - Any feature can request any connector data
✅ Testability - Mock the connector repo to test features
✅ Scalability - Easy to add new connectors as backend grows
✅ Mirrors Backend - Mobile structure mirrors backend structure
Anti-Patterns
❌ DON'T: Implement query logic in feature repository
❌ DON'T: Duplicate queries across multiple repositories
❌ DON'T: Put mapping logic in features
❌ DON'T: Call DataConnectService directly from BLoCs
DO: Use connector repositories through use cases in features.
Current Implementation
Staff Connector
Location: apps/mobile/packages/data_connect/lib/src/connectors/staff/
Available Queries:
getProfileCompletion()- Returns bool indicating if profile is complete- Checks: personal info, emergency contacts, tax forms, experience (skills/industries)
Used By:
staff_main- Guards bottom nav items requiring profile completion
Backend Queries Used:
legacy/dataconnect-v1/connector/staff/queries/profile_completion.gql
Shifts Connector
Location: apps/mobile/packages/data_connect/lib/src/connectors/shifts/
Available Queries:
listShiftRolesByVendorId()- Fetches shifts for a specific vendor with status mappingapplyForShifts()- Handles shift application with error tracking
Backend Queries Used:
legacy/dataconnect-v1/connector/shifts/queries/list_shift_roles_by_vendor.gqllegacy/dataconnect-v1/connector/shifts/mutations/apply_for_shifts.gql
Future Expansion
As the app grows, additional connectors will be added:
order_connector_repository(queries fromlegacy/dataconnect-v1/connector/order/)user_connector_repository(queries fromlegacy/dataconnect-v1/connector/user/)emergency_contact_connector_repository(queries fromlegacy/dataconnect-v1/connector/emergencyContact/)- etc.
Each following the same Clean Architecture pattern implemented for Staff Connector.