Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Filling the gap between domain experts and software developers with a Java DSL

Filling the gap between domain experts and software developers with a Java DSL

Software projects fail more often because of misunderstandings of requirements or other forms of miscomunications between the domain experts and the development team than for strictly technical reasons. An effective way to mitigate this problem is writing the parts of the software implementing the business logic using a Domain Specific Language that could be understandable also for the non-technical participants to the project. This allows the domain expert to read and validate the business logic directly from the source code and check that it corresponds to what they had in mind. This talk will quickly introduce DSLs clarifying the differences between internal and external ones. Then it will illustrate with practical examples the 3 main patterns to implement DSLs in Java 8: method chaining, nested functions and function sequence with lambda expressions, discussing the pros and cons of each of them and showing how these patterns have been used in real-world open source projects. Finally it will demonstrate how to combine these 3 patterns in a single DSL to take advantage of their respective best features.

Mario Fusco

May 08, 2022
Tweet

More Decks by Mario Fusco

Other Decks in Programming

Transcript

  1. by Mario Fusco twitter: @mariofusco Real world DSL making technical

    and business people speaking the same language
  2. What is a Domain Specific Language? A computer programming language

    of limited expressiveness focused on a particular domain computer programming language limited expressiveness particular domain
  3. Written once, read many times Always code as if the

    person who will maintain your code is a maniac serial killer that knows where you live
  4. "Any fool can write code that a computer can understand.

    Good programmers write code that humans can understand“ Martin Fowler
  5. Pros & Cons of DSLs + Expressiveness  Communicativity +

    Conciseness + Readability  Maintainability  Modificability + Higher level of abstraction + Higher productivity in the specific problem domain ̶ Language design is hard ̶ Upfront cost ̶ Additional indirection layer  Performance concerns ̶ Lack of adequate tool support ̶ Yet-another-language-to-learn syndrome
  6. DSL taxonomy External DSL  a language having custom syntactical

    rules separate from the main language of the application it works with Internal DSL  a particular way of employing a general-purpose language, using only a small subset of the language's features Language workbench  a specialized IDE for defining and building DSL, usually in a visual way, used both to determine the structure of the DSL and to provide an editing environment for people using it
  7. Internal DSL External DSL Language Workbench DSL Types Comparison +

    learning curve + cost of building + programmers familiarity + IDE support (autocompletion …) + composability + flexibility + readability + clear separation between business (DSL) and host language + helpful when business code is written by a separate team (domain experts) ̶ syntactic noise ̶ needs to be recompiled (if host language is static) ̶ need to learn of grammars and language parsing ̶ boundary between DSL and host language ̶ easier to grow out of control + visual + rapid development + IDE support for external DSL ̶ tools immaturity ̶̶ vendor lock-in
  8. What is an internal DSL? A (business) internal DSL is

    a fluent interface built on top of a clearly defined Command & Query API
  9. The Command & Query API public interface Car { void

    setColor(Color color); void addEngine(Engine engine); void add Transmission(Transmission transmission); } public interface Engine { enum Type { FUEL, DIESEL, ELECTRIC, HYDROGEN, METHANE } void setType(Type type); void setPower(int power); void setCylinder(int cylinder); } public interface Transmission { enum Type { MANUAL, AUTOMATIC, CONTINOUSLY_VARIABLE } void setType(Type type); void setNumberOfGears(int gears); }
  10. Let's build a car Car car = new CarImpl(); Car.setColor(Color.WHITE);

    Engine engine1 = new EngineImpl(); engine1.setType(Engine.Type.FUEL); engine1.setPower(73); engine1.setCylinder(4); car.addEngine(engine1); Engine engine2 = new EngineImpl(); engine2.setType(Engine.Type.ELECTRIC); engine2.setPower(60); car.addEngine(engine2); Transmission transmission = new TransmissionImpl(); transmission.setType(Transmission.Type.CONTINOUSLY_VARIABLE); car.setTransmission(transmission);
  11. "Domain users shouldn't be writing code in our DSL but

    it must be designed for them to understand and validate“ Debasish Ghosh
  12. Object Initialization new CarImpl() {{ color(Color.WHITE); engine(new EngineImpl {{ type(Engine.Type.FUEL);

    power(73); cylinder(4); }}); engine(new EngineImpl {{ type(Engine.Type.FUEL); power(60); }}); transmission(new TransmissionImpl {{ type(Transmission.Type.CONTINOUSLY_VARIABLE); }}); }}; ̶ syntactial noise ̶ explicit use of constructors + clear hierarchic structure + small implementation effort ̶ unclear separation between Command API and DSL
  13. Functions Sequence car(); color(Color.WHITE); engine(); type(Engine.Type.FUEL); power(73); cylinder(4); engine(); type(Engine.Type.ELECTRIC);

    power(60); transmission(); type(Transmission.Type.CONTINOUSLY_VARIABLE); end(); + works well for defining the items of a (top level) list ̶ use of global context variable(s) ̶ hierarchy defined only by identation convention + lowest possible syntactic noise ̶ impossible to enforce right sequence of global function invocation
  14. Methods Chaining car() .color(Color.WHITE) .engine() .type(Engine.Type.FUEL) .power(73) .cylinder(4) .engine() .type(Engine.Type.ELECTRIC)

    .power(60) .transmission() .type(Transmission.Type.CONTINOUSLY_VARIABLE) .end(); + method names act as keyword argument ̶ hierarchy defined only by identation convention + object scoping + works good with optional parameter
  15. Nested Functions car( color(Color.WHITE), transmission( type(Transmission.Type.CONTINOUSLY_VARIABLE), ), engine( type(Engine.Type.FUEL), power(73),

    cylinder(4) ), engine( type(Engine.Type.ELECTRIC), power(60) ) ); + no need for context variables ̶ higher punctuation noise + hierarchic structure is echoed by function nesting ̶ inverted evaluation order ̶ arguments defined by position rather than name ̶ rigid list of arguments or need of methods overloading
  16. Is my DSL good (concise, expressive, readable) enough? You cooked

    the meal … … now eat it! DSL design is an iterative process
  17. Mixed Strategy car( color(Color.WHITE), transmission() .type(Transmission.Type.CONTINOUSLY_VARIABLE), engine() .type(Engine.Type.FUEL) .power(73) .cylinder(4),

    engine() .type(Engine.Type.ELECTRIC) .power(60) ); car( ... ); function sequence for top level lists method chaining for arguments definition nested function for top level object creation
  18. Hibernate Criteria Queries List cats = session .createCriteria(Cat.class) .add( Restrictions.like("name",

    "Fritz%") ) .add( Restrictions.between("weight", min, max) ) .addOrder( Order.desc("age") ) .setMaxResults(50) .list();
  19. jMock Turtle turtle = context.mock(Turtle.class); // The turtle will be

    told to turn 45 degrees once only oneOf(turtle).turn(45); // The turtle can be told to flash its LEDs any number // of times or not at all allowing(turtle).flashLEDs(); // The turtle can be asked about its pen any number of times // and will always return PEN_DOWN allowing(turtle).queryPen(); will(returnValue(PEN_DOWN)); // The turtle will be told to stop at least once. atLeast(1).of(turtle).stop();
  20. lambdaj Plain Java version: List<Car> automaticCars = new ArrayList<Car>(); for

    (Car car : cars) { if (car.getTransmission().getType() == Transmission.TYPE.AUTOMATIC) automaticCars.add(car); } lambdaj version: List<Sale> salesOfAFerrari = select(cars, having( on(Car.class).getTransmission().getType(), equalTo(Transmission.TYPE.AUTOMATIC) ));