37
Domain Specific Languages Escreva menos e entenda mais com DSLs desacopladas By / Leonardo Tumadjian @tumadjian

Escreva menos e entenda mais com DSLs desacopladas

Embed Size (px)

Citation preview

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.

Referências

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

Quando usar?

Tipos de DSLsExternasInternas

Bancadas(Workbenches)

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 2016­01­15 from 17:00 to 18:00

Appointment Dinner at Tiffanis date 2016­12­25 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('[email protected]', $constraint);

DSLs Internasuse DSLPAL\DSLValidator;

$fluent = new DSLValidator;

$errors = $fluent­>length(5, 20) ­>notBlank() ­>email() ­>validate('[email protected]');

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' => '[email protected]', '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' => '[email protected]', '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' => '[email protected]', '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;

Como programar DSLs?

Sugestão de workflow

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 Comando­consulta 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 ); );

);

Atenção!Tomem muito cuidado para que isso não

aconteça com vocês

That's all folks!Espero que tenham gostado, dúvidas?

Obrigado!

Meus contatosAbout me: https://about.me/leonardotumadjian

Email me: [email protected]

Twitter: @tumadjian

Exemplos no Github