Files
minhthu/lib/features/warehouse/ARCHITECTURE.md
2025-10-28 00:09:46 +07:00

18 KiB

Warehouse Feature - Architecture Diagram

Clean Architecture Layers

┌─────────────────────────────────────────────────────────────────┐
│                      PRESENTATION LAYER                          │
│  (UI, State Management, User Interactions)                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌─────────────────────┐  ┌──────────────────────────────────┐ │
│  │  WarehouseCard      │  │  WarehouseSelectionPage          │ │
│  │  - Shows warehouse  │  │  - Displays warehouse list       │ │
│  │    information      │  │  - Handles user selection        │ │
│  └─────────────────────┘  │  - Pull to refresh               │ │
│                            │  - Loading/Error/Empty states    │ │
│                            └──────────────────────────────────┘ │
│                                        ↓                          │
│                            ┌──────────────────────────────────┐ │
│                            │  WarehouseNotifier               │ │
│                            │  (StateNotifier)                 │ │
│                            │  - loadWarehouses()              │ │
│                            │  - selectWarehouse()             │ │
│                            │  - refresh()                     │ │
│                            └──────────────────────────────────┘ │
│                                        ↓                          │
│                            ┌──────────────────────────────────┐ │
│                            │  WarehouseState                  │ │
│                            │  - warehouses: List              │ │
│                            │  - selectedWarehouse: Warehouse? │ │
│                            │  - isLoading: bool               │ │
│                            │  - error: String?                │ │
│                            └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                                    ↓ uses
┌─────────────────────────────────────────────────────────────────┐
│                        DOMAIN LAYER                              │
│  (Business Logic, Entities, Use Cases)                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  GetWarehousesUseCase                                     │  │
│  │  - Encapsulates business logic for fetching warehouses    │  │
│  │  - Single responsibility                                  │  │
│  │  - Returns Either<Failure, List<WarehouseEntity>>         │  │
│  └──────────────────────────────────────────────────────────┘  │
│                              ↓ uses                               │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  WarehouseRepository (Interface)                          │  │
│  │  + getWarehouses(): Either<Failure, List<Warehouse>>      │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  WarehouseEntity                                          │  │
│  │  - id: int                                                │  │
│  │  - name: String                                           │  │
│  │  - code: String                                           │  │
│  │  - description: String?                                   │  │
│  │  - isNGWareHouse: bool                                    │  │
│  │  - totalCount: int                                        │  │
│  │  + hasItems: bool                                         │  │
│  │  + isNGType: bool                                         │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                                    ↓ implements
┌─────────────────────────────────────────────────────────────────┐
│                         DATA LAYER                               │
│  (API Calls, Data Sources, Models)                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  WarehouseRepositoryImpl                                  │  │
│  │  - Implements WarehouseRepository interface               │  │
│  │  - Coordinates data sources                               │  │
│  │  - Converts exceptions to failures                        │  │
│  │  - Maps models to entities                                │  │
│  └──────────────────────────────────────────────────────────┘  │
│                              ↓ uses                               │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  WarehouseRemoteDataSource (Interface)                    │  │
│  │  + getWarehouses(): Future<List<WarehouseModel>>          │  │
│  └──────────────────────────────────────────────────────────┘  │
│                              ↓                                    │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  WarehouseRemoteDataSourceImpl                            │  │
│  │  - Makes API calls using ApiClient                        │  │
│  │  - Parses ApiResponse wrapper                             │  │
│  │  - Throws ServerException or NetworkException             │  │
│  └──────────────────────────────────────────────────────────┘  │
│                              ↓ uses                               │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  WarehouseModel                                           │  │
│  │  - Extends WarehouseEntity                                │  │
│  │  - Adds JSON serialization (fromJson, toJson)             │  │
│  │  - Maps API fields to entity fields                       │  │
│  └──────────────────────────────────────────────────────────┘  │
│                              ↓ uses                               │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  ApiClient (Core)                                         │  │
│  │  - Dio HTTP client wrapper                                │  │
│  │  - Adds authentication headers                            │  │
│  │  - Handles 401 errors                                     │  │
│  │  - Logging and error handling                             │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Data Flow

1. Loading Warehouses Flow

User Action (Pull to Refresh / Page Load)
    ↓
WarehouseSelectionPage
    ↓ calls
ref.read(warehouseProvider.notifier).loadWarehouses()
    ↓
WarehouseNotifier.loadWarehouses()
    ↓ sets state
state = state.setLoading() → UI shows loading indicator
    ↓ calls
GetWarehousesUseCase.call()
    ↓ calls
WarehouseRepository.getWarehouses()
    ↓ calls
WarehouseRemoteDataSource.getWarehouses()
    ↓ makes HTTP request
ApiClient.get('/warehouses')
    ↓ API Response
{
  "Value": [...],
  "IsSuccess": true,
  "IsFailure": false,
  "Errors": [],
  "ErrorCodes": []
}
    ↓ parse
List<WarehouseModel> from JSON
    ↓ convert
List<WarehouseEntity>
    ↓ wrap
Right(warehouses) or Left(failure)
    ↓ update state
state = state.setSuccess(warehouses)
    ↓
UI rebuilds with warehouse list

2. Error Handling Flow

API Error / Network Error
    ↓
ApiClient throws DioException
    ↓
_handleDioError() converts to custom exception
    ↓
ServerException or NetworkException
    ↓
WarehouseRemoteDataSource catches and rethrows
    ↓
WarehouseRepositoryImpl catches exception
    ↓
Converts to Failure:
  - ServerException → ServerFailure
  - NetworkException → NetworkFailure
    ↓
Returns Left(failure)
    ↓
GetWarehousesUseCase returns Left(failure)
    ↓
WarehouseNotifier receives Left(failure)
    ↓
state = state.setError(failure.message)
    ↓
UI shows error state with retry button

3. Warehouse Selection Flow

User taps on WarehouseCard
    ↓
onTap callback triggered
    ↓
_onWarehouseSelected(warehouse)
    ↓
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse)
    ↓
state = state.setSelectedWarehouse(warehouse)
    ↓
Navigation: context.push('/operations', extra: warehouse)
    ↓
OperationSelectionPage receives warehouse

Dependency Graph

┌─────────────────────────────────────────────────┐
│              Riverpod Providers                 │
├─────────────────────────────────────────────────┤
│                                                 │
│  secureStorageProvider                          │
│         ↓                                       │
│  apiClientProvider                              │
│         ↓                                       │
│  warehouseRemoteDataSourceProvider              │
│         ↓                                       │
│  warehouseRepositoryProvider                    │
│         ↓                                       │
│  getWarehousesUseCaseProvider                   │
│         ↓                                       │
│  warehouseProvider (StateNotifierProvider)      │
│         ↓                                       │
│  WarehouseSelectionPage watches this provider   │
│                                                 │
└─────────────────────────────────────────────────┘

File Dependencies

warehouse_selection_page.dart
    ↓ imports
    - warehouse_entity.dart
    - warehouse_card.dart
    - warehouse_provider.dart (via DI setup)

warehouse_card.dart
    ↓ imports
    - warehouse_entity.dart

warehouse_provider.dart
    ↓ imports
    - warehouse_entity.dart
    - get_warehouses_usecase.dart

get_warehouses_usecase.dart
    ↓ imports
    - warehouse_entity.dart
    - warehouse_repository.dart (interface)

warehouse_repository_impl.dart
    ↓ imports
    - warehouse_entity.dart
    - warehouse_repository.dart (interface)
    - warehouse_remote_datasource.dart

warehouse_remote_datasource.dart
    ↓ imports
    - warehouse_model.dart
    - api_client.dart
    - api_response.dart

warehouse_model.dart
    ↓ imports
    - warehouse_entity.dart

State Transitions

┌──────────────┐
│   Initial    │
│ isLoading: F │
│ error: null  │
│ warehouses:[]│
└──────────────┘
       ↓
  loadWarehouses()
       ↓
┌──────────────┐
│   Loading    │
│ isLoading: T │────────────────┐
│ error: null  │                │
│ warehouses:[]│                │
└──────────────┘                │
       ↓                        │
     Success                  Failure
       ↓                        ↓
┌──────────────┐        ┌──────────────┐
│   Success    │        │    Error     │
│ isLoading: F │        │ isLoading: F │
│ error: null  │        │ error: "..." │
│ warehouses:[…]│        │ warehouses:[]│
└──────────────┘        └──────────────┘
       ↓                        ↓
   Selection                  Retry
       ↓                        ↓
┌──────────────┐          (back to Loading)
│  Selected    │
│ selected: W  │
└──────────────┘

API Response Parsing

Raw API Response (JSON)
    ↓
{
  "Value": [
    {
      "Id": 1,
      "Name": "Warehouse A",
      "Code": "001",
      ...
    }
  ],
  "IsSuccess": true,
  ...
}
    ↓
ApiResponse.fromJson() parses wrapper
    ↓
ApiResponse<List<WarehouseModel>> {
  value: [WarehouseModel, WarehouseModel, ...],
  isSuccess: true,
  isFailure: false,
  errors: [],
  errorCodes: []
}
    ↓
Check isSuccess
    ↓
if (isSuccess && value != null)
    return value!
else
    throw ServerException(errors.first)
    ↓
List<WarehouseModel>
    ↓
map((model) => model.toEntity())
    ↓
List<WarehouseEntity>

Separation of Concerns

Domain Layer

  • No dependencies on Flutter, Dio, or other frameworks
  • Contains pure business logic
  • Defines contracts (repository interfaces)
  • Independent and testable

Data Layer

  • Implements domain contracts
  • Handles external dependencies (API, database)
  • Converts between models and entities
  • Transforms exceptions to failures

Presentation Layer

  • Depends only on domain layer
  • Handles UI rendering and user interactions
  • Manages local state with Riverpod
  • Observes changes and reacts to state updates

Testing Strategy

Unit Tests
├── Domain Layer
│   ├── Test entities (equality, methods)
│   ├── Test use cases (mock repository)
│   └── Verify business logic
├── Data Layer
│   ├── Test models (JSON serialization)
│   ├── Test data sources (mock ApiClient)
│   └── Test repository (mock data source)
└── Presentation Layer
    ├── Test notifier (mock use case)
    └── Test state transitions

Widget Tests
├── Test UI rendering
├── Test user interactions
└── Test state-based UI changes

Integration Tests
├── Test complete flow
└── Test with real dependencies

Benefits of This Architecture

  1. Testability: Each layer can be tested independently with mocks
  2. Maintainability: Changes in one layer don't affect others
  3. Scalability: Easy to add new features following the same pattern
  4. Reusability: Domain entities and use cases can be reused
  5. Separation: Clear boundaries between UI, business logic, and data
  6. Flexibility: Easy to swap implementations (e.g., change API client)

Last Updated: 2025-10-27 Version: 1.0.0