KEMBAR78
Symfony in microservice architecture | PDF
SYMFONYSYMFONY
COMPONENTS INCOMPONENTS IN
MICROSERVICESMICROSERVICES
ARCHITECTUREARCHITECTURE
ABOUT MEABOUT ME
Daniele D'Angeli
@dangelidaniele
http://danieledangeli.com
http://www.sainsburys.co.uk/
London
Born and raised in Rome
WHAT WE'RE DOINGWHAT WE'RE DOING
A "brownfield" project
8 "micro" service so far
API gateway written in GoLang
Python, PHP, Symfony, Django, GoLang
TECHNOLOGIESTECHNOLOGIES
... LET ME START... LET ME START
FROMFROM
CONCLUSIONSCONCLUSIONS
IS SYMFONY SUITABLE FOR
A MICROSERVICES
ARCHITECTURE?
UNDER CERTAINUNDER CERTAIN
CONDITIONSCONDITIONS
YESYES
.What they are
.Drawbacks
.When Symfony can fail?
.When Symfony can success?
MICROSERVICESMICROSERVICES
WHAT THEY AREWHAT THEY ARE
BUZZWORD WINNINGBUZZWORD WINNING
AWARD 2015AWARD 2015
...EMERGED FROM...EMERGED FROM
#CONTINUOS#CONTINUOS
DELIVERYDELIVERY
#DOMAIN DRIVEN#DOMAIN DRIVEN
DESIGNDESIGN
#ON DEMAND#ON DEMAND
VIRTUALIZATIONVIRTUALIZATION
#INFRASTRACTURE#INFRASTRACTURE
AUTOMATIONAUTOMATION
SMALL, AUTONOMOUSSMALL, AUTONOMOUS
SERVICES THAT WORKSERVICES THAT WORK
TOGETHERTOGETHER
small and focused on doing one thing well
"Gather together those things that
change for the same reason,
and separate those things that
change for different reasons"
Robert C. Martin
HOW SMALL IS SMALL?HOW SMALL IS SMALL?
AUTONOMOUSAUTONOMOUS
Each microservice
can be able to change independently
without requiring consumer to change
IDEALLY!!!IDEALLY!!!
DRAWBACKSDRAWBACKS
TESTINGTESTING
REPRODUCE THE ENTIREREPRODUCE THE ENTIRE
STACK ON A LOCAL ENVSTACK ON A LOCAL ENV
WHEN SYMFONY CANWHEN SYMFONY CAN
FAILFAIL
SYMFONY IS NOT ASYMFONY IS NOT A
MICROMICRO FRAMEWORKFRAMEWORK
IT CAN BEIT CAN BE
CONSIDERED MORECONSIDERED MORE
AA "FULL-STACK""FULL-STACK"
FRAMEWORKFRAMEWORK
...IF THEY ARE SO...IF THEY ARE SO
SMALLSMALL
TOO MANY TOOLS INTOO MANY TOOLS IN
OUR "FRAMEWORK"OUR "FRAMEWORK"
SYMFONY IS HARDSYMFONY IS HARD
TO LEARNTO LEARN
MICROSERVICESMICROSERVICES
====
ETEREGENOUS TEAMETEREGENOUS TEAM
WHAT ABOUT IFWHAT ABOUT IF
YOU ASK TO AYOU ASK TO A
PYTHONIST TOPYTHONIST TO
WORK WITHWORK WITH
SYMFONY?SYMFONY?
TOO MANY CONFIGURATIONTOO MANY CONFIGURATION
FILESFILES
WHEN SYMFONY CANWHEN SYMFONY CAN
SUCCESS?SUCCESS?
...THEY ARE SO...THEY ARE SO
SMALL?SMALL?
THERE ARE A LOT OFTHERE ARE A LOT OF
EXAMPLES WHEREEXAMPLES WHERE
COMPANIES FAILEDCOMPANIES FAILED
WITH MICROSERVICESWITH MICROSERVICES
IT'S NOT ABOUTIT'S NOT ABOUT
SYMFONY, OR OTHERSYMFONY, OR OTHER
MVC FRAMEWORKS...MVC FRAMEWORKS...
IT'S ABOUTIT'S ABOUT
PREMATUREPREMATURE
DECOMPOSITIONDECOMPOSITION
HTTPS://RCLAYTON.SILVRBACK.COM/FAILING-HTTPS://RCLAYTON.SILVRBACK.COM/FAILING-
AT-MICROSERVICESAT-MICROSERVICES
"... ANOTHER LESSON I LEARNED WAS"... ANOTHER LESSON I LEARNED WAS
TO NOT GET TOO GRANULAR WITHTO NOT GET TOO GRANULAR WITH
MICROSERVICES AT THE BEGINNINGMICROSERVICES AT THE BEGINNING
OF A PROJECT"OF A PROJECT"
"... INSTEAD OF DOING WHAT WE DID"... INSTEAD OF DOING WHAT WE DID
(STARTING WITH 8 SERVICES), TRY(STARTING WITH 8 SERVICES), TRY
STARTING WITH TWO OR THREESTARTING WITH TWO OR THREE
SERVICES OF LOGICALLY RELATEDSERVICES OF LOGICALLY RELATED
FUNCTIONALITY (THEY WON'T BEFUNCTIONALITY (THEY WON'T BE
MICRO, HOWEVER)"MICRO, HOWEVER)"
HOW FACINGHOW FACING
PREMATUREPREMATURE
DECOMPOSITION?DECOMPOSITION?
FIRST APPROACHFIRST APPROACH
MONOLITH FIRSTMONOLITH FIRST
http://martinfowler.com/bliki/MonolithFirst.html
RealityHope vs.
http://martinfowler.com/articles/dont-start-monolith.html
DOMAIN DRIVENDOMAIN DRIVEN
DESIGN PRINCIPLESDESIGN PRINCIPLES
ARE USEFULARE USEFUL
ERIC EVANSERIC EVANS
He talked about how microservices boundary enable DDD
https://skillsmatter.com/skillscasts/6259-ddd-and-microservices-at-last-some-
bounderies
DDD eXchange 2015
THOUGHWORKSTHOUGHWORKS
https://www.thoughtworks.com/insights/blog/domain-driven-design-services-
architecture
Domain Driven Design for Services Architecture
Bounded Contexts Designed as Service Applications
ONEONE
"MICRO"SERVICE"MICRO"SERVICE
FOR EACH BOUNDEDFOR EACH BOUNDED
CONTEXTCONTEXT
https://flic.kr/p/3p16o1
IN A NUTSHELLIN A NUTSHELL
LESS MICRO, MORELESS MICRO, MORE
"MACRO""MACRO"
WE NEED TO DESIGNWE NEED TO DESIGN
OUR APPLICATIONSOUR APPLICATIONS
PROPERLYPROPERLY
IN SUCHIN SUCH
SCENARIOSSCENARIOS
SYMFONY CAN HELPSYMFONY CAN HELP
...BUT PROBABLY WE...BUT PROBABLY WE
NEED SOMENEED SOME
EXPEDIENTSEXPEDIENTS
A PRATICAL EXAMPLEA PRATICAL EXAMPLE
.Symfony scaffolding
&minimal configuration
.Testing
.How develop the integrations
LET'SLET'S
CONSIDER ANCONSIDER AN
APPLICATIONAPPLICATION
LIKE SLACKLIKE SLACK
DISCOVERDISCOVER
BOUNDED CONTEXT (HARD)BOUNDED CONTEXT (HARD)
Channel
Context
Message
Context
Authorization
Context
Authentication
Context
User
management
Context
MESSAGE CONTEXTMESSAGE CONTEXT
Messages
Context
ALLOWS AALLOWS A PUBLISHERPUBLISHER
TO PUBLISH AND DELETETO PUBLISH AND DELETE
MESSAGGES ONMESSAGGES ON OPENOPEN
CHANNELSCHANNELS
AA PUBLISHERPUBLISHER MUST BEMUST BE
AUTHORIZEDAUTHORIZED TOTO
PUBLISH A MESSAGE ONPUBLISH A MESSAGE ON
A CHANNELA CHANNEL
SYMFONYSYMFONY
SCAFFOLDINGSCAFFOLDING
AND MINIMALAND MINIMAL
CONFIGURATIONCONFIGURATION
AVOID A CLASSICAVOID A CLASSIC
SYMFONYSYMFONY
SCAFFOLDINGSCAFFOLDING
USE A MINIMALUSE A MINIMAL
SYMFONYSYMFONY
CONFIGURATIONCONFIGURATION
PLUSPLUS
DDD DIRECTORYDDD DIRECTORY
STRUCTURESTRUCTURE
https://github.com/danieledangeli/symfony-microservice-
bounded-context-example
Github repository
MINIMAL SYMFONY CONFMINIMAL SYMFONY CONF
Inspired by:
http://www.whitewashing.de/2014/10/26/symfony_all_the_things_web.html
environment variables
.env files
only one config.yml
only one entry point (index.php)
DotEnv
https://github.com/vlucas/phpdotenv
require_once __DIR__ . "/../vendor/autoload.php";
require_once __DIR__ . "/../app/AppKernel.php";
use SymfonyComponentHttpFoundationRequest;
use DotenvDotenv;
//load environment variables. It doesn't overwrite existing ones
$dotenv = new Dotenv(__DIR__ . '/../');
$dotenv->load();
$request = Request::createFromGlobals();
$kernel = new AppKernel(
$_SERVER['SYMFONY_ENV'],
(bool)$_SERVER['SYMFONY_DEBUG']
);
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Only one index.php
Only 1 config.yml
public function registerBundles()
{
...
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__ . '/config/config.yml');
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
$loader->load(function ($container) {
$container->loadFromExtension('web_profiler', array(
'toolbar' => true,
));
$container->loadFromExtension('framework', array(
'test' => true,
));
});
}
}
The result:
START TOSTART TO
CODINGCODING
TESTINGTESTING
TEST BEHAVIOURTEST BEHAVIOUR
FIRSTFIRST
MAKE THE VALUE EXPLICITMAKE THE VALUE EXPLICIT
(It's important also for other developers)
Feature: message publisher
As a Publisher
I need to be able to publish a message on a channel
...
Scenario: A publisher, if not authorized
is not able to publish a new
message on a channel
Given I am publisher
And exists an "open" channel
And I'm not authorized
to publish messages on that channel
When I write the message "Hi guys"
Then I'm informed that I'm not authorized
And no new messages will be published on
that channel
FUNCTIONAL TESTSFUNCTIONAL TESTS
Stub dependencies
http://martinfowler.com/articles/microservice-testing/
PHPUNIT/PHPSPECPHPUNIT/PHPSPEC
with or without
MOCKERYMOCKERY
UNIT TESTSUNIT TESTS
MAKE CLEAR THE TESTMAKE CLEAR THE TEST
TYPE:TYPE: FUNCTIONAL,FUNCTIONAL, UNIT,UNIT, INTEGRATIONINTEGRATION
PHPUNIT @GROUPSPHPUNIT @GROUPS
HOW TOHOW TO
DEVELOP THEDEVELOP THE
INTEGRATIONSINTEGRATIONS
final class Publisher
{
public function publishOnChannel(
Channel $channel,
ChannelAuthorization $channelAuthorization,
BodyMessage $body
) {
if($channelAuthorization->canPublisherPublishOnChann
if (!$channel->isClosed()) {
//create message
}
throw new ChannelClosedException(
sprintf("The channel %s is closed", $channel
);
}
throw new PublisherNotAuthorizedException;
}
}
Channel {
id ChannelId
isOpen boolean
}
ChannelAuthorization {
publisherId PublisherId
channelId ChannelId
isAuthorized boolean
}
MESSAGE CONTEXT MODELSMESSAGE CONTEXT MODELS
Publisher {
id PublisherId
}
sent within the
authenticated
request
taken from services integrations
(Channel Context, Channel Authorization Context)
namespace MessageContextDomainServiceGateway;
interface ChannelGatewayInterface
{
/**
* @param ChannelId $channelId
*
* @return Channel
*/
public function getChannel(ChannelId $channelId);
}
<?php
namespace MessageContextInfrastructureBundleServiceChannel;
class ChannelGateway implements ChannelGatewayInterface
{
private $channelAdapter;
...
/**
* @param ChannelId $channelId
*
* @return Channel
*/
public function getChannel(ChannelId $channelId)
{
return $this->channelAdapter
->toChannel($channelId);
}
}
namespace MessageContextInfrastructureBundleServiceChannelAuthorizatio
class ChannelAdapter
{
...
public function toChannel(ChannelId $channelId)
{
$request = new Request("GET", sprintf("%s/api/channels/%s",
$this->channelContextUri,
$channelId
));
$request->addHeader("Accept", "application/json");
$response = $this->requestHandler->handle($request);
return $this->channelTranslator->toChannelFromResponse(
$response
);
}
}
class ChannelTranslator
{
public function toChannelFromResponse(Response $response)
{
if (200 === $response->getStatusCode()) {
$contentArray = $this->validateAndGetResponseBodyArray($response);
return new Channel(new ChannelId($contentArray["id"]), $contentArray["isOpen"]);
}
....
}
private function validateAndGetResponseBodyArray(Response $response)
{
$contentArray = $response->getBody();
if (isset($contentArray["id"]) && isset($contentArray["isOpen"])) {
return $contentArray;
}
....
}
}
Only what we need
{
"id": "456t-889-4444",
"isOpen": false,
"createAt": "26/05/2015",
"publisherId": "11111-5555-3333-5555",
"name": "a channel name",
"spot": "a channel spot",
"messagges_count": 450
...
...
}
The original response
ARE WE MISSINGARE WE MISSING
SOMETHINGSOMETHING
??
<?php
interface ServiceIntegrationInterface
{
/**
* @param $message
*
* @throws ServiceNotAvailableException
*/
public function onServiceNotAvailable($message);
/**
* @param $message
*
* @throws ServiceFailureException
*/
public function onServiceFailure($message);
}
namespace MessageContextDomainServiceGateway;
interface ChannelGatewayInterface
extends ServiceIntegrationInterface
{
/**
* @param ChannelId $channelId
*
* @throws MicroServiceIntegrationException
* @return Channel
*/
public function getChannel(ChannelId $channelId);
}
class ChannelGateway implements ChannelGatewayInterface
{
...
/**
* @param $message
* @throws ServiceNotAvailableException
*/
public function onServiceNotAvailable($message)
{
throw new ServiceNotAvailableException($message);
}
/**
* @param $message
* @throws ServiceFailureException
*/
public function onServiceFailure($message)
{
throw new ServiceFailureException($message);
}
ARE WE STILLARE WE STILL
MISSINGMISSING
SOMETHINGSOMETHING
??
IF AN INTEGRATIONIF AN INTEGRATION
IS NOT AVAILABLE,IS NOT AVAILABLE,
WHY CONTINUINGWHY CONTINUING
TO SEND REQUESTSTO SEND REQUESTS
TO IT?TO IT?
CIRCUIT BREAKERCIRCUIT BREAKER
"A CIRCUIT BREAKER IS"A CIRCUIT BREAKER IS
USED TO DETECT FAILURESUSED TO DETECT FAILURES
AND ENCAPSULATES LOGICAND ENCAPSULATES LOGIC
OF PREVENTING A FAILUREOF PREVENTING A FAILURE
TO REOCCURTO REOCCUR
CONSTANTLY"CONSTANTLY"
HTTPS://GITHUB.COM/EJSMONT-ARTUR/PHP-HTTPS://GITHUB.COM/EJSMONT-ARTUR/PHP-
CIRCUIT-BREAKERCIRCUIT-BREAKER
namespace MessageContextInfrastructureBundleCircuitBreaker;
class CircuitBreaker implements PostContextCircuitBreakerInterface
{
private $circuitBreaker;
...
public function isAvailable($serviceName)
{
return $this->circuitBreaker->isAvailable($serviceName);
}
public function reportSuccess($serviceName)
{
$this->circuitBreaker->reportSuccess($serviceName);
}
public function reportFailure($serviceName)
{
$this->circuitBreaker->reportFailure($serviceName);
}
}
public function getChannel(ChannelId $channelId)
{
if ($this->circuitBreaker->isAvailable($this->serviceName)) {
try {
$channel = $this->channelAdapter->toChannel($channelId);
$this->circuitBreaker->reportSuccess($this->serviceName);
return $channel;
} catch (UnableToProcessResponseFromService $e) {
$this->handleNotExpectedResponse($e->getResponse());
}
}
$this->onServiceNotAvailable("Service not available");
}
private function handleNotExpectedResponse(Response $response)
{
$this->circuitBreaker->reportFailure($this->serviceName);
...
}
CONCLUSIONCONCLUSION
(AGAIN)(AGAIN)
SYMFONY CAN BE USED INSYMFONY CAN BE USED IN
SOME MICROSERVICESOME MICROSERVICE
ARCHITECTURES,ARCHITECTURES,
ESPECIALLY IF WE STARTESPECIALLY IF WE START
WITH A MONOLITH FIRSTWITH A MONOLITH FIRST
APPROACHAPPROACH
HOWEVER WE MAY NEED TOHOWEVER WE MAY NEED TO
CONSIDER SOMECONSIDER SOME
EXPEDIENTSEXPEDIENTS
(env variables, avoid complex tools)
(IT DEPENDS ON THE TEAM!!)(IT DEPENDS ON THE TEAM!!)
MINIMAL INTEGRATIONSMINIMAL INTEGRATIONS
&
HANDLING FAILURESHANDLING FAILURES
ARE A GOOD STARTING POINTARE A GOOD STARTING POINT
TO FACE THE OVERCOMPLEXITYTO FACE THE OVERCOMPLEXITY
INTRODUCED BYINTRODUCED BY
THIS ARCHITECTURETHIS ARCHITECTURE
WHAT WE HAVEWHAT WE HAVE
MISSEDMISSED
OTHER INTEGRATION WAYS:OTHER INTEGRATION WAYS:
SERVICE CHOREOGRAPHYSERVICE CHOREOGRAPHY
OTHER TESTING WAYS:OTHER TESTING WAYS:
CONSUMER DRIVEN CONTRACTCONSUMER DRIVEN CONTRACT
TESTSTESTS
QUESTIONS?QUESTIONS?
https://www.slideshare.net/danieledangeli10/symfony-in-
microservice-architecture

Symfony in microservice architecture