a
This commit is contained in:
527
docs/REVIEWS_ARCHITECTURE.md
Normal file
527
docs/REVIEWS_ARCHITECTURE.md
Normal file
@@ -0,0 +1,527 @@
|
||||
# Reviews Feature - Architecture Diagram
|
||||
|
||||
## Clean Architecture Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ UI Components │ │
|
||||
│ │ - ProductTabsSection (_ReviewsTab) │ │
|
||||
│ │ - WriteReviewPage │ │
|
||||
│ │ - _ReviewItem widget │ │
|
||||
│ └───────────────┬───────────────────────────────────────────┘ │
|
||||
│ │ watches providers │
|
||||
│ ┌───────────────▼───────────────────────────────────────────┐ │
|
||||
│ │ Riverpod Providers (reviews_provider.dart) │ │
|
||||
│ │ - productReviewsProvider(itemId) │ │
|
||||
│ │ - productAverageRatingProvider(itemId) │ │
|
||||
│ │ - productReviewCountProvider(itemId) │ │
|
||||
│ │ - submitReviewProvider │ │
|
||||
│ │ - deleteReviewProvider │ │
|
||||
│ └───────────────┬───────────────────────────────────────────┘ │
|
||||
└──────────────────┼───────────────────────────────────────────────┘
|
||||
│ calls use cases
|
||||
┌──────────────────▼───────────────────────────────────────────────┐
|
||||
│ DOMAIN LAYER │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Use Cases │ │
|
||||
│ │ - GetProductReviews │ │
|
||||
│ │ - SubmitReview │ │
|
||||
│ │ - DeleteReview │ │
|
||||
│ └───────────────┬───────────────────────────────────────────┘ │
|
||||
│ │ depends on │
|
||||
│ ┌───────────────▼───────────────────────────────────────────┐ │
|
||||
│ │ Repository Interface (ReviewsRepository) │ │
|
||||
│ │ - getProductReviews() │ │
|
||||
│ │ - submitReview() │ │
|
||||
│ │ - deleteReview() │ │
|
||||
│ └───────────────┬───────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────▼───────────────────────────────────────────┐ │
|
||||
│ │ Entities │ │
|
||||
│ │ - Review │ │
|
||||
│ │ - id, itemId, rating, comment │ │
|
||||
│ │ - reviewerName, reviewerEmail, reviewDate │ │
|
||||
│ │ - starsRating (computed: rating * 5) │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────┬───────────────────────────────────────────────┘
|
||||
│ implemented by
|
||||
┌──────────────────▼───────────────────────────────────────────────┐
|
||||
│ DATA LAYER │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Repository Implementation │ │
|
||||
│ │ ReviewsRepositoryImpl │ │
|
||||
│ │ - delegates to remote data source │ │
|
||||
│ │ - converts models to entities │ │
|
||||
│ │ - sorts reviews by date │ │
|
||||
│ └───────────────┬───────────────────────────────────────────┘ │
|
||||
│ │ uses │
|
||||
│ ┌───────────────▼───────────────────────────────────────────┐ │
|
||||
│ │ Remote Data Source (ReviewsRemoteDataSourceImpl) │ │
|
||||
│ │ - makes HTTP requests via DioClient │ │
|
||||
│ │ - handles response parsing │ │
|
||||
│ │ - error handling & transformation │ │
|
||||
│ └───────────────┬───────────────────────────────────────────┘ │
|
||||
│ │ returns │
|
||||
│ ┌───────────────▼───────────────────────────────────────────┐ │
|
||||
│ │ Models (ReviewModel) │ │
|
||||
│ │ - fromJson() / toJson() │ │
|
||||
│ │ - toEntity() / fromEntity() │ │
|
||||
│ │ - handles API response format │ │
|
||||
│ └───────────────┬───────────────────────────────────────────┘ │
|
||||
└──────────────────┼───────────────────────────────────────────────┘
|
||||
│ communicates with
|
||||
┌──────────────────▼───────────────────────────────────────────────┐
|
||||
│ EXTERNAL SERVICES │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Frappe/ERPNext API │ │
|
||||
│ │ - POST /api/method/...item_feedback.get_list │ │
|
||||
│ │ - POST /api/method/...item_feedback.update │ │
|
||||
│ │ - POST /api/method/...item_feedback.delete │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow: Fetching Reviews
|
||||
|
||||
```
|
||||
User opens product detail page
|
||||
│
|
||||
▼
|
||||
ProductTabsSection renders
|
||||
│
|
||||
▼
|
||||
_ReviewsTab watches productReviewsProvider(itemId)
|
||||
│
|
||||
▼
|
||||
Provider executes GetProductReviews use case
|
||||
│
|
||||
▼
|
||||
Use case calls repository.getProductReviews()
|
||||
│
|
||||
▼
|
||||
Repository calls remoteDataSource.getProductReviews()
|
||||
│
|
||||
▼
|
||||
Data source makes HTTP POST to API
|
||||
│
|
||||
▼
|
||||
API returns JSON response
|
||||
│
|
||||
▼
|
||||
Data source parses JSON to List<ReviewModel>
|
||||
│
|
||||
▼
|
||||
Repository converts models to List<Review> entities
|
||||
│
|
||||
▼
|
||||
Repository sorts reviews by date (newest first)
|
||||
│
|
||||
▼
|
||||
Provider returns AsyncValue<List<Review>>
|
||||
│
|
||||
▼
|
||||
_ReviewsTab renders reviews with .when()
|
||||
│
|
||||
▼
|
||||
User sees review list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow: Submitting Review
|
||||
|
||||
```
|
||||
User clicks "Write Review" button
|
||||
│
|
||||
▼
|
||||
Navigate to WriteReviewPage
|
||||
│
|
||||
▼
|
||||
User selects stars (1-5) and enters comment
|
||||
│
|
||||
▼
|
||||
User clicks "Submit" button
|
||||
│
|
||||
▼
|
||||
Page validates form:
|
||||
- Rating: 1-5 stars ✓
|
||||
- Comment: 20-1000 chars ✓
|
||||
│
|
||||
▼
|
||||
Convert stars to API rating: apiRating = stars / 5.0
|
||||
│
|
||||
▼
|
||||
Call submitReviewProvider.call()
|
||||
│
|
||||
▼
|
||||
Use case validates:
|
||||
- Rating: 0-1 ✓
|
||||
- Comment: not empty, 20-1000 chars ✓
|
||||
│
|
||||
▼
|
||||
Use case calls repository.submitReview()
|
||||
│
|
||||
▼
|
||||
Repository calls remoteDataSource.submitReview()
|
||||
│
|
||||
▼
|
||||
Data source makes HTTP POST to API
|
||||
│
|
||||
▼
|
||||
API processes request and returns success
|
||||
│
|
||||
▼
|
||||
Data source returns (void)
|
||||
│
|
||||
▼
|
||||
Use case returns (void)
|
||||
│
|
||||
▼
|
||||
Page invalidates productReviewsProvider(itemId)
|
||||
│
|
||||
▼
|
||||
Page shows success SnackBar
|
||||
│
|
||||
▼
|
||||
Page navigates back to product detail
|
||||
│
|
||||
▼
|
||||
ProductTabsSection refreshes (due to invalidate)
|
||||
│
|
||||
▼
|
||||
User sees updated review list with new review
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rating Scale Conversion Flow
|
||||
|
||||
```
|
||||
UI Layer (Stars: 1-5)
|
||||
│
|
||||
│ User selects 4 stars
|
||||
│
|
||||
▼
|
||||
Convert to API: 4 / 5.0 = 0.8
|
||||
│
|
||||
▼
|
||||
Domain Layer (Rating: 0-1)
|
||||
│
|
||||
│ Use case validates: 0 ≤ 0.8 ≤ 1 ✓
|
||||
│
|
||||
▼
|
||||
Data Layer sends: { "rating": 0.8 }
|
||||
│
|
||||
▼
|
||||
API stores: rating = 0.8
|
||||
│
|
||||
▼
|
||||
API returns: { "rating": 0.8 }
|
||||
│
|
||||
▼
|
||||
Data Layer parses: ReviewModel(rating: 0.8)
|
||||
│
|
||||
▼
|
||||
Domain Layer converts: Review(rating: 0.8)
|
||||
│
|
||||
│ Entity computes: starsRating = (0.8 * 5).round() = 4
|
||||
│
|
||||
▼
|
||||
UI Layer displays: ⭐⭐⭐⭐☆
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Flow
|
||||
|
||||
```
|
||||
User action (fetch/submit/delete)
|
||||
│
|
||||
▼
|
||||
Try block starts
|
||||
│
|
||||
▼
|
||||
API call may throw exceptions:
|
||||
│
|
||||
├─► DioException (timeout, connection, etc.)
|
||||
│ │
|
||||
│ ▼
|
||||
│ Caught by _handleDioException()
|
||||
│ │
|
||||
│ ▼
|
||||
│ Converted to app exception:
|
||||
│ - TimeoutException
|
||||
│ - NoInternetException
|
||||
│ - UnauthorizedException
|
||||
│ - ServerException
|
||||
│ - etc.
|
||||
│
|
||||
├─► ParseException (JSON parsing error)
|
||||
│ │
|
||||
│ ▼
|
||||
│ Rethrown as-is
|
||||
│
|
||||
└─► Unknown error
|
||||
│
|
||||
▼
|
||||
UnknownException(originalError, stackTrace)
|
||||
│
|
||||
▼
|
||||
Exception propagates to provider
|
||||
│
|
||||
▼
|
||||
Provider returns AsyncValue.error(exception)
|
||||
│
|
||||
▼
|
||||
UI handles with .when(error: ...)
|
||||
│
|
||||
▼
|
||||
User sees error message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider Dependency Graph
|
||||
|
||||
```
|
||||
dioClientProvider
|
||||
│
|
||||
▼
|
||||
reviewsRemoteDataSourceProvider
|
||||
│
|
||||
▼
|
||||
reviewsRepositoryProvider
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
▼ ▼ ▼
|
||||
getProductReviews submitReview deleteReview
|
||||
Provider Provider Provider
|
||||
│ │ │
|
||||
▼ │ │
|
||||
productReviewsProvider│ │
|
||||
(family) │ │
|
||||
│ │ │
|
||||
┌──────┴──────┐ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
productAverage productReview (used directly
|
||||
RatingProvider CountProvider in UI components)
|
||||
(family) (family)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Interaction Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ProductDetailPage │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ ProductTabsSection │ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ │
|
||||
│ │ │ Specifications│ │ Reviews │ │ (other tab) │ │ │
|
||||
│ │ │ Tab │ │ Tab │ │ │ │ │
|
||||
│ │ └──────────────┘ └──────┬───────┘ └─────────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌─────────────────────────▼───────────────────────┐ │ │
|
||||
│ │ │ _ReviewsTab │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ┌───────────────────────────────────────────┐ │ │ │
|
||||
│ │ │ │ WriteReviewButton │ │ │ │
|
||||
│ │ │ │ (navigates to WriteReviewPage) │ │ │ │
|
||||
│ │ │ └───────────────────────────────────────────┘ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ┌───────────────────────────────────────────┐ │ │ │
|
||||
│ │ │ │ Rating Overview │ │ │ │
|
||||
│ │ │ │ - Average rating (4.8) │ │ │ │
|
||||
│ │ │ │ - Star display (⭐⭐⭐⭐⭐) │ │ │ │
|
||||
│ │ │ │ - Review count (125 đánh giá) │ │ │ │
|
||||
│ │ │ └───────────────────────────────────────────┘ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ┌───────────────────────────────────────────┐ │ │ │
|
||||
│ │ │ │ _ReviewItem (repeated) │ │ │ │
|
||||
│ │ │ │ - Avatar │ │ │ │
|
||||
│ │ │ │ - Reviewer name │ │ │ │
|
||||
│ │ │ │ - Date (2 tuần trước) │ │ │ │
|
||||
│ │ │ │ - Star rating (⭐⭐⭐⭐☆) │ │ │ │
|
||||
│ │ │ │ - Comment text │ │ │ │
|
||||
│ │ │ └───────────────────────────────────────────┘ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
│ clicks "Write Review"
|
||||
▼
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WriteReviewPage │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ Product Info Card (read-only) │ │
|
||||
│ │ - Product image │ │
|
||||
│ │ - Product name │ │
|
||||
│ │ - Product code │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ StarRatingSelector │ │
|
||||
│ │ ☆☆☆☆☆ → ⭐⭐⭐⭐☆ (4 stars selected) │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ Comment TextField │ │
|
||||
│ │ [ ] │ │
|
||||
│ │ [ Multi-line text input ] │ │
|
||||
│ │ [ ] │ │
|
||||
│ │ 50 / 1000 ký tự │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ ReviewGuidelinesCard │ │
|
||||
│ │ - Be honest and fair │ │
|
||||
│ │ - Focus on the product │ │
|
||||
│ │ - etc. │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ [Submit Button] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
│ clicks "Submit"
|
||||
▼
|
||||
|
||||
Validates & submits review
|
||||
│
|
||||
▼
|
||||
Shows success SnackBar
|
||||
│
|
||||
▼
|
||||
Navigates back to ProductDetailPage
|
||||
│
|
||||
▼
|
||||
Reviews refresh automatically
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management Lifecycle
|
||||
|
||||
```
|
||||
1. Initial State (Loading)
|
||||
├─► productReviewsProvider returns AsyncValue.loading()
|
||||
└─► UI shows CircularProgressIndicator
|
||||
|
||||
2. Loading State → Data State
|
||||
├─► API call succeeds
|
||||
├─► Provider returns AsyncValue.data(List<Review>)
|
||||
└─► UI shows review list
|
||||
|
||||
3. Data State → Refresh State (after submit)
|
||||
├─► User submits new review
|
||||
├─► ref.invalidate(productReviewsProvider)
|
||||
├─► Provider state reset to loading
|
||||
├─► API call re-executes
|
||||
└─► UI updates with new data
|
||||
|
||||
4. Error State
|
||||
├─► API call fails
|
||||
├─► Provider returns AsyncValue.error(exception)
|
||||
└─► UI shows error message
|
||||
|
||||
5. Empty State (special case of Data State)
|
||||
├─► API returns empty list
|
||||
├─► Provider returns AsyncValue.data([])
|
||||
└─► UI shows "No reviews yet" message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Caching Strategy
|
||||
|
||||
```
|
||||
Provider State Cache (Riverpod)
|
||||
│
|
||||
├─► Auto-disposed when widget unmounted
|
||||
│ (productReviewsProvider uses AutoDispose)
|
||||
│
|
||||
├─► Cache invalidated on:
|
||||
│ - User submits review
|
||||
│ - User deletes review
|
||||
│ - Manual ref.invalidate() call
|
||||
│
|
||||
└─► Cache refresh:
|
||||
- Pull-to-refresh gesture (future enhancement)
|
||||
- App resume from background (future enhancement)
|
||||
- Time-based expiry (future enhancement)
|
||||
|
||||
HTTP Cache (Dio CacheInterceptor)
|
||||
│
|
||||
├─► Reviews NOT cached (POST requests)
|
||||
│ (only GET requests cached by default)
|
||||
│
|
||||
└─► Future: Implement custom cache policy
|
||||
- Cache reviews for 5 minutes
|
||||
- Invalidate on write operations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
```
|
||||
Unit Tests
|
||||
├─► Domain Layer
|
||||
│ ├─► Use cases
|
||||
│ │ ├─► GetProductReviews
|
||||
│ │ ├─► SubmitReview (validates rating & comment)
|
||||
│ │ └─► DeleteReview
|
||||
│ └─► Entities
|
||||
│ └─► Review (starsRating computation)
|
||||
│
|
||||
├─► Data Layer
|
||||
│ ├─► Models (fromJson, toJson, toEntity)
|
||||
│ ├─► Remote Data Source (API calls, error handling)
|
||||
│ └─► Repository (model-to-entity conversion, sorting)
|
||||
│
|
||||
└─► Presentation Layer
|
||||
└─► Providers (state transformations)
|
||||
|
||||
Widget Tests
|
||||
├─► _ReviewsTab
|
||||
│ ├─► Loading state
|
||||
│ ├─► Empty state
|
||||
│ ├─► Data state
|
||||
│ └─► Error state
|
||||
│
|
||||
├─► _ReviewItem
|
||||
│ ├─► Displays correct data
|
||||
│ ├─► Date formatting
|
||||
│ └─► Star rendering
|
||||
│
|
||||
└─► WriteReviewPage
|
||||
├─► Form validation
|
||||
├─► Submit button states
|
||||
└─► Error messages
|
||||
|
||||
Integration Tests
|
||||
└─► End-to-end flow
|
||||
├─► Fetch reviews
|
||||
├─► Submit review
|
||||
├─► Verify refresh
|
||||
└─► Error scenarios
|
||||
```
|
||||
|
||||
This architecture follows:
|
||||
- ✅ Clean Architecture principles
|
||||
- ✅ SOLID principles
|
||||
- ✅ Dependency Inversion (interfaces in domain layer)
|
||||
- ✅ Single Responsibility (each class has one job)
|
||||
- ✅ Separation of Concerns (UI, business logic, data separate)
|
||||
- ✅ Testability (all layers mockable)
|
||||
Reference in New Issue
Block a user