DEV Community

david duymelinck
david duymelinck

Posted on

Domain specific language versus design patterns

I don't like libraries/frameworks versus posts, because most of the time the comparisons are asymmetric. I'm going to use code examples, but these should be seen more as a mental aid than an opinion about the compared solutions.

How I got here

For a while I have been thinking about abstractions and their value. And more specifically the abstractions that are able to read and write data storage.

The seed in my head started to grow when I realized Doctrine's ORM and Laravel's Eloquent data storage fields had a different level of abstraction. In Eloquent there is no abstraction, in Doctrine the field name is abstracted by the model.
This means when a misnamed field is used in Eloquent it will result in a database error, where Doctrine will return an ORM error.

// Eloquent
Recording::select('titel')->get();
// Doctrine query builder
$querybuilder->select('p.titel')->from(Recording::class, 'p')->getResult();
Enter fullscreen mode Exit fullscreen mode

This led me to Doctrine's DQL, which is an alternative to the query builder. DQL is a DSL that stays very close to the SQL syntax, which is one of the most known DSLs on its own.

$entitymanager->createQuery('SELECT p.titel FROM Recording p')->getResult();
Enter fullscreen mode Exit fullscreen mode

I don't like short strings in code because of the magic number trap, that is why I started to think about longer strings. And the more I saw examples of longer strings the more I got the feeling that the abstraction was clearer with strings than solutions with design patterns.

Why I think design patterns can be a problem

When we go back to the query builder and the DQL examples, the query builder can affect the code in ways that is not possible with DQL.

/**
 * The assumption is there are three tables; recording, crew and persons.
 * Where there is a relation between recording and crew and between crew and persons.
**/

// Eloquent
$recording = Recording::where('id', 'tt3890160')->first();
$crew = $recording->crew;  
$results = $crew->map(function ($crewMember) use ($recording) {
    return [
        'title' => $recording->title,
        'director_name' => $crewMember->category === 'director' ? $crewMember->person->name : null,
        'player_name' => in_array($crewMember->category, ['actor', 'actress']) ? $crewMember->person->name : null,
        'characters' => in_array($crewMember->category, ['actor', 'actress']) ? $crewMember->characters : null,
    ];
});
// DQL
SELECT r.title,
       CASE WHEN c.category = 'director' THEN p.name ELSE NULL END AS director_name,
       CASE WHEN c.category IN ('actor', 'actress') THEN p.name ELSE NULL END AS player_name,
       CASE WHEN c.category IN ('actor', 'actress') THEN c.characters ELSE NULL END AS characters
FROM App\Entity\Recording r
LEFT JOIN r.crew c
LEFT JOIN c.person p
WHERE t.id = :id
Enter fullscreen mode Exit fullscreen mode

I think the first noticeable difference is the result of the SQL query.
With the query builder the result needs to be processed after the data fetching, while DQL does the processing during the data fetching.

A less noticeable difference is that the $crew = $recording->crew; line triggers an extra SQL query.
Because DQL is very close to the SQL syntax it is a lot harder to create unintentional extra SQL queries.

I think design patterns are used to make it easier to reason about code, while a DSL is used to make it easier to reason about the actions that need to happen.
The more complex the actions or action chains get, the more design patterns move away from them because they want to provide the loosest coupling that is possible. Which means the mental model you make in your head becomes larger than with a DSL.

What are the problems with a DSL

The main problem with a DSL is that creating a string that can handle current and future actions is a more upfront process than writing code and iterating it depending on new situations.

When a DSL is a string it requires more cogs than design patterns, because you could look at it as a code frontend. Behind the curtains the string will be transformed to code.

Conclusion

I see two paths to go for a DSL:

  • The domain actions are well understood, then go for a DSL from the start.
  • The domain actions settle after a while, access if a DSL makes the code easier to understand and less error prone.

In other cases design patterns are the solution.

Top comments (1)

Collapse
 
miketalbot profile image
Mike Talbot ⭐

That's an excellent point; hidden ORM queries were the bane of the legacy product I've been replacing.