a soa approximation on symfony
TRANSCRIPT
A SOA Approximation using Symfony
Joseluis Laso & Carlos Agudo
Who we are
•Carlos Agudo (@karloslupus)•Joseluis Laso (@jl_laso)
And Others.. (Spanish team)• Petar Georgiev (@pgkirilov)• Daniel Abad (@ruudy_es)• Noel García (@wolfwolker)• Joaquín Fernández Campo
(@xocas__)• Miguel Vilata
(@miguelvilata)
Where?
What is Digilant
DSP: Demand Side Platform => programmatic advertising.
We have a backend to configure ours RTB bidder(s) architecture.• A programmatic campaign can have thousands of
parameters.
Long journey
From monolithic sf1 (legacy , 8 years of development) to a SOA in sf2-3:
Requirements
• API Rest• Auto Scalable (can be a huge huge demanding
platform)• Symfony 2• Easy to deploy
So?
Do not try this at home
Yes Symfony 2-3
Bundle: “A bundle is simply a structured set of files within a directory that implement a single feature. “
-- We can split services as bundles, and start here.This sounds weird??Uncle Bob: “Don't leap into microservices just because it sounds cool. Segregate the system into jars using a plugin architecture first. If that's not sufficient, then consider introducing service boundaries at strategic points.”
Some Questions arose
• One repo? One per service orchestrated with composer? • One repo, let’s split it later, and coordinate with composer
later. (easier to develop)• One Db per service? One db for the whole project, with
soft-relations between services?• One Db for the whole project, let’s split it later. (easier to
develop??, careful with soft-relations, lost of referential integrity)
Our motto
Let’s split it later
And let’s start!!
Backend Services View
Bundles
Yes, services bundles
Service- Machine
We can create as many services-machine as we want. Depending on demand. We can cook them!!
Fully elastic scalability.LineItem Service - Machine
LineItem Core
LineItem- Campaign - Machine
LineItem
Campaign
Core
Cooking Process 1
Using ansistrano (ansible), in each deploy, we can select how many bundles a machine have.
Machine A- Geo and Creatives Machine B- All services but Pixel
Cooking Process 2
We need to dynamically load, our “bundles-service” on deploy, our models, our routings, etc..
A lot of magic happens here!
Compiler pass to the rescue!
Cooking Process 3
In our AppKernel.php. We have the LocateBundles (utility class) in a CoreBundle
Core Bundle
We have a CoreBundle, that act as a glue, (Compiler passes)• Communication between services (Can be http-
external, http-internal, process) We have a call Router. API-REST, that’s why http-internal or external
• Some bussines event listeners (a filter for accounts, softdelete, audit db listener)
• User-Permission Model
Models 1
In order to don’t get coupled to the persistence implementation (Doctrine), don’t use annotations for defining the model, map in an xml:
Models 2
We want to use our own folders:
Base?? We will explain it later, we have an autogenerator of code.
Models 3
Compiler Pass to Load Models in our own folders.
We pass through our bundles, and load models, in our folders.
The key:DoctrineOrmMappingPass
We also use an alias:
LineItem:ThirdPartyDataGeo:City
ServiceName:Model
Others Compilers Passes
Here we load our services (symfony) in each Bundle.
Also Lists calls (api-rest list call, defaults limit per model, orderby etc..)
Others Compilers Passes II
Load the routes per each bundle!
Controller Services
GeoBundle/Resources/config/services.ymlimports: - resource: ServicesControllers/alpha.yml
We load versionable controllers (alpha, v1..) as services:Right now: alpha (Remember WIP?)
Routes:/alpha/creative/*/current/creative/*
A lot of CRUD tables-controllers
We have a lot of master- admin tables.
We have a RestController for this purpose.
Controversy:- We have a lot of
classes child of this one.
- Needs refactor in services!
2nd part
And now ….
The funny part :)
Generator of code
Once upon a time …We had time and we were youngs :) and we decide to invest time creating a code generator to make our life easier … you know ? developers ... this lazy race
With a lot of database schemas per create we thought that was a good idea to convert this MWB files directly in code … and was a really nightmare ...
Code generator (AKA mwb-import)
But … it worked
MWB file
it’s a ZIP
xmlsqliteetc ..
unzip
generate code with twig
generate fixtures
Code generator (AKA mwb-import)
ApiDoc documentation generator
document !document !document !document !document !document !
ApiDoc documentation generator
But not only generates documentation.Why don’t use annotations to generate automatic documentation of API routes ?
There are a couple of bundles in the market that do this.
Again … we thought that we can do better.
ApiDoc documentation generator/*** @ApiDoc\Description("Create an Account")* @ApiDoc\Method("post")* @ApiDoc\Route("/account")* @ApiDoc\Version("~")* @ApiDoc\Subdomain("account")* @ApiDoc\Param(name="maxBudgetValue",type="decimal",nullable=false,description="Account maxBudgetValue")
* @ApiDoc\Header(name="Authorization", placeholder="Authorization: Bearer {token}", type="string", nullable=false, description="mandatory token obtained in login check call")
* @ApiDoc\ReturnHeader("* HTTP 200 OK* Cache-Control: no-cache* Content-Type: application/json* ")* @ApiDoc\ReturnData(type="json", sample="{'id':'integer'}")*/public function createAction(Request $request)
DocumentatorFeatures:automatic creation of documentation in html format, including a sandbox to test API endpoints
Documentator
DocumentatorFeatures:automatic creation of documentation in html format, including a sandbox to test API endpoints
- export to POSTMAN collection
Call Router
How to make a call to a service that you even know where is ?
LineItem Service - Machine
LineItem Core
LineItem- Campaign - Machine
LineItem
Campaign
Core
Call Router
How to call a controller method if you don’t know where exactly is ?
Remember, we can deploy to n-servers …To solve that we created a CallRouter that makes the call internally (if the server has the service required) or makes the call external (guzzle) if the service is in another server.
Call Router
try { // ... $route = $this->router->match($pathInfo);
return $this->makeInternalCall($route['_controller'], $request);
} catch (ResourceNotFoundException $e) {
return $this->makeExternalCall($request->getMethod(), $subdomain, $pathInfo, $request->getContent());
}