escreva menos e entenda mais com dsls desacopladas
Post on 21-Mar-2017
861 Views
Preview:
TRANSCRIPT
Domain Specific LanguagesEscreva menos e entenda mais com DSLs desacopladas
By / Leonardo Tumadjian @tumadjian
Sobre mim:Formado em Análise e Desenvolvimento de Sistemas
Programador PHP desde 2009Instrutor desde 2012
Nas horas vagas: Gamer, Biker, Shooter, GuitarristaEvangelista da comunidade PHPSP
Víciado em séries estranhas!
Antes de começarmos:Contribua com sua comunidade mais próxima, se tiver um
tempo ;)
Como contribuir?
Aviso!O conteúdo desta palestra tem o intuito de ilustrar como as
DSLs podem facilitar a usabilidade de seu código, porémdevemos ter em mente que são específicos os casos, e que
devem ser analisados com muita cautela.
AlinhamentoAPI comando-consulta: componente ou biblioteca
ex: BrowserKit, SwiftMailer, Dispatcher, etc..
Linguagem de propósito geral: PHP, Java, C++, Ruby
Modelo Semântico: Modelo de objetos ou estrutura de dados
O que é?“A computer programming language of limited
expressiveness focused on a particulardomain.” ―Martin Fowler
DSLs Externasroute path /myblog/pageId/commentId controller Myblog::commentsAction assert pageId: "\d+", commentId: "\d+" request GETendroute path /myblog/joinin controller Myblog::processForm request POSTend
DSLs Externas// Without the External DSLrequire __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Routing\Route;
$app = new Silex\Application();
$route1 = new Route('/myblog/pageId/commentId');$route1>setDefault('_controller', 'Myblog::commentsAction');$route1>setMethods(['GET']);$route1>setRequirement('pageId', '\d+');$route1>setRequirement('commentId', '\d+');
$app['routes']>add('route1', $route1);
$route2 = new Route('/myblog/joinin');$route2>setDefault('_controller', 'Myblog::processForm');$route2>setMethods(['POST']);
$app['routes']>add('route2', $route2);
$app>run();
DSLs Externas AgendaAgenda Appointment Dentist date 20160115 from 17:00 to 18:00
Appointment Dinner at Tiffanis date 20161225 from 17:00 to 18:00EndAgenda
DSLs ExternasCase de sucesso de uso de DSL externa, o Behat
Scenario: List 2 files in a directory Given I am in a directory "test" And I have a file named "foo" And I have a file named "bar" When I run "ls" Then I should get: """ bar foo """
Usabilidade:
Scenario: Some description of the scenario Given [some context] When [some event] Then [outcome]
DSLs Externas// Generated class by behat command
class FeatureContext extends BehatContext private $output;
// ...
/** @Given /I am in a directory "(["]*)"$/ */ public function iAmInADirectory($dir) if (!file_exists($dir)) mkdir($dir); chdir($dir);
/** @Given /I have a file named "(["]*)"$/ */ public function iHaveAFileNamed($file) touch($file); // ...
DSLs Internas// Without Internal DSL$o1 = new Order();$customer>addOrder($o1);$line1 = new OrderLine(6, Product::find('TAL'));$o1>addLine($line1);$line2 = new OrderLine(5, Product::find('HPK'));$o1>addLine($line2);$line3 = new OrderLine(3, Product::find('LGV'));$o1>addLine($line3);$line2>setSkippable(true);$o1>setRush(true);
// With Internal DSL$customer>newOrder() >with(6, 'TAL') >with(5, 'HPK')>skippable() >with(3, 'LGV') >priorityRush();
Font by Cal Evans: http://devzone.zend.com/777/fluent-interfaces-in-php/
Symfony Validationuse Symfony\Component\Validator\Validation;use Symfony\Component\Validator\Constraints as Assert;
$constraint = [ new Assert\Length([ 'min' => 5, 'max' => 20 ]), new Assert\NotBlank(), new Assert\Email()];
$validator = Validation::createValidator();
$errors = $validator>validate('teste@gmail.com', $constraint);
DSLs Internasuse DSLPAL\DSLValidator;
$fluent = new DSLValidator;
$errors = $fluent>length(5, 20) >notBlank() >email() >validate('teste@teste.com');
Symfony Validationuse Symfony\Component\Validator\Validation;use Symfony\Component\Validator\Constraints as Assert;
$constraints = new Assert\Collection([ 'nome' => [ new Assert\Type('string'), new Assert\Length(['min' => 5, 'max' => 10]) ], 'email' => [ new Assert\Email() ], 'sexo' => new Assert\Optional([ new Assert\NotBlank(), new Assert\Choice(['M', 'F']) ])]);
$validator = Validation::createValidator();
$res = $validator>validate([ 'nome' => 'Leonardo', 'email' => 'teste@teste.com', 'sexo' => 'N'], $constraints);
DSLs Internasuse DSLPAL\DSLValidator;
$fluent = new DSLValidator;
$constraints = $fluent>collection([ 'nome' => $fluent >type('string') >length(5, 10) >end(), 'email' => $fluent >email() >end(), 'sexo' => $fluent >notBlank() >choice(['M', 'F']) >optional() >end()]);
$res = $constraints>validate([ 'nome' => 'Leonardo', 'email' => 'teste@teste.com', 'sexo' => 'N']);
DSLs Internasuse DSLPAL\ValidatorFactory as dsl;
$const = dsl::collection([ 'nome' => dsl::type('string') >length(5, 10),
'email' => dsl::email(),
'sexo' => dsl::notBlank() >choice(['M', 'F']) >optional()]);
$erros = $const>validate([ 'nome' => 'Leonardo', 'email' => 'teste@teste.com', 'sexo' => 'N']);
Cases de sucesso:Respect Validator: http://respect.github.io/Validation/
// Use Respect/Validation!use Respect\Validation\Validator as v;
$res = v::numeric() >positive() >between(1, 255) >validate(180);
Mockery: http://docs.mockery.io
public function testGetsAverageTemperatureFromThreeServiceReadings() $service = m::mock('service'); $service>shouldReceive('readTemp') >times(3) >andReturn(10, 12, 14);
$temperature = new Temperature($service); $this>assertEquals(12, $temperature>average());
Annotations/** * @ManyToMany(targetEntity="Group", inversedBy="features") * @JoinTable(name="user_groups", * joinColumns=@JoinColumn(name="user_id", referencedColumnName="id"), * inverseJoinColumns=@JoinColumn(name="group_id", referencedColumnName="id") * ) */private $groups;
/** * Inverse Side * * @ManyToMany(targetEntity="User", mappedBy="groups") */private $features;
Algumas reflexões:1. Seu Modelo Semantico é complexo?2. Se sim, você deve saber usá-lo muito bem3. Evite retornar objetos diferentes do modelo DSL4. Lance sempre exceções detalhando os erros5. Contexto completo para a execução6. Sua DSL deve ser TODA documentada7. Simplifique sempre, evite nome de métodos complexos8. DSLs não são feitas só de encadeamento de métodos
Construindo DSL de Agendamento// API Comandoconsulta Calendaruse Calendar\Agenda;use Calendar\Appointment;
$agenda = new Agenda;
$appoint = Appointment::create();
$appoint>setAppointment('Dentist');$appoint>setDate(15, 01, 2016);$appoint>setFrom('17:00');$appoint>setTo('18:00');
$agenda>addAppointment($appoint);
$appoint = Appointment::create();
$appoint>setAppointment('Dinner at Tiffanis');$appoint>setDate(25, 12, 2015);$appoint>setFrom('18:00');$appoint>setTo('01:00');
$agenda>addAppointment($appoint);
Como ficará a DSL// DSL for Calendar
use Calendar\Builder;
$builder = Builder::make();
$builder >add('Dentist') >on(1, 15, 2016) >from('17:00') >to('18:00') >add('Dinner at Tiffanis') >on(12, 25, 2015) >from('18:00') >to('01:00');
$agenda = $builder>getAgenda();
$user>setAgenda($agenda);
O construtornamespace Calendar;
// Construtor de Expressões / Desacoplar a DSLclass Builder /** * @var Agenda */ protected $agenda; // keep all the appointments inside
public function __construct(Agenda $agenda) $this>agenda = $agenda;
public static function make() return new self(new Agenda);
// ...
Os métodos // ... Inside Class Builder
public function add($name) $this>agenda>addAppointment(Appointment::create()); $this>current()>setAppointmentName($name); return $this;
public function on($month, $day, $year) $this>current()>setDate($month, $day, $year); return $this;
protected function current() return $this>agenda>getCurrent();
// ...
Retorna o objeto Agenda já configurado // ... Inside Class Builder public function getAgenda() // Clona o objeto para retornar $agenda = clone $this>agenda;
// Limpa o iterator da propriedade $agenda $this>agenda>__construct();
return $agenda; // ...
Muito cuidado com referência X copia de objeto
Problemas com DSLs1. Cacofonia de linguagem2. Custo de construção3. Linguagem de gueto4. Abstração restrita5. Difícil testar6. Fácil fazer errado
Outros exemplos// Sequência de funçõescar('Maverik'); engine('V8'); cylinders('3,535'); filter('Monster'); color('green'); doors(2);
// Função aninhadacar( 'Maverik', engine( 'V8', cylinders('3,535'), filter('Monster') ), color( 'green' ), doors( 2 ));
Outros exemplos// Fecho aninhado Closures$carBuilder>make(function ($car, $engine, $torque) $car>name = 'Maverik'; $car>color = 'green'; $car>doors = 2;
$car>engine(function () use ($engine, $torque) $engine>type = 'V8'; $engine>cylinder = '3,535';
$engine>torque(function () use ($torque) $torque>initial = '1000rpm'; // not real $torque>final = '70000rpm'; // not real ); );
);
Meus contatosAbout me: https://about.me/leonardotumadjian
Email me: tumadjian@gmail.com
Twitter: @tumadjian
Exemplos no Github
top related