The TeaStore consists of five distinct services and a Registry service as shown in Figure 1.
All services communicate with the Registry.
Additionally, the WebUI service issues calls to the Image-Provider, Authentication, Persistence and Recommender services.
The Image provider and Recommender both connect to a provided interface at the Persistence service.
However, this is only necessary on startup (dashed lines).
The Image provider must generate an image for each product, whereas the Recommender needs the current order history as training data.
Once running, only the Authentication and the WebUI access, modify, and create data using the Persistence.
All services communicate via representational state transfer (REST) calls, as REST has established itself as the de-facto industry standard in the micro-service domain.
The services are deployed as web-services on Apache Tomcat.
Yet, the services can be deployed on any Java application server able to run web-services packaged as war files.
As an alternative to deploying the war files, we provide convenient Docker images, containing the entire Tomcat stack.
Each service is packaged in its own war file or Docker image.
The TeaStore uses the client-side load balancer Ribbon, to allow replication of instances of one service type.
Ribbon distributes REST calls among running instances of a service.
Instead of using Netflix Eureka, the TeaStore uses its own registry that supplies service instances with target instances of a specified target specific service type.
To enable this, all running instances register and unregister at the registry, which can be queried for all running instances of a service.
This allows for dynamic addition and removal of service instances during run-time.
Each service also sends heartbeats to the registry.
In case a service is overloaded or crashed and therefore fails to send heartbeat messages, it is removed from the list of available instances.
Subsequently, it will not receive further requests from other services.
This mechanism ensures good error recovery and minimizes the amount of requests sent to unavailable service instances that would otherwise generate request timeouts.
As the TeaStore is primarily a benchmarking and testing application, it is open source and available to instrumentation using available monitoring solutions.
Pre-instrumented Docker images for each service that include the Kieker monitoring application [31], [32] as well as a central trace repository service, are already available.
We choose Kieker, as it requires no source code instrumentation and the instrumentation can be adapated at runtime.
However, as the TeaStore is open source, other monitoring solutions, such as Prometheus or Logstash can also be utilized.
Generally, all requests to the WebUI by a user or load generator are handled in a similar fashion.
The WebUI always retrieves information from the Persistence service.
If all information is available, images for presentation are fetched from the Image provider and embedded into the page.
Finally a Java Server Page (JSP) is compiled and returned.
This behavior ensures that even non-graphical browsers and simple load generators that otherwise would not fetch images from a regular site cause image I/O in the TeaStore, ensuring comparability regardless of the load generation method.
Figure 2 shows the service calls for a user request for a product information page.
After receiving the HTTP request, the WebUI checks the user’s login status by calling the Auth service.
Next, it queries the Persistence for the corresponding product information, based on a unique identifier.
Afterwards, the WebUI requests advertisement options for the current product from the Recommender, which generates a recommendation based on the learned historical order data.
The call to the Recommender takes the current login status into account.
Specifically, a logged in user receives personalized recommendations, whereas an anonymous user is served recommendations based on general item popularity.
Having received all product information, the WebUI queries the image provider to supply a full size image of the product shown in detail and preview images for the recommendations.
The image data is embedded in the HTML response as base-64 encoded strings.
The TeaStore consists of five services, in addition to a registry necessary for service discovery and load balancing.
In case monitoring is enabled, a trace repository service can be used to collect the monitoring traces centrally.
1) WebUI: This service provides the user interface, compiling and serving Java Server Pages (JSPs).
All data, available categories, their products, product recommendations and images, are retrieved from the Image provider and Persistence service instances.
The WebUI service performs preliminary validity checking on user inputs before passing the inputs to the Persistence service.
The WebUI focuses purely on presentation and web front-end operations.
However, the performance of the WebUI depends on the page that has to be rendered as each page contains at least one picture in different formats.
2) Image Provider: The Image provider serves images of different image sizes to the WebUI when being queried.
It optimizes image sizes depending on the target size in the presentation view.
The Image provider uses an internal cache and returns the image with the target size from the cache if available.
If the image is not available for this size, the image provider uses the largest available image for the category or product, scales it to the target size, and enters it into the cache.
It uses a least frequently used cache, reducing resource demand on frequently accessed data.
Through the caching, the response time for an image depends on whether this image is in the cache or not.
This service queries the Persistence service once on start-up to generate all product images with a fixed random seed.
3) Authentication: This service is responsible for the verification of both the login and the session data of a user.
The session data is validated using SHA-512 hashes.
For login verification, the BCrypt algorithm is used.
The session data includes information about the current shopping cart content, the user’s login status and old orders.
Thus, the performance of the hashing for the session data depends on number of articles in the cart and number of old orders.
Furthermore, as all session data is passed to the client, the Authentication itself manages to remain stateless and does not need additional information on startup.
4) Recommender: The Recommender service uses a rating algorithm to recommend products for the user to purchase.
The recommendations are based on items other customers bought, on the products in a user’s current shopping cart, and on the product the user is viewing at the time.
The initial Recommender instance usually uses the automatically generated data-set, as provided by the persistence service at initial startup, for training.
Any additional Recommender instance queries existing Recommender service instances for their training data-set and uses only those purchases for training.
This way, all Recommenders stay coherent, recommending identical products for the same input.
In addition, using identical training input also ensures that different instances of the Recommender service exhibit the same performance characteristics, which is important for many benchmarking and modeling contexts.
The Recommender service queries the Persistence service only once on startup.
For recommending, different algorithm implementations exhibiting different performance behaviors are available.
Next to a fallback algorithm based on overall item-popularity, two variants of Slope One [33] and one order-based nearest-neighbor approach are currently implemented.
One variant of Slope One calculates the predicted rating matrix beforehand and keeps it in the memory (memory-intensive), wheras the other one calculates every row if needed, but discards all results after each recommendation step (CPU-intensive).
5) Persistence: The Persistence service provides access and caching for the store’s relational database.
Products, their categories, purchases, and registered store users are stored in a relational SQL database.
The Persistence service uses caching to decrease response times and to reduce the load on the database itself for improved scalability.
The cache is kept coherent across multiple Persistence service instances.
We use the EclipseLink JPA implementation as a black-box cache.
All data inside the database itself is generated at the first start of the initial persistence instance.
By using a persistence service in separation from the actual database, we improve scalability by providing a replicable caching service.
However, the performance of the database accesses depends on the content in the database that is changed or can be repopulated during the operation of the store.
6) Registry: The registry is not part of the TeaStore application under test but is a necessary support service.
It keeps track of all running service instances, their IP addresses or host names and port numbers under which the services are available.
All service instances send keep-alive messages to the registry after registration.
If a service unregisters or no keep-alive message is received within a fixed time frame, the service is removed from the list of available service instances.
All services can query the list of service instances for a specified service type in order to distribute their outgoing requests between running target instances.
7) TraceRepository: The services are configured with optional Kieker monitoring [31], [32].
With monitoring enabled, each service instance collects information about utilization, response times and call paths.
Collecting these monitoring traces manually is only feasible for small deployments.
Therefore, we offer a central trace repository, which consists of an AMQP server coupled with a graphical web interface.
All service instances send their logs to the AMQP server.
The web interface collects them and makes them available for download.
The trace repository does not only reduce the effort required to acquire the monitoring traces, but also enables online analysis such as online resource demand estimation [34].
Kieker traces are also available for use with tools other than Kieker’s own tooling, as they can be automatically transformed to Open Execution Trace Exchange (OPEN.xtrace) traces, an open source trace format enabling interoperability between software performance engineering approaches [35].
