Ce diaporama a bien été signalé.
Le téléchargement de votre SlideShare est en cours. ×

Api and Fluency

Prochain SlideShare
CORBA
CORBA
Chargement dans…3
×

Consultez-les par la suite

1 sur 44
1 sur 44

Api and Fluency

Télécharger pour lire hors ligne

An introduction to Domain Specific Language design with different examples and some focus in PHP

An introduction to Domain Specific Language design with different examples and some focus in PHP

Plus De Contenu Connexe

Api and Fluency

  1. 1. API & Fluency An introdcution to Internal DSL (for PHP)
  2. 2. Where it started... In 2005 Eric Evans proposed a different style of defining an API excluding the Setter concept in favor of Mutators with return value; this approach was named Fluent Interface in an article by Martin Fowler. https://www.martinfowler.com/bliki/FluentInterface.html
  3. 3. Why Fluent? The fluent attribute comes out from the special syntax of the developed API that, as prose, makes its use readable and explicit. Java example
  4. 4. Fluent Interface & Builder Fluent Interfaces find immediate application in those areas where the Builder Design Pattern can be used, getting a powerful configuration tool. C# example
  5. 5. Fluent Interface & DSL The concept of Fluent Interface can be related to that of Domain Specific Language << ...In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a Domain-Specific Language... >> https://en.wikipedia.org/wiki/Fluent_interface
  6. 6. DSL: External, Internal (or Embedded) DSLs are classified into: External and Internal They are defined external when they arise from grammars, and they involve sets of elements proper to the theory of programming languages such as: parser, lexer, interpreter or compiler They are defined internal when they arise as a syntax enrichment of a general-purpose hosting language, focusing on a specific jargon.
  7. 7. External DSL Vs Internal DSL An External DSL is a complex engineering work that leads to a project with a long-term perspective that provides a powerful maintenance equipment (tools & efforts) to obtain the maximum return on initial investment An Internal DSL is generally simpler to build, can be also defined by a single developer, but has a shorted horizon, different target and possibilities
  8. 8. External DSL (Resources) https://tomassetti.me/ https://strumenta.com/ http://voelter.de/
  9. 9. Why Internal DSL ? ● Using a known programming language to lead added value with: ● Simplify repetitive tasks ● Simplify/Encapsulate some existing API ● Encapsulate boilerplate code ● Abstract complex business problems ● Provide an API that expresses the intent of the code clearly ● Guide the developer in the domain with IDE autocomplete
  10. 10. Internal DSL : Artifacts & DSL Session Deploying an Internal DSL can be done as a library. Another possibility is to define script & macro definitions in a template language Using the artifact allows a developer to create several instances of the DSL that can be named as DSL Session.
  11. 11. Internal DSL : logical schema An internal DSL can be assimilated to the sequence of several fluent objects so named because their different methods lead to a fluent sequence of contexts of the jargon you are working on.
  12. 12. Internal DSL : What style? Example Form::WithId("My-Form")→ Attributes()→ Action("/Work.php")→ Method(HttpMethod::POST())→ Target(FormTarget::SELF)→ End()→ Elements()→ Text( textAttributes()→ WithId("t001")→ Named("text1") )→ Text( textAttributes()→ WithId("t002")→ Named("text2") )→ CheckBox( checkboxAttributes()→ WithId("male")→ Named("gender")→ WithValue("male") )→ CheckBox( checkboxAttributes() WithId("female")→ Named("gender)→ WithValue("female") )→ End()→ AsHTML();
  13. 13. Internal DSL : What style? Example Form::With( Form::Id("My-Form"), Form::Attributes("/Work.php", HttpMethod::POST(), FormTarget::SELF()), Form::Fields( Text::Of("t001", "text1"), Text::Of("t002", "text2"), Checkbox::Of(Gender::MALE, Gender::GENDER_LABEL, Gender::MALE), Checkbox::Of(Gender::FEMALE, Gender::GENDER_LABEL, Gender::FEMALE), ) );
  14. 14. Internal DSL : What style? Example Form()→ Attributes(function(FormId $form):FormAttributes{ return $form → WithId("My-Form") → Action("/Work.php") → Method(FormMethod::POST) → Target(FormTarget::SELF); })→ Text(function( FormField $field):void{ $field → Id("t001") → Name("text1"); })→ Text(function(FormField $field):void{ $field → Id("t002") → Name("text2"); })→ Checkbox(function(FormField $field):void{ $field → Id("male") → Name("gender") → Value("male"); })→ Checkbox(function(FormField $field):void{ $field → Id("female") → Name("gender") → Value("female"); }) → End() → asHTML();
  15. 15. Internal DSL : Design Guidelines The fundamental guidelines are related to: ● The limits imposed by the Hosting Language (syntax, idioms, meta-prg support, ...) ● Complete study of the elements of the domain to which fluency is to be obtained ● Definition of the grammar and resulting syntax fluent objects https://www.bottlecaps.de/rr/ui (EBNF Diagram Tool) ● Objectives on quality, re-usability, maintenance, ... of the DSL sought
  16. 16. Internal DSL : Design Guidelines SCALA val qux = value of method “someInternalMethod” of bar public class SimpleProject extends Project { public int defaultNumOfWindows(){ return 4;} public void define(){ defineHouseProject("SimpleHouse") .withWindows(defaultNumOfWindows()) .whereWallsAre() .painted(WHITE) .decoratedWith(decorationsPalette() .<ManualExecution>use( SPONGING, manualExecution()) .withInk(YELLOW)) .done() .roofMadeIn(WOOD) .build(); }} JAVA from elasticsearch import Elasticsearch client = Elasticsearch() response = client.search( index="my-index", body={ "query": { "bool": { "must": [{"match": {"title": "python"}}], "must_not": [{"match": {"description": "beta"}}], "filter": [{"term": {"category": "search"}}] } }, "aggs" : { "per_tag": { "terms": {"field": "tags"}, "aggs": { "max_lines": {"max": {"field": "lines"}} } } } }) Python
  17. 17. Internal DSL : Design Patterns The chaining of different methods on the fluent objects can be defined considering some primitive design principles that are classifiable in three families: ● Context primitives ● Pure primitives ● Glues primitives
  18. 18. Internal DSL : Design Patterns The primitive design included in the 3 families are: ● Starter ● Finisher ● Transition ● Action ● Back-Track ● Delegation ● Scoper
  19. 19. Internal DSL : Context Primitives Starter: It is the primitive that describes the method of the fluent object that allows the start of a DSL session; The invocation of the input method defines the input context Finisher: It is the primitive that describes the method that does not return any fluent object concluding the current DSL session Transition: It is the primitive that describes a method of a fluent object that returns another fluent object leading to a change of context.
  20. 20. Internal DSL : Pure Primitives Action: It is the primitive that describes methods that determines a side-effect in the context current and continuing with the current fluent-object Back-Track: It is the primitive that describes methods that determine a possible side-effect in the current context and lead to a previous fluent object.
  21. 21. Internal DSL : Glue Primitives Delegation: It is the primitive that that describes a method that receives a behavior that delegate an activity with respect to the current fluent object. It may have returned as much as the present context that a new context. Scoper: It is a primitive that describes methods that are used internally in the DSL for implement a binding between the current context and supporting object of the DSL.
  22. 22. Internal DSL : How to implement There may be one or more entry points to the DSL; generally when Builder nature prevails it will highlight a single entrypoint If there are several entry points, they must be independent of each other and lead to different contexts. To define a precise initial context, the entry point of the DSL can be access extending a specific context object Java example
  23. 23. Internal DSL : How to implement  The exit point is generally unique although, more rarely, there may be several.  The exit point is will be the one that will produce the final result of the DSL. Java examples
  24. 24. Internal DSL : How to implement Rules for the contextual transition: ● a fluent object can have one or more methods ● a fluent object has a contract defined by an interface ● a fluent object that exposes a new context and is not preceded by others is a Starter ● a fluent object that is not followed by another fluent object is a Finisher ● a fluent object can have methods that return to the current context (this) or that lead to a new fluent object (Transition)
  25. 25. Internal DSL : How to implement ● A fluent object representing mandatory DSL terms will be defined by all methods that represent these keywords. The methods lead to a new fluent object or to the Finisher ● A fluent object that represents a freely repeatable term will be defined by an interface with a method that represents this keyword and returns to the same interface and at the same time extending the interface of following fluent object or that of the finisher.
  26. 26. Internal DSL : How to implement ● A fluent object representing an optional keyword will be expressed by an interface that extends that of a successor object or Finisher. There will be a method that expresses the optional keyword and returns the interface of the next fluent object. ● A flowing object that triggers a sub-branch of the DSL will be defined by an interface with methods whose parameters will be other fluent objects allowing recursion in the DSL
  27. 27. Internal DSL : Using the rules - Example
  28. 28. Internal DSL : Using the rules - Example Car()→ BaseProduct()→ Produce(); Car()→ WithPreset(Preset::MARINE())→ Produce(); Car()→ StandardPreset()→ FullOptionals()→ Produce(); Car()→ StandardPreset()→ Produce(); Car()→ AdvancedPreset()→ Luxury()→ Produce(); Car()→ AdvancedPreset()→ Sports()→ Produce(); Car()→ WithEquipement()→ Part("Engine001") → Part("BrakeTX3") → Part("WheelDDS7") → Produce();
  29. 29. Internal DSL : Using the rules - Example interface Car { // single keyword in DSL public function BaseProduct():CarProducer; // parametric keyword in DSL public function WithPreset(Preset $presetId): CarProducer; // single optional keyword in DLS public function StandardPreset():CarConfigWithOptional; // multi choice exclusive keyword in DSL public function AdvancedPreset():CarConfigAdvanced; // repeatable keyword in DSL public function WithEquipement():VariableCarConfig; }
  30. 30. Internal DSL : Using the rules - Example public function StandardPreset():CarConfigWithOptional; interface CarConfigWithOptional extends CarProducer { public function FullOptionals():CarProducer; } public function AdvancedPreset():CarConfigAdvanced; interface CarConfigAdvanced { public function Luxury():CarProducer; public function Sports():CarProducer; }
  31. 31. Internal DSL : Using the rules - Example public function WithEquipement():VariableCarConfig; interface VariableCarConfig extends CarProducer { public function Part(string $partId):VariableCarConfig; }
  32. 32. Internal DSL : Mutations vs Immutability ● When is defined a Transition to the same context, especially with the Action Pattern, a side-effect happen ● It’s possible to choose a Transition to the same context, creating a new fluent object of the same type or cloning the existing one ● Can be adopted validation policies for each node of the DSL and Immutable Value Object as parameters
  33. 33. Internal DSL : Eager vs Lazy ● In a DSL session the operation defined by the method chaining are immediately evaluated ● It’s possible to choose a lazy evaluation by using lambda, closure, anonymous classes or classes hosting these kind of elements, that are chained or aggregated; the evaluation happen on the Finisher or by the lazy element returned to the Finisher
  34. 34. Internal DSL : Fluent Lazy Builder - Example interface NameBuilder { public function WithName(string $name): FamilyNameBuilder; } interface FamilyNameBuilder { public function WithFamilyName(string $name): AgeBuilder; } interface AgeBuilder { public function WithAge(int $age):Person; }
  35. 35. Internal DSL : Fluent Lazy Builder - Example public static final function Define():NameBuilder{ return new class() implements NameBuilder{ public function WithName(string $name): FamilyNameBuilder{ return new class($name) implements FamilyNameBuilder{ private string $name; public function __construct(string $name){ $this->name=$name;} public function WithFamilyName(string $familyName): AgeBuilder{ return new class($this->name,$familyName) implements AgeBuilder{ private string $name; private string $familyName; public function __construct(string $name,string $familyName){ $this->name=$name; $this->familyName = $familyName; } public function WithAge(int $age):Person{ return new Person($this->name, $this->familyName, $age); } }; }}; }}; Person::Define() → WithName("John") → WithFamilyName("Black") → WithAge(40);
  36. 36. Internal DSL : Fluent Lazy Builder Cascading Closure class Person{ … public static final function PersonBuilder():Closure { return fn(string $name):Closure=> fn(string $familyName):Closure=> fn(int $age):Person=> new Person( $name, $familyName, $age); } … } Person::PersonBuilder()("John")("Black")(40)
  37. 37. Internal DSL : Execute Around Pattern  Execute Around Pattern frees the user from certain actions that should always be executed before and after the business method. MailSender::Send(function(Mail $config):Mail{ return $config → From("john.black@kmail.com") → To("jack.white@jmail.com") → Subject("Test message from Netscape Communicator 4.7") → Body("Hello World!"); });
  38. 38. Internal DSL : Execute Around Pattern class MailSender{ ... public static final function Send(Closure $mailConfig):void{ $mailSender = new MailSender(); $connection = null; try{ $connection = $mailSender->connectToServer(); $connection→ Open(); $mailSender→ mailProcessing($connection, $mailConfig); }finally{ if(isset($connection)) { $connection.close(); } } } ... }
  39. 39. Internal DSL : Loan Pattern $refill = function(ToyBox $box):ToyBox{ return $box → addToy("lego") → addToy("mechano") → addToy("laser"); }; $play = function(ToyBox $box):ToyBox{ return $box → nextToy() → nextToy() → nextToy(); }; $playSession = ToyBox::play($refill, $play); $playSession(); $playSession(); /** * * @param Closure(ToyBox):ToyBox $boxFillerPolicy * @param Closure(ToyBox):ToyBox $playActions * @return Closure():void */ public static final function play( Closure $boxFillerPolicy, Closure $playActions):Closure{ return function()use( $boxFillerPolicy, $playActions):void{ $box = new ToyBox(); $box→ Open(); $box = $boxFillerPolicy($box); $box = $playActions($box); $box→ Close() → CleanUpToys(); }; }
  40. 40. Internal DSL : Typed Key Value Pair $arr = entriesForValueClass(Foo::class,[ entry("a",new Foo()), entry("b",new Foo()), ]); $arr2 = entriesForStringValue([ entry("WW","1"), entry("ss","2"), ]); $arr3 = entriesForIntValue([ entry("WW",1), entry("ss",2), ]);
  41. 41. Internal DSL : Typed Key Value Pair /** * * @template T * **/ final class KeyValue { … } /** * * @template T * @param T $value * @return KeyValue<T> */ function entry(string $key, $value): KeyValue { return new KeyValue($key, $value); } /** * * @template R * @param array<KeyValue<R>> $pairs * @param class-string<R> $clazz * @return array<string,R> * @psalm-suppress UnusedParam */ function entriesForValueClass(string $clazz, array $pairs): array { $result = array(); foreach ($pairs as $pair) { $result[$pair→ key()] = $pair→ value(); } return $result; }
  42. 42. Internal DSL : Typed Key Value Pair /** * * @param array<KeyValue<string>> $pairs * @return array<string,string> */ function entriesForStringValue(array $pairs): array { $result = array(); foreach ($pairs as $pair) { $result[$pair → key()] = $pair → value(); } return $result; } /** * @param array<KeyValue<int>> $pairs * @return array<string,int> */ function entriesForIntValue(array $pairs): array { $result = array(); foreach ($pairs as $pair) { $result[$pair → key()] = $pair → value(); } return $result; }
  43. 43. Internal DSL : Problems  Careful drafting of a DSL can require a major effort  It is not easy to find compromises with respect to principles and good practices  Use of inheritance, generics, composition for decoration can be difficult  The maintenance of large DSL can be a considerable effort: it is always good to consider a DSL as a product and evaluate when it is appropriate to invest in it
  44. 44. That All Folks More PHP Code Examples: https://github.com/stefanofago73/php_dsl

×