Review Service

review-service is a Quarkus service that owns customer reviews and ratings for products and stores in ShopSTAR3. It exposes a GraphQL subgraph via Apollo Federation for storefront read access, a REST API for review submission and helpfulness voting, and a REST admin API for moderation and settings. Storage is PostgreSQL with store_id as a discriminator column.

Data Model#

Reviews#

reviewsreview_id UUID PK, store_id, target_type ENUM (PRODUCT / STORE), target_id UUID, customer_id, order_id (set for verified purchaser submissions), rating SMALLINT (1–5), title, body, status ENUM (PENDING / APPROVED / REJECTED), rejection_reason, moderated_by UUID (staff ID; null = auto-approved or pending), moderated_at, submitted_at, updated_at.

Media Attachments#

review_mediamedia_id UUID PK, review_id FK, store_id, media_type ENUM (IMAGE / VIDEO), url TEXT (object storage reference), position.

review-service stores references only. File upload and storage are handled by the content sandbox or the platform’s object storage layer.

Helpfulness Votes#

review_votesvote_id UUID PK, review_id FK, store_id, customer_id, is_helpful BOOLEAN, voted_at. UNIQUE on (review_id, customer_id) — one vote per customer per review.

Aggregate Ratings#

rating_aggregatesaggregate_id UUID PK, store_id, target_type, target_id UUID, avg_rating NUMERIC(3,2), total_count, count_1 through count_5 INT (star distribution), updated_at. UNIQUE on (store_id, target_type, target_id).

Aggregates are recomputed atomically in the same transaction as any review status change that affects the APPROVED set (approval, rejection of an approved review, or GDPR deletion).

Review Settings (per store)#

review_settingsstore_id UUID PK, eligibility ENUM (OPEN / VERIFIED_PURCHASER / REGISTERED_CUSTOMER), auto_approve BOOLEAN, moderation_ai_enabled BOOLEAN, min_display_count INT (minimum approved reviews before the aggregate is shown on storefront), show_unverified BOOLEAN, default_sort_order ENUM (NEWEST / HIGHEST_RATED / MOST_HELPFUL), max_media_per_review INT.

Submission Eligibility#

When a review is submitted, the configured eligibility rule is enforced before the record is inserted:

EligibilityCheck
OPENNo check — any request with a valid store context is accepted
REGISTERED_CUSTOMERcustomer-service GetCustomer(customerId, storeId) must return an active customer
VERIFIED_PURCHASERRedis cache checked first (verified_purchase:{storeId}:{customerId}:{productId}); on miss, order-service VerifyPurchase is called via gRPC

Verified purchaser eligibility is cached in Redis (TTL = 2 years) when order.fulfilled is consumed, making the hot path a Redis lookup rather than a cross-service call.

Moderation#

Reviews are inserted with status = PENDING unless auto_approve = true. When moderation_ai_enabled = true and auto_approve = false, an asynchronous AI screening call flags low-risk reviews for auto-approval and surfaces suspicious ones for manual review.

State machine:

  • PENDING → APPROVED — rating aggregate updated
  • PENDING → REJECTED — rejection reason stored
  • APPROVED → REJECTED — aggregate contribution reversed, reason stored

GraphQL Subgraph#

review-service joins the Apollo Federation graph and extends the Product and Store types:

extend type Product @key(fields: "id") {
  id: ID! @external
  reviews(first: Int, after: String, sort: ReviewSort): ReviewConnection!
  ratingAggregate: RatingAggregate
}

type Review {
  id: ID!
  rating: Int!
  title: String
  body: String
  customer: ReviewAuthor
  isVerifiedPurchaser: Boolean!
  helpfulCount: Int!
  media: [ReviewMedia!]!
  submittedAt: DateTime!
}

type RatingAggregate {
  average: Float
  totalCount: Int!
  distribution: [RatingBucket!]!
}

Only APPROVED reviews are returned by the subgraph. The min_display_count setting suppresses the aggregate until sufficient approved reviews exist.

REST Interfaces#

Storefront#

MethodEndpointPurpose
POST/reviewsSubmit a review (authenticated customer)
POST/reviews/{id}/votesSubmit a helpfulness vote

Admin#

MethodEndpointPurpose
GET/reviews?status=PENDINGModeration queue
PATCH/reviews/{id}Approve or reject with reason
GET/reviewsFull list with filters
DELETE/reviews/{id}Hard delete (GDPR erasure)
GET/PUT/settingsStore-level review settings

Kafka Topics#

Consumed#

TopicAction
order.fulfilledCache verified purchaser eligibility in Redis per product
customer.anonymizedAnonymize or delete reviews for the customer (GDPR)

Published#

TopicPayload
review.submittedreviewId, storeId, targetType, targetId, rating
review.approvedreviewId, storeId, targetType, targetId
review.rejectedreviewId, storeId, reason

review.approved is consumed by catalog-service to update its own product rating cache for storefront display.

Vault Configuration#

Key pathPurpose
ss3/kv/review-service/db.urlPostgreSQL JDBC URL
ss3/kv/review-service/db.usernameDatabase username
ss3/kv/review-service/db.passwordDatabase password
ss3/kv/shared/Shared Kafka bootstrap, OTel config, Redis URL