From 860a35de9be667ff36788d3df09ed4a96b9290fd Mon Sep 17 00:00:00 2001 From: Juliano Costa Date: Wed, 14 Sep 2022 07:02:23 +0200 Subject: [PATCH] Add PHP quote service (#345) --- .env | 3 + .gitignore | 4 +- CHANGELOG.md | 2 + docker-compose.yml | 22 +++++ docs/manual_span_attributes.md | 7 ++ docs/service_table.md | 1 + docs/trace_service_features.md | 1 + src/quoteservice/.dockerignore | 4 + src/quoteservice/.gitignore | 7 ++ src/quoteservice/Dockerfile | 26 ++++++ src/quoteservice/README.md | 28 ++++++ src/quoteservice/app/dependencies.php | 29 ++++++ src/quoteservice/app/routes.php | 54 ++++++++++++ src/quoteservice/app/settings.php | 25 ++++++ src/quoteservice/composer.json | 31 +++++++ src/quoteservice/public/index.php | 88 +++++++++++++++++++ .../src/Application/Settings/Settings.php | 22 +++++ .../Settings/SettingsInterface.php | 13 +++ 18 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 src/quoteservice/.dockerignore create mode 100644 src/quoteservice/.gitignore create mode 100644 src/quoteservice/Dockerfile create mode 100644 src/quoteservice/README.md create mode 100644 src/quoteservice/app/dependencies.php create mode 100644 src/quoteservice/app/routes.php create mode 100644 src/quoteservice/app/settings.php create mode 100644 src/quoteservice/composer.json create mode 100644 src/quoteservice/public/index.php create mode 100644 src/quoteservice/src/Application/Settings/Settings.php create mode 100644 src/quoteservice/src/Application/Settings/SettingsInterface.php diff --git a/.env b/.env index 28304ad02a..28aad2f959 100644 --- a/.env +++ b/.env @@ -37,6 +37,9 @@ PAYMENT_SERVICE_ADDR=paymentservice:${PAYMENT_SERVICE_PORT} PRODUCT_CATALOG_SERVICE_PORT=3550 PRODUCT_CATALOG_SERVICE_ADDR=productcatalogservice:${PRODUCT_CATALOG_SERVICE_PORT} +QUOTE_SERVICE_PORT=8090 +QUOTE_SERVICE_ADDR=quoteservice:${QUOTE_SERVICE_PORT} + RECOMMENDATION_SERVICE_PORT=9001 RECOMMENDATION_SERVICE_ADDR=recommendationservice:${RECOMMENDATION_SERVICE_PORT} diff --git a/.gitignore b/.gitignore index 43b7af7dde..3bba855e1c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,6 @@ build src/frontend/protos next-env.d.ts src/frontend/cypress/videos -src/frontend/cypress/screenshots \ No newline at end of file +src/frontend/cypress/screenshots +vendor/ +composer.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index b5fd2a7380..3dd4e85713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,5 +89,7 @@ significant modifications will be credited to OpenTelemetry Authors. ([#331](https://github.com/open-telemetry/opentelemetry-demo/pull/331)) * Add span events to shipping service ([#344](https://github.com/open-telemetry/opentelemetry-demo/pull/344)) +* Add PHP quote service +([#345](https://github.com/open-telemetry/opentelemetry-demo/pull/345)) * Improve initial run time, without a build ([#362](https://github.com/open-telemetry/opentelemetry-demo/pull/362)) diff --git a/docker-compose.yml b/docker-compose.yml index b4766f7b51..a85d457074 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -278,6 +278,27 @@ services: - otelcol logging: *logging + quoteservice: + image: ${IMAGE_NAME}:${IMAGE_VERSION}-quoteservice + container_name: quoteservice + build: + context: ./ + dockerfile: ./src/quoteservice/Dockerfile + ports: + - "${QUOTE_SERVICE_PORT}" + environment: + # OTEL_EXPORTER_OTLP_TRACES_ENDPOINT # Not working for PHP + - QUOTE_SERVICE_PORT + - OTEL_SERVICE_NAME=quoteservice + - OTEL_EXPORTER_OTLP_ENDPOINT=otelcol:4317 + - OTEL_TRACES_SAMPLER=parentbased_always_on + - OTEL_TRACES_EXPORTER=otlp + - OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc + - OTEL_PHP_TRACES_PROCESSOR=simple + depends_on: + - otelcol + logging: *logging + # RecommendationService recommendationservice: image: ${IMAGE_NAME}:${IMAGE_VERSION}-recommendationservice @@ -313,6 +334,7 @@ services: - "${SHIPPING_SERVICE_PORT}" environment: - SHIPPING_SERVICE_PORT + - QUOTE_SERVICE_ADDR - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT - OTEL_SERVICE_NAME=shippingservice depends_on: diff --git a/docs/manual_span_attributes.md b/docs/manual_span_attributes.md index f70c6b7a5d..ef4114012a 100644 --- a/docs/manual_span_attributes.md +++ b/docs/manual_span_attributes.md @@ -96,6 +96,13 @@ This document contains the list of manual Span Attributes used throughout the de | `app.products.count` | number | Number of products in catalog | | `app.products_search.count` | number | Number of products returned in search | +## QuoteService + +| Name | Type | Description | +|-----------------------------|--------|----------------------| +| `app.quote.items.count` | number | Total items to ship | +| `app.quote.cost.total` | number | Total shipping quote | + ## RecommendationService | Name | Type | Description | diff --git a/docs/service_table.md b/docs/service_table.md index b5c6295880..d6a23a93a7 100644 --- a/docs/service_table.md +++ b/docs/service_table.md @@ -14,5 +14,6 @@ View [Service Graph](../README.md#architecture) to visualize request flows. | [loadgenerator](../src/loadgenerator/README.md) | Python/Locust | Continuously sends requests imitating realistic user shopping flows to the frontend. | | [paymentservice](../src/paymentservice/README.md) | JavaScript | Charges the given credit card info (mock) with the given amount and returns a transaction ID. | | [productcatalogservice](../src/productcatalogservice/README.md) | Go | Provides the list of products from a JSON file and ability to search products and get individual products. | +| [quoteservice](../src/quoteservice/README.md) | PHP | Calculates the shipping costs, based on the number of items to be shipped. | | [recommendationservice](../src/recommendationservice/README.md) | Python | Recommends other products based on what's given in the cart. | | [shippingservice](../src/shippingservice/README.md) | Rust | Gives shipping cost estimates based on the shopping cart. Ships items to the given address (mock). | diff --git a/docs/trace_service_features.md b/docs/trace_service_features.md index 15793e5aa5..59d857fc3d 100644 --- a/docs/trace_service_features.md +++ b/docs/trace_service_features.md @@ -17,5 +17,6 @@ Emoji Legend | Frontend | JavaScript | :100: | :100: | :100: | :no_bell: | :100: | :100: | | Payment | JavaScript | :100: | :100: | :100: | :no_bell: | :no_bell: | :100: | | Product Catalog | Go | :100: | :construction: | :100: | :no_bell: | :no_bell: | :no_bell: | +| Quote Service | PHP | :100: | :100: | :100: | :no_bell: | :no_bell: | :no_bell: | | Recommendation | Python | :100: | :100: | :100: | :no_bell: | :no_bell: | :no_bell: | | Shipping | Rust | :no_bell: | :100: | :100: | :100: | :no_bell: | :no_bell: | diff --git a/src/quoteservice/.dockerignore b/src/quoteservice/.dockerignore new file mode 100644 index 0000000000..6a2aedc09f --- /dev/null +++ b/src/quoteservice/.dockerignore @@ -0,0 +1,4 @@ +.dockerignore +.idea +Dockerfile +vendor diff --git a/src/quoteservice/.gitignore b/src/quoteservice/.gitignore new file mode 100644 index 0000000000..63066a6e3f --- /dev/null +++ b/src/quoteservice/.gitignore @@ -0,0 +1,7 @@ +.idea/ +.vscode/ +/coverage/ +/vendor/ +/logs/* +!/logs/README.md +.phpunit.result.cache diff --git a/src/quoteservice/Dockerfile b/src/quoteservice/Dockerfile new file mode 100644 index 0000000000..94704bf82a --- /dev/null +++ b/src/quoteservice/Dockerfile @@ -0,0 +1,26 @@ +FROM composer:2.4.1 AS build + +WORKDIR /tmp/ +COPY ./src/quoteservice/composer.json . + +RUN composer install \ + --ignore-platform-reqs \ + --no-interaction \ + --no-plugins \ + --no-scripts \ + --prefer-dist + +FROM php:8.1-cli + +# install GRPC (required for the OTel exporter) +RUN apt-get -y update && apt install -y --no-install-recommends zlib1g-dev && \ + pecl install grpc protobuf && \ + docker-php-ext-enable grpc protobuf + +WORKDIR /var/www +COPY --from=build /tmp/vendor/ /var/www/vendor/ +COPY ./src/quoteservice/ /var/www + +EXPOSE ${QUOTE_SERVICE_PORT} + +ENTRYPOINT php -S 0.0.0.0:${QUOTE_SERVICE_PORT} -t public diff --git a/src/quoteservice/README.md b/src/quoteservice/README.md new file mode 100644 index 0000000000..19ed583dc1 --- /dev/null +++ b/src/quoteservice/README.md @@ -0,0 +1,28 @@ +# Quote Service + +The Quote Service calculates the shipping costs, +based on the number of items to be shipped. + +It is a PHP based service. + +## Build the service + +To build the quote service, run the following from root directory +of opentelemetry-demo + +```sh +docker compose build quoteservice +``` + +## Run the service + +Execute the below command to run the service. + +```sh +docker compose up quoteservice +``` + +In order to get traffic into the service you have to deploy +the whole opentelemetry-demo. + +Please follow the root README to do so. diff --git a/src/quoteservice/app/dependencies.php b/src/quoteservice/app/dependencies.php new file mode 100644 index 0000000000..9a948b5515 --- /dev/null +++ b/src/quoteservice/app/dependencies.php @@ -0,0 +1,29 @@ +addDefinitions([ + LoggerInterface::class => function (ContainerInterface $c) { + $settings = $c->get(SettingsInterface::class); + + $loggerSettings = $settings->get('logger'); + $logger = new Logger($loggerSettings['name']); + + $processor = new UidProcessor(); + $logger->pushProcessor($processor); + + $handler = new StreamHandler($loggerSettings['path'], $loggerSettings['level']); + $logger->pushHandler($handler); + + return $logger; + }, + ]); +}; diff --git a/src/quoteservice/app/routes.php b/src/quoteservice/app/routes.php new file mode 100644 index 0000000000..b33514ae13 --- /dev/null +++ b/src/quoteservice/app/routes.php @@ -0,0 +1,54 @@ +spanBuilder('calculate-quote') + ->setSpanKind(SpanKind::KIND_INTERNAL) + ->startSpan(); + $childSpan->addEvent('Calculating quote'); + + try { + $numberOfItems = intval($jsonObject['numberOfItems']); + $quote = 8.90 * $numberOfItems; + + $childSpan->setAttribute('app.quote.items.count', $numberOfItems); + $childSpan->setAttribute('app.quote.cost.total', $quote); + + $childSpan->addEvent('Quote calculated, returning its value'); + } catch (\Exception $exception) { + $childSpan->recordException($exception); + } finally { + $childSpan->end(); + return $quote; + } +} + +return function (App $app) { + $app->post('/getquote', function (Request $request, Response $response, Tracer $tracer) { + $span = AbstractSpan::getCurrent(); + $span->addEvent('Received get quote request, processing it'); + + $body = $request->getBody()->getContents(); + $jsonObject = json_decode($body, true); + + $data = calculateQuote($jsonObject, $tracer); + + $payload = json_encode($data); + $response->getBody()->write($payload); + + $span->addEvent('Quote processed, response sent back'); + + return $response + ->withHeader('Content-Type', 'application/json'); + }); +}; diff --git a/src/quoteservice/app/settings.php b/src/quoteservice/app/settings.php new file mode 100644 index 0000000000..f1963d4127 --- /dev/null +++ b/src/quoteservice/app/settings.php @@ -0,0 +1,25 @@ +addDefinitions([ + SettingsInterface::class => function () { + return new Settings([ + 'displayErrorDetails' => true, // Should be set to false in production + 'logError' => false, + 'logErrorDetails' => false, + 'logger' => [ + 'name' => 'slim-app', + 'path' => 'php://stdout', + 'level' => Logger::DEBUG, + ], + ]); + } + ]); +}; diff --git a/src/quoteservice/composer.json b/src/quoteservice/composer.json new file mode 100644 index 0000000000..dadb58b69e --- /dev/null +++ b/src/quoteservice/composer.json @@ -0,0 +1,31 @@ +{ + "name": "openteletry-demo/quoteservice", + "description": "Quote Service part of OpenTelemetry Demo", + "license": "Apache-2.0", + "require": { + "php": "7.4 || 8.1", + "ext-json": "dev-main", + "monolog/monolog": "2.8.0", + "open-telemetry/opentelemetry": "0.0.15", + "guzzlehttp/guzzle": "7.4.5", + "php-di/php-di": "6.4.0", + "php-di/slim-bridge": "3.2.0", + "php-http/guzzle7-adapter": "1.0.0", + "slim/psr7": "1.5", + "slim/slim": "4.10.0" + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "scripts": { + "start": "php -S 0.0.0.0:${QUOTE_SERVICE_PORT} -t public", + "test": "phpunit" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/src/quoteservice/public/index.php b/src/quoteservice/public/index.php new file mode 100644 index 0000000000..d840037178 --- /dev/null +++ b/src/quoteservice/public/index.php @@ -0,0 +1,88 @@ +create(); +ShutdownHandler::register([$tracerProvider, 'shutdown']); +$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php'); + +$containerBuilder->addDefinitions([ + Tracer::class => $tracer +]); + +// Build PHP-DI Container instance +$container = $containerBuilder->build(); + +// Instantiate the app +AppFactory::setContainer($container); +$app = Bridge::create($container); + +// Register middleware +//middleware starts root span based on route pattern, sets status from http code +$app->add(function (Request $request, RequestHandler $handler) use ($tracer) { + $parent = TraceContextPropagator::getInstance()->extract($request->getHeaders()); + $routeContext = RouteContext::fromRequest($request); + $route = $routeContext->getRoute(); + $root = $tracer->spanBuilder($route->getPattern()) + ->setStartTimestamp((int) ($request->getServerParams()['REQUEST_TIME_FLOAT'] * 1e9)) + ->setParent($parent) + ->setSpanKind(SpanKind::KIND_SERVER) + ->startSpan(); + $scope = $root->activate(); + + try { + $response = $handler->handle($request); + $root->setStatus($response->getStatusCode() < 500 ? StatusCode::STATUS_OK : StatusCode::STATUS_ERROR); + } finally { + $root->end(); + $scope->detach(); + } + + return $response; +}); +$app->addRoutingMiddleware(); + +// Register routes +$routes = require __DIR__ . '/../app/routes.php'; +$routes($app); + +// Create Request object from globals +$serverRequestCreator = ServerRequestCreatorFactory::create(); +$request = $serverRequestCreator->createServerRequestFromGlobals(); + +// Add Body Parsing Middleware +$app->addBodyParsingMiddleware(); + +// Add Error Middleware +$errorMiddleware = $app->addErrorMiddleware(true, true, true); + +// Run App +$app->run(); +$tracerProvider->shutdown(); diff --git a/src/quoteservice/src/Application/Settings/Settings.php b/src/quoteservice/src/Application/Settings/Settings.php new file mode 100644 index 0000000000..d0efaffdf4 --- /dev/null +++ b/src/quoteservice/src/Application/Settings/Settings.php @@ -0,0 +1,22 @@ +settings = $settings; + } + + /** + * @return mixed + */ + public function get(string $key = '') + { + return (empty($key)) ? $this->settings : $this->settings[$key]; + } +} diff --git a/src/quoteservice/src/Application/Settings/SettingsInterface.php b/src/quoteservice/src/Application/Settings/SettingsInterface.php new file mode 100644 index 0000000000..cfe3e3f984 --- /dev/null +++ b/src/quoteservice/src/Application/Settings/SettingsInterface.php @@ -0,0 +1,13 @@ +