Pull to refresh

Пример использование DSL (Domain Specific Languages) в реальном проекте

Reading time3 min
Views20K
DSL (Domain Specific Languages) — языки, специфичные для решения задач какой-либо предметной области (в противовес языкам общего назначения типа Java или C#). Более подробное описание и примеры есть на википедии, я же хочу написать про то, как довольно легко встроить в приложение (C#) свой собственный DSL на базе языка Boo.

Перед нами стояла задача – сервис для онлайн-синхронизации данных из Active Directory в корпоративную информационную систему XXX (пользователи, организационные подразделения, группы и т.п. ). Основная очевидная сложность тут в том, что в каждом конкретном предприятии может быть своя схема AD (где-то для подразделений могут использовать OrganizationalUnit, где-то – группы и т.п.), равно как и свои классы для представления подразделений и работников в чистеме XXX (свои свойства и логика, причем на уровне сервиса синхронизации мы про них ничего не знаем, только их базовые классы), да еще и разные правила для синхронизации (где-то надо синхронизировать должности, где-то – нет).

Навскидку видится решение с заданием маппинга AD в XXX через XML, типа такого:


Но тут вылезает множество проблем – необходимость реализации своего парсера правил (который создает объекты правил по XML) и интерпретатора правил (которой их выполняет), ограниченность возможностей XML (как, например, задать преобразование типов или чуть более сложное вычисление, например, добавить к имени логина префикес с именем домена) и т.д. Основное преимущество XML – к таким правилам легко прикрутить GUI для их задания, ну и задача это знакомая и уже не раз решенная.

Эти проблемы подтолкнули нас к мысли, что задача подходит под тип задач, которые хорошо решаются с помощью DSL. Поскольку опыта и знаний у нас в этой области было не очень много, то вначале мы провели небольшое исследование и сделали пару прототипов. Основывались мы в основном на книге DSLs in Boo — Domain-Specific Languages in .NET и фреймворке Rhino DSL от автора книги.

Вот несколько примеров DSL из книги:

DSL для задания правил авторизации:


DSL для задания правил поставки модульного ПО и требований модулей (+ тут же пример реализации редактора DSL с Intellisense и подстветкой синтаксиса на базе редактора кода из SharpDevelop):


Все это примеры «внутренних» DSL, которые хостятся в языке Boo, то есть приведенный выше код – легальный код на Boo, который может компилироваться в обычные clr-классы или интерпретироваться. Boo выбран, так как у него довольно удобный синтаксис для DSL, богатые возможности по расширению синтаксиса (можно писать макросы, типа макросов в C, но они манипулируют не текстом, а синтаксическим деревом) + он открытый и у него удобное API компилятора, в которое можно легко встраивать свои действия.

Особенно удобен тот факт, что Boo выдает нам на выходе обычные .Net классы. В итоге, после обработки файлов с DSL из примера 1, мы получаем тип Rule, у которого есть метод, например, Evaluate(user), при вызове которого будет выполняться код на Boo, который и выдаст результат, нужно ли авторизовать пользователя. То есть с точки зрения разработчика и приложения все выглядит абсолютно прозрачно, как будто правила, которые написаны на DSL, написаны просто как обычный код. Очень круто.

Наш результат


Вот пример довольно сложного правила, которое синхронизирует пользователей и OrganizationalUnit из AD в департаменты и работников корпоративной системы XXX, при этом также учитывая и иерархию подразделений:


Кстати, эти правила прозрачно отлаживаются в студии без установки в нее каких-либо плагинов.

Внутренняя реализация


Собственно на самом деле тут все просто и все сделано стандартным синтаксисом Boo. Правило компиляется в контексте нашего класса, в котором есть методы с именами rule, for_type и т.д. Так что код

for_type user
это на самом деле просто вызов метода for_type c одним строковым параметром «user». А внутри метода мы просто записываем переданное значение в свойство правила и в дальнейшем можем им манипулировать.

Entity и ldap – это свойства правила с типами, которые реализуют специальный интерфейс Boo – IQuackFoo, который позволяет реализовать в них динамическую типизацию (типа IExpandoObject и dynamic в C#). Поэтому когда в правиле пишут ldap.GivenName, класс ldap на самом деле ищет в атрибутах пользователя атрибут с именем «GivenName» и возвращает его значение. Аналогично и с entity.

Единственное синтаксически хитрое место – выражение on entity.Sid == ldap.objectGUID. Тут on – это мета-метод, который отрабатывает во время компиляции и подменяет синтаксическое дерево выражения entity.Sid == ldap.objectGUID на другое. Конкретно тут мы просто выпарсиваем из дерева имена свойств, по которым задан ключ и запоминаем их. Выглядит мета-метод как просто метод на C#, который помечен специальным атрибутом и который на вход принимает синтаксическое дерево и возвращает новое дерево. Все просто:).

Детально по реализации спрашивайте, ну или стоит просто почитать DSLs in Boo и поковырять Rhino DSL, там все очень здорово описано, а Rhino DSL позволяет подключить к проекту DSL на Boo за пару минут.
Tags:
Hubs:
Total votes 37: ↑31 and ↓6+25
Comments5

Articles