Files
worker/docs/REVIEWS_ARCHITECTURE.md
Phuoc Nguyen 192c322816 a
2025-11-17 17:56:34 +07:00

25 KiB

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)