feat: Update architecture documentation for Data Connect Connectors pattern and remove unused import in staff connector repository implementation
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
|
|
||||||
import '../../domain/repositories/staff_connector_repository.dart';
|
|
||||||
|
|
||||||
/// Implementation of [StaffConnectorRepository].
|
/// Implementation of [StaffConnectorRepository].
|
||||||
///
|
///
|
||||||
/// Fetches staff-related data from the Data Connect backend using
|
/// Fetches staff-related data from the Data Connect backend using
|
||||||
|
|||||||
@@ -85,10 +85,18 @@ graph TD
|
|||||||
### 2.4 Data Connect (`apps/mobile/packages/data_connect`)
|
### 2.4 Data Connect (`apps/mobile/packages/data_connect`)
|
||||||
- **Role**: Interface Adapter for Backend Access (Datasource Layer).
|
- **Role**: Interface Adapter for Backend Access (Datasource Layer).
|
||||||
- **Responsibilities**:
|
- **Responsibilities**:
|
||||||
- Implement Firebase Data Connect connector and service layer.
|
- **Connectors**: Centralized repository implementations for each backend connector (see `03-data-connect-connectors-pattern.md`)
|
||||||
- Map Domain Entities to/from Data Connect generated code.
|
- One connector per backend connector domain (staff, order, user, etc.)
|
||||||
- Handle Firebase exceptions and map to domain failures.
|
- Repository interfaces and use cases defined at domain level
|
||||||
- Provide centralized `DataConnectService` with session management.
|
- Repository implementations query backend and map responses
|
||||||
|
- Implement Firebase Data Connect connector and service layer
|
||||||
|
- Map Domain Entities to/from Data Connect generated code
|
||||||
|
- Handle Firebase exceptions and map to domain failures
|
||||||
|
- Provide centralized `DataConnectService` with session management
|
||||||
|
- **RESTRICTION**:
|
||||||
|
- NO feature-specific logic. Connectors are domain-neutral and reusable.
|
||||||
|
- All queries must follow Clean Architecture (domain → data layers)
|
||||||
|
- See `03-data-connect-connectors-pattern.md` for detailed pattern documentation
|
||||||
|
|
||||||
### 2.5 Design System (`apps/mobile/packages/design_system`)
|
### 2.5 Design System (`apps/mobile/packages/design_system`)
|
||||||
- **Role**: Visual language and component library.
|
- **Role**: Visual language and component library.
|
||||||
@@ -195,3 +203,19 @@ Each app (`staff` and `client`) has different role requirements and session patt
|
|||||||
- **Session Store**: `ClientSessionStore` with `ClientSession(user: User, business: ClientBusinessSession?)`
|
- **Session Store**: `ClientSessionStore` with `ClientSession(user: User, business: ClientBusinessSession?)`
|
||||||
- **Lazy Loading**: `getUserSessionData()` fetches via `getBusinessById()` if session null
|
- **Lazy Loading**: `getUserSessionData()` fetches via `getBusinessById()` if session null
|
||||||
- **Navigation**: On auth → `Modular.to.toClientHome()`, on unauth → `Modular.to.toInitialPage()`
|
- **Navigation**: On auth → `Modular.to.toClientHome()`, on unauth → `Modular.to.toInitialPage()`
|
||||||
|
|
||||||
|
## 7. Data Connect Connectors Pattern
|
||||||
|
|
||||||
|
See **`03-data-connect-connectors-pattern.md`** for comprehensive documentation on:
|
||||||
|
- How connector repositories work
|
||||||
|
- How to add queries to existing connectors
|
||||||
|
- How to create new connectors
|
||||||
|
- Integration patterns with features
|
||||||
|
- Benefits and anti-patterns
|
||||||
|
|
||||||
|
**Quick Reference**:
|
||||||
|
- All backend queries centralized in `apps/mobile/packages/data_connect/lib/src/connectors/`
|
||||||
|
- One connector per backend connector domain (staff, order, user, etc.)
|
||||||
|
- Each connector follows Clean Architecture (domain interfaces + data implementations)
|
||||||
|
- Features use connector repositories through dependency injection
|
||||||
|
- Results in zero query duplication and single source of truth
|
||||||
|
|||||||
273
docs/MOBILE/03-data-connect-connectors-pattern.md
Normal file
273
docs/MOBILE/03-data-connect-connectors-pattern.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
# Data Connect Connectors Pattern
|
||||||
|
|
||||||
|
## 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 backend structure:**
|
||||||
|
```
|
||||||
|
backend/dataconnect/connector/
|
||||||
|
├── staff/
|
||||||
|
├── order/
|
||||||
|
├── user/
|
||||||
|
├── emergency_contact/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Clean Architecture Layers
|
||||||
|
|
||||||
|
Each connector follows Clean Architecture with three layers:
|
||||||
|
|
||||||
|
### Domain Layer (`connectors/{name}/domain/`)
|
||||||
|
|
||||||
|
**Repository Interface:**
|
||||||
|
```dart
|
||||||
|
// staff_connector_repository.dart
|
||||||
|
abstract interface class StaffConnectorRepository {
|
||||||
|
Future<bool> getProfileCompletion();
|
||||||
|
Future<Staff> getStaffById(String id);
|
||||||
|
// ... more queries
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
```dart
|
||||||
|
// 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:**
|
||||||
|
```dart
|
||||||
|
// 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 `DataConnectService` to 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:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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
|
||||||
|
|
||||||
|
```dart
|
||||||
|
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:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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:**
|
||||||
|
```dart
|
||||||
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding New Queries to Existing Connector
|
||||||
|
|
||||||
|
When backend adds `getStaffById()` query to staff connector:
|
||||||
|
|
||||||
|
1. **Add to interface:**
|
||||||
|
```dart
|
||||||
|
abstract interface class StaffConnectorRepository {
|
||||||
|
Future<Staff> getStaffById(String id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Implement in repository:**
|
||||||
|
```dart
|
||||||
|
@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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Use in features:**
|
||||||
|
```dart
|
||||||
|
// 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`):
|
||||||
|
|
||||||
|
1. Create directory: `apps/mobile/packages/data_connect/lib/src/connectors/order/`
|
||||||
|
|
||||||
|
2. Create domain layer with repository interface and use cases
|
||||||
|
|
||||||
|
3. Create data layer with repository implementation
|
||||||
|
|
||||||
|
4. Export from `krow_data_connect.dart`
|
||||||
|
|
||||||
|
5. 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**:
|
||||||
|
- `backend/dataconnect/connector/staff/queries/profile_completion.gql`
|
||||||
|
|
||||||
|
## Future Expansion
|
||||||
|
|
||||||
|
As the app grows, additional connectors will be added:
|
||||||
|
- `order_connector_repository` (queries from `backend/dataconnect/connector/order/`)
|
||||||
|
- `user_connector_repository` (queries from `backend/dataconnect/connector/user/`)
|
||||||
|
- `emergency_contact_connector_repository` (queries from `backend/dataconnect/connector/emergencyContact/`)
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
Each following the same Clean Architecture pattern implemented for Staff Connector.
|
||||||
Reference in New Issue
Block a user