Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PHP quote service #345

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
julianocosta89 marked this conversation as resolved.
Show resolved Hide resolved
- 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();
julianocosta89 marked this conversation as resolved.
Show resolved Hide resolved
$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