diff --git a/src/adservice/src/main/java/hipstershop/AdService.java b/src/adservice/src/main/java/hipstershop/AdService.java index 75ce76f..dfb1e40 100644 --- a/src/adservice/src/main/java/hipstershop/AdService.java +++ b/src/adservice/src/main/java/hipstershop/AdService.java @@ -17,6 +17,8 @@ package hipstershop; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.Iterables; import hipstershop.Demo.Ad; import hipstershop.Demo.AdRequest; import hipstershop.Demo.AdResponse; @@ -42,6 +44,7 @@ import io.opencensus.trace.Tracer; import io.opencensus.trace.Tracing; import io.opencensus.trace.samplers.Samplers; import java.io.IOException; +import java.util.Collection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -109,8 +112,8 @@ public class AdService { try (Scope scope = spanBuilder.startScopedSpan()) { Span span = tracer.getCurrentSpan(); span.putAttribute("method", AttributeValue.stringAttributeValue("getAds")); - List ads = new ArrayList<>(); - logger.info("received ad request (context_words=" + req.getContextKeysCount() + ")"); + List allAds = new ArrayList<>(); + logger.info("received ad request (context_words=" + req.getContextKeysList() + ")"); if (req.getContextKeysCount() > 0) { span.addAnnotation( "Constructing Ads using context", @@ -120,21 +123,19 @@ public class AdService { "Context Keys length", AttributeValue.longAttributeValue(req.getContextKeysCount()))); for (int i = 0; i < req.getContextKeysCount(); i++) { - Ad ad = service.getAdsByKey(req.getContextKeys(i)); - if (ad != null) { - ads.add(ad); - } + Collection ads = service.getAdsByCategory(req.getContextKeys(i)); + allAds.addAll(ads); } } else { span.addAnnotation("No Context provided. Constructing random Ads."); - ads = service.getDefaultAds(); + allAds = service.getRandomAds(); } - if (ads.isEmpty()) { - // Serve default ads. + if (allAds.isEmpty()) { + // Serve random ads. span.addAnnotation("No Ads found based on context. Constructing random Ads."); - ads = service.getDefaultAds(); + allAds = service.getRandomAds(); } - AdResponse reply = AdResponse.newBuilder().addAllAds(ads).build(); + AdResponse reply = AdResponse.newBuilder().addAllAds(allAds).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } catch (StatusRuntimeException e) { @@ -144,18 +145,19 @@ public class AdService { } } - static final HashMap cacheMap = new HashMap(); + static final ImmutableListMultimap adsMap = createAdsMap(); - Ad getAdsByKey(String key) { - return cacheMap.get(key); + Collection getAdsByCategory(String category) { + return adsMap.get(category); } + private static final Random random = new Random(); - public List getDefaultAds() { + public List getRandomAds() { List ads = new ArrayList<>(MAX_ADS_TO_SERVE); - Object[] keys = cacheMap.keySet().toArray(); + Collection allAds = adsMap.values(); for (int i=0; i createAdsMap() { + Ad camera = Ad.newBuilder().setRedirectUrl("/product/2ZYFJ3GM2N") + .setText("Film camera for sale. 50% off.").build(); + Ad lens = Ad.newBuilder().setRedirectUrl("/product/66VCHSJNUP") + .setText("Vintage camera lens for sale. 20% off.").build(); + Ad recordPlayer = Ad.newBuilder().setRedirectUrl("/product/0PUK6V6EV0") + .setText("Vintage record player for sale. 30% off.").build(); + Ad bike = Ad.newBuilder().setRedirectUrl("/product/9SIQT8TOJO") + .setText("City Bike for sale. 10% off.").build(); + Ad baristaKit = Ad.newBuilder().setRedirectUrl("/product/1YMWWN1N4O") + .setText("Home Barista kitchen kit for sale. Buy one, get second kit for free").build(); + Ad airPlant = Ad.newBuilder().setRedirectUrl("/product/6E92ZMYYFZ") + .setText("Air plants for sale. Buy two, get third one for free").build(); + Ad terrarium = Ad.newBuilder().setRedirectUrl("/product/L9ECAV7KIM") + .setText("Terrarium for sale. Buy one, get second one for free").build(); + return ImmutableListMultimap.builder() + .putAll("photography", camera, lens) + .putAll("vintage", camera, lens, recordPlayer) + .put("cycling", bike) + .put("cookware", baristaKit) + .putAll("gardening", airPlant, terrarium) + .build(); } public static void initStackdriver() { @@ -222,8 +238,6 @@ public class AdService { public static void main(String[] args) throws IOException, InterruptedException { // Add final keyword to pass checkStyle. - initializeAds(); - new Thread( new Runnable() { public void run(){ initStackdriver(); diff --git a/src/frontend/handlers.go b/src/frontend/handlers.go index 10bc2ff..d7e8765 100644 --- a/src/frontend/handlers.go +++ b/src/frontend/handlers.go @@ -80,7 +80,7 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) { "products": ps, "cart_size": len(cart), "banner_color": os.Getenv("BANNER_COLOR"), // illustrates canary deployments - "ad": fe.chooseAd(r.Context(), log), + "ad": fe.chooseAd(r.Context(), []string{}, log), }); err != nil { log.Error(err) } @@ -133,7 +133,7 @@ func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request) if err := templates.ExecuteTemplate(w, "product", map[string]interface{}{ "session_id": sessionID(r), "request_id": r.Context().Value(ctxKeyRequestID{}), - "ad": fe.chooseAd(r.Context(), log), + "ad": fe.chooseAd(r.Context(), p.Categories, log), "user_currency": currentCurrency(r), "currencies": currencies, "product": product, @@ -346,8 +346,8 @@ func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Requ // chooseAd queries for advertisements available and randomly chooses one, if // available. It ignores the error retrieving the ad since it is not critical. -func (fe *frontendServer) chooseAd(ctx context.Context, log logrus.FieldLogger) *pb.Ad { - ads, err := fe.getAd(ctx) +func (fe *frontendServer) chooseAd(ctx context.Context, ctxKeys []string, log logrus.FieldLogger) *pb.Ad { + ads, err := fe.getAd(ctx, ctxKeys) if err != nil { log.WithField("error", err).Warn("failed to retrieve ads") return nil diff --git a/src/frontend/rpc.go b/src/frontend/rpc.go index 05c99b2..a1dd313 100644 --- a/src/frontend/rpc.go +++ b/src/frontend/rpc.go @@ -116,12 +116,12 @@ func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, return out, err } -func (fe *frontendServer) getAd(ctx context.Context) ([]*pb.Ad, error) { +func (fe *frontendServer) getAd(ctx context.Context, ctxKeys []string) ([]*pb.Ad, error) { ctx, cancel := context.WithTimeout(ctx, time.Millisecond*100) defer cancel() resp, err := pb.NewAdServiceClient(fe.adSvcConn).GetAds(ctx, &pb.AdRequest{ - ContextKeys: nil, + ContextKeys: ctxKeys, }) return resp.GetAds(), errors.Wrap(err, "failed to get ads") } diff --git a/src/productcatalogservice/products.json b/src/productcatalogservice/products.json index be0825b..f41b2d9 100644 --- a/src/productcatalogservice/products.json +++ b/src/productcatalogservice/products.json @@ -9,7 +9,8 @@ "currencyCode": "USD", "units": 67, "nanos": 990000000 - } + }, + "categories": ["vintage"] }, { "id": "66VCHSJNUP", @@ -20,7 +21,8 @@ "currencyCode": "USD", "units": 12, "nanos": 490000000 - } + }, + "categories": ["photography", "vintage"] }, { "id": "1YMWWN1N4O", @@ -30,7 +32,8 @@ "priceUsd": { "currencyCode": "USD", "units": 124 - } + }, + "categories": ["cookware"] }, { "id": "L9ECAV7KIM", @@ -41,7 +44,8 @@ "currencyCode": "USD", "units": 36, "nanos": 450000000 - } + }, + "categories": ["gardening"] }, { "id": "2ZYFJ3GM2N", @@ -51,7 +55,8 @@ "priceUsd": { "currencyCode": "USD", "units": 2245 - } + }, + "categories": ["photography", "vintage"] }, { "id": "0PUK6V6EV0", @@ -62,7 +67,8 @@ "currencyCode": "USD", "units": 65, "nanos": 500000000 - } + }, + "categories": ["music", "vintage"] }, { "id": "LS4PSXUNUM", @@ -73,7 +79,8 @@ "currencyCode": "USD", "units": 24, "nanos": 330000000 - } + }, + "categories": ["cookware"] }, { "id": "9SIQT8TOJO", @@ -84,7 +91,8 @@ "currencyCode": "USD", "units": 789, "nanos": 500000000 - } + }, + "categories": ["cycling"] }, { "id": "6E92ZMYYFZ", @@ -95,7 +103,8 @@ "currencyCode": "USD", "units": 12, "nanos": 300000000 - } + }, + "categories": ["gardening"] } ] }