GoogleCloudPlatform/microservices-demo · error

failed to get product #%q

Error message

failed to get product #%q

What it means

prepOrderItems fails when ProductCatalogService.GetProduct errors for a cart item; the product ID is interpolated with %q. Note the underlying error is swallowed (not wrapped), so only the product ID is visible. Commonly caused by cart entries referencing products that no longer exist in the catalog.

Source

Thrown at src/checkoutservice/main.go:346

	}
	return cart.GetItems(), nil
}

func (cs *checkoutService) emptyUserCart(ctx context.Context, userID string) error {
	if _, err := pb.NewCartServiceClient(cs.cartSvcConn).EmptyCart(ctx, &pb.EmptyCartRequest{UserId: userID}); err != nil {
		return fmt.Errorf("failed to empty user cart during checkout: %+v", err)
	}
	return nil
}

func (cs *checkoutService) prepOrderItems(ctx context.Context, items []*pb.CartItem, userCurrency string) ([]*pb.OrderItem, error) {
	out := make([]*pb.OrderItem, len(items))
	cl := pb.NewProductCatalogServiceClient(cs.productCatalogSvcConn)

	for i, item := range items {
		product, err := cl.GetProduct(ctx, &pb.GetProductRequest{Id: item.GetProductId()})
		if err != nil {
			return nil, fmt.Errorf("failed to get product #%q", item.GetProductId())
		}
		price, err := cs.convertCurrency(ctx, product.GetPriceUsd(), userCurrency)
		if err != nil {
			return nil, fmt.Errorf("failed to convert price of %q to %s", item.GetProductId(), userCurrency)
		}
		out[i] = &pb.OrderItem{
			Item: item,
			Cost: price}
	}
	return out, nil
}

func (cs *checkoutService) convertCurrency(ctx context.Context, from *pb.Money, toCurrency string) (*pb.Money, error) {
	result, err := pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(context.TODO(), &pb.CurrencyConversionRequest{
		From:   from,
		ToCode: toCurrency})
	if err != nil {
		return nil, fmt.Errorf("failed to convert currency: %+v", err)

View on GitHub (pinned to 72ba613a05)

Solutions

  1. Verify the product ID exists via ProductCatalogService.ListProducts or the catalog data (products.json).
  2. Check productcatalogservice health and logs.
  3. Wrap the underlying error (%w) so root cause is visible in logs instead of being discarded.
  4. Clean up stale carts: validate cart items against the catalog before checkout, or evict unknown IDs.
  5. Ensure checkoutservice can reach productcatalogservice over gRPC.

Example fix

// before
product, err := cl.GetProduct(ctx, &pb.GetProductRequest{Id: item.GetProductId()})
if err != nil {
	return nil, fmt.Errorf("failed to get product #%q", item.GetProductId())
}
// after
product, err := cl.GetProduct(ctx, &pb.GetProductRequest{Id: item.GetProductId()})
if err != nil {
	return nil, fmt.Errorf("failed to get product #%q: %w", item.GetProductId(), err)
}
Defensive patterns

Strategy: validation

Validate before calling

// validate cart items against the catalog before checkout
for _, item := range cartItems {
	if _, err := catalogClient.GetProduct(ctx, &pb.GetProductRequest{Id: item.GetProductId()}); err != nil {
		// remove item from cart or reject checkout early
	}
}

Type guard

func isValidProductID(id string) bool {
	return id != "" && productCatalogContains(id) // cached catalog ID set
}

Try / catch

product, err := catalogClient.GetProduct(ctx, req)
if err != nil {
	if status.Code(err) == codes.NotFound {
		return fmt.Errorf("product %q no longer exists: %w", id, err)
	}
	return fmt.Errorf("failed to get product %q: %w", id, err)
}

Prevention

When it happens

Trigger: cl.GetProduct(ctx, &pb.GetProductRequest{Id: item.GetProductId()}) returns an error: product ID not found in the catalog, catalog service unavailable, or RPC deadline exceeded.

Common situations: Stale cart data referencing removed/renamed product SKUs; productcatalogservice pod down; catalog data re-seeded with different IDs; typo'd product ID injected by clients.

Related errors


AI-assisted analysis of GoogleCloudPlatform/microservices-demo@72ba613a05 (2026-09-02). Data as JSON: /api/errors/40958b6717fdc918. Report an issue: GitHub.