Skip to content

Commit

Permalink
Add PHP quote service (#345)
Browse files Browse the repository at this point in the history
  • Loading branch information
Juliano Costa committed Sep 14, 2022
1 parent 82f6cb0 commit 860a35d
Show file tree
Hide file tree
Showing 18 changed files with 366 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ build
src/frontend/protos
next-env.d.ts
src/frontend/cypress/videos
src/frontend/cypress/screenshots
src/frontend/cypress/screenshots
vendor/
composer.lock
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
22 changes: 22 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
7 changes: 7 additions & 0 deletions docs/manual_span_attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
1 change: 1 addition & 0 deletions docs/service_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -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). |
1 change: 1 addition & 0 deletions docs/trace_service_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
4 changes: 4 additions & 0 deletions src/quoteservice/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.dockerignore
.idea
Dockerfile
vendor
7 changes: 7 additions & 0 deletions src/quoteservice/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.idea/
.vscode/
/coverage/
/vendor/
/logs/*
!/logs/README.md
.phpunit.result.cache
26 changes: 26 additions & 0 deletions src/quoteservice/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions src/quoteservice/README.md
Original file line number Diff line number Diff line change
@@ -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.
29 changes: 29 additions & 0 deletions src/quoteservice/app/dependencies.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);

use App\Application\Settings\SettingsInterface;
use DI\ContainerBuilder;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Monolog\Processor\UidProcessor;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

return function (ContainerBuilder $containerBuilder) {
$containerBuilder->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;
},
]);
};
54 changes: 54 additions & 0 deletions src/quoteservice/app/routes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);

use OpenTelemetry\API\Trace\AbstractSpan;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\SDK\Trace\Tracer;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;

function calculateQuote($jsonObject, Tracer $tracer): float
{
$quote = 0.0;
$childSpan = $tracer
->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');
});
};
25 changes: 25 additions & 0 deletions src/quoteservice/app/settings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);

use App\Application\Settings\Settings;
use App\Application\Settings\SettingsInterface;
use DI\ContainerBuilder;
use Monolog\Logger;

return function (ContainerBuilder $containerBuilder) {
// Global Settings Object
$containerBuilder->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,
],
]);
}
]);
};
31 changes: 31 additions & 0 deletions src/quoteservice/composer.json
Original file line number Diff line number Diff line change
@@ -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
}
}
}
88 changes: 88 additions & 0 deletions src/quoteservice/public/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);

use DI\Bridge\Slim\Bridge;
use DI\ContainerBuilder;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\SDK\Trace\Tracer;
use OpenTelemetry\SDK\Trace\TracerProviderFactory;
use OpenTelemetry\SDK\Common\Util\ShutdownHandler;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Factory\ServerRequestCreatorFactory;
use Slim\Routing\RouteContext;

require __DIR__ . '/../vendor/autoload.php';

// Instantiate PHP-DI ContainerBuilder
$containerBuilder = new ContainerBuilder();

// Set up settings
$settings = require __DIR__ . '/../app/settings.php';
$settings($containerBuilder);

// Set up dependencies
$dependencies = require __DIR__ . '/../app/dependencies.php';
$dependencies($containerBuilder);

// Add OTel
$tracerProvider = (new TracerProviderFactory('quoteservice'))->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();
Loading

0 comments on commit 860a35d

Please sign in to comment.