Under certain conditions, Symfony can be used successfully in a microservices architecture. Symfony works best when services start large and decompose over time into bounded contexts, rather than starting with many small services. Key considerations include using minimal Symfony configurations, domain-driven design principles to map services to contexts, handling failures through circuit breakers, and focusing on behavior-driven development and integration testing between services. While Symfony introduces more complexity than micro frameworks, these strategies can help address issues of over-decomposition when applying it to microservices.
13. 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?
19. SYMFONY IS NOT ASYMFONY IS NOT A
MICROMICRO FRAMEWORKFRAMEWORK
IT CAN BEIT CAN BE
CONSIDERED MORECONSIDERED MORE
AA "FULL-STACK""FULL-STACK"
FRAMEWORKFRAMEWORK
33. "... 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"
34. "... 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)"
39. 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
48. 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
53. 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
54. 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
55. 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,
));
});
}
}
59. MAKE THE VALUE EXPLICITMAKE THE VALUE EXPLICIT
(It's important also for other developers)
60. 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
66. 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;
}
}
67. 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)
70. 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
);
}
}
71. 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
80. "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"
82. 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);
}
}
83. 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);
...
}
85. 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
86. 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!!)
87. 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