57
Предметно-ориентированные языки в проектах на Java Иван Гаммель Ведущий разработчик 14 ноября 2012 года

Предметно-ориентированные языки (DSL) в проектах на Java

  • Upload
    custis

  • View
    1.132

  • Download
    22

Embed Size (px)

DESCRIPTION

Открытый семинар для студентов в компании CUSTIS (14 ноября 2012). Лектор: Иван Гаммель, ведущий разработчик и архитектор Java. Предметно-ориентированные языки (DSL) — современный инструмент для создания лаконичного и выразительного кода, точно описывающего предметную область задачи. На этом семинаре слушатели познакомятся со встроенными предметно-ориентированными языками на Java, на примере реальных задач увидят их практическую применимость и изучат основные архитектурные приемы для построения собственных встроенных DSL. Видеозапись семинара: https://vimeo.com/55268612

Citation preview

Page 1: Предметно-ориентированные языки (DSL) в проектах на Java

Предметно-ориентированные языки в проектах на Java

Иван Гаммель Ведущий разработчик

14 ноября 2012 года

Page 2: Предметно-ориентированные языки (DSL) в проектах на Java

Что такое DSL? DSL, или domain-specific language

(предметно-ориентированный язык) – язык программирования для решения круга задач, ограниченного предметной областью

Примеры: CSS – DSL для описания визуальных стилей

оформления веб-страниц SQL – DSL для построения запросов к СУБД MediaWiki Templates – язык для описания

страниц Википедии

2/57

Page 3: Предметно-ориентированные языки (DSL) в проектах на Java

Особенности DSL Неполнота – на DSL нельзя писать

программы

Зависимость от внешней системы – браузера, СУБД, интерпретатора

DSL бывают как декларативные (CSS), так и императивные (SQL)

DSL ближе к естественным языкам (например, к английскому), чем языки программирования общего назначения

3/57

Page 4: Предметно-ориентированные языки (DSL) в проектах на Java

DSL бывают двух типов – внешние (“external”) и внутренние (“internal”). Этот семинар – о внутренних DSL!

4/57

Page 5: Предметно-ориентированные языки (DSL) в проектах на Java

Что такое встроенный DSL? Встроенный, или «внутренний», DSL –

язык, использующий синтаксические конструкции языка программирования общего назначения

Примеры: SVG – встроенный DSL на основе XML для

векторной графики XML Schema – встроенный DSL на основе XML

для описания форматов XML-файлов Criteria API в JPA (Java) и Linq в C# – DSL для

построения запросов к БД

5/57

Page 6: Предметно-ориентированные языки (DSL) в проектах на Java

Встроенные DSL можно делать и на Java! Этот семинар – об основных приемах построения встроенных DSL на Java.

6/57

Page 7: Предметно-ориентированные языки (DSL) в проектах на Java

Будем изучать DSL на примерах 1. Библиотека доступа к ресурсам приложения

2. Построение запросов при удаленных вызовах

3. Построение объектов модели предметной области

4. Моделирование диаграммы состояний

5. Построение отчета на основе шаблона документа

6. Кастомизация оформления интерфейса

7. Декларативное описание логики интерфейса

8. Написание тестов по функциональным спецификациям

7/57

Page 8: Предметно-ориентированные языки (DSL) в проектах на Java

Что будем использовать в примерах? Шаблоны проектирования (паттерны) Factory, Factory Method Builder Specification Visitor

Особые возможности языка Java Ковариантные типы Generics Аннотации

8/57

Page 9: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Библиотека доступа к ресурсам приложения

Постановка задачи:

В приложениях часто необходимо считывать настройки или ресурсы для локализации из файлов .properties, базы данных или другого источника

Настройки могут быть различных типов: строки, числа и т. п.

Иногда нужны значения по умолчанию

9/57

Page 10: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Библиотека доступа к ресурсам приложения

Традиционный подход:

Читаем из ResourceBundle, ловим исключение MissingResourceException, выполняем преобразование типа

В лучшем случае пишем обертку, которая занимается поиском ResourceBundle и получением из него значений

Активно перегружаем методы

10/57

Page 11: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Библиотека доступа к ресурсам приложения

Традиционный подход:

11/57

Page 12: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Библиотека доступа к ресурсам приложения

Алгоритм работы:

1. Построить ключ ресурса

2. Найти хранилище, содержащее заданную пару ключ – значение

3. Если ресурс не найден, взять значение по умолчанию

4. Преобразовать к нужному типу и вернуть результат

12/57

Page 13: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Фабричный метод Строим ключ ресурса в перегруженных

фабричных методах

13/57

Page 14: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Свободный интерфейс Получаем значения нужного типа

из хранилища ресурсов

Забываем про геттеры, сеттеры и JavaBeans, пишем по-английски (fluent interface):

OptionalValue<String> value = resources.findBy(key(MyClass.class, “someValue”)); String string = value.asIs(); Integer integer = value.as(Integer.class);

14/57

Page 15: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Реализация интерфейса

Использовали ковариантный тип возвращаемого значения!

15/57

Page 16: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: «Зацепление» Если ресурс не найден, взять значение

по умолчанию или бросить исключение – в зависимости от задачи

Аналог сложного предложения – вызов нескольких методов в одной строке (method chaining)

Integer integer = value.or(5).asIs(); boolean flag = value.notNull().as(Boolean.class);

16/57

Page 17: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Как устроено «зацепление»? Для зацепления возвращаем объект

со свободным интерфейсом

17/57

Page 18: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Реализация условия NotNull

18/57

Page 19: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Отличия MandatoryValue и OptionalValue В интерфейсе OptionalValue есть методы or

и notNull

Метод OptionalValue.asIs может вернуть null И это нормально! Мы можем проверить наличие значения вызовом

метода OptionalValue.isSet

MandatoryValue гарантирует, что методы as и asIs вернут непустое значение Исключение MissingValueException мы получим только

тогда, когда отсутствие ресурса – это проблема! 19/57

Page 20: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 1: Какие приемы мы использовали? Паттерн «Фабричный метод» (factory method)

Свободный интерфейс (fluent interface)

Зацепление (method chaining)

20/57

Page 21: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Построение запросов при удаленных вызовах

Постановка задачи:

Типичный программный продукт для бизнеса имеет трехзвенную архитектуру: клиент – сервер приложений – СУБД

Пользователь клиентского ПО может формировать критерии отбора данных для представления в таблице

Нужно передать эти критерии на сервер и получить табличные данные

21/57

Page 22: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Построение запросов при удаленных вызовах

Идея: почему бы не использовать Detached Criteria

из Hibernate?

Проблемы: Дыра в абстракции: клиент получает зависимость

от Hibernate Дыра в безопасности: можно попросить

что-нибудь лишнее и получить это Аналогия с SQL Injection

Плохая идея!

22/57

Page 23: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Построение запросов при удаленных вызовах

Решение: Реализуем модель запроса (Query Object)

для передачи данных с клиента на сервер Транслируем модель запроса в запрос к СУБД

на сервере Строим модель запроса с помощью DSL

Предполагаемый вид запроса:

Criteria<Document> criteria = from(Document.class) .select(start(5), count(10)) .where(property(“date”).between(startDate, endDate) .and(property(“status”).equals(PROCESSED)));

23/57

Page 24: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Строим предикаты с помощью паттерна «Спецификация» Используем зацепление:

24/57

Page 25: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Используем фабричные методы для конкретных условий

25/57

Page 26: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Задаем постраничную разбивку с помощью токенов Было:

select(10, 5)

Можно перепутать параметры при вызове

Стало: select(count(5), start(10)) и select(start(10), count(5))

Токены позволяют контролировать семантику входных параметров метода на уровне компилятора

26/57

Page 27: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Реализация с помощью токенов Используем зацепление с return this:

27/57

Page 28: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Реализация токена для постраничной разбивки

Мы можем получить экземпляр токена с помощью фабричного метода

28/57

Page 29: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: А что на сервере? А на сервере с помощью instanceof

разбираем полученный объект и строим критерий JPA или SQL-запрос

А можем и не разбирать, если храним данные в памяти: У нас есть метод matches! Переберем объекты и проверим на соответствие

критерию

Но это уже совсем другая история…

29/57

Page 30: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 2: Использованные приемы Зацепление c return this

Паттерн «Спецификация» (Specification)

Токены (tokens)

Фабричные методы, свободный интерфейс

30/57

Page 31: Предметно-ориентированные языки (DSL) в проектах на Java

Немного о зацеплении: мы уже видели return this и ковариантные типы. Что еще можно сделать?

31/57

Page 32: Предметно-ориентированные языки (DSL) в проектах на Java

Зацепление и наследование Ковариантные типы возвращаемых

значений – подкласс может уточнить тип возвращаемого значения

32/57

Page 33: Предметно-ориентированные языки (DSL) в проектах на Java

Зацепление и наследование Generics: параметризация самим собой –

уточнение типа можно сделать уже в суперклассе!

33/57

Page 34: Предметно-ориентированные языки (DSL) в проектах на Java

«Зацепление» и неизменяемость (immutability) А почему бы и нет? Можно в любой момент получить экземпляр

класса в нужном состоянии, не боясь испортить исходный объект

Используем return new MyObject(…); Пример: java.math.BigDecimal

34/57

Page 35: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 3: Паттерн Builder Builder («строитель») – класс,

предназначенный для построения экземпляров другого класса

Формулируем с помощью DSL задачу и командуем: build()

Currency currency = aCurrency().withName(“Рубль”).build();

35/57

Page 36: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 3: Исходный код

36/57

Page 37: Предметно-ориентированные языки (DSL) в проектах на Java

Когда использовать Builder? Сложная логика формирования объектов

Создание объектов по образцу

Построение иерархий объектов

Отложенное создание объекта

37/57

Page 38: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 4: Модель диаграммы Моделируем получение событий

от внешней системы

Некоторые события могут быть обработаны только после получения других событий

События связаны друг с другом разными способами

38/57

Page 39: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 4: Используем «слова» DSL Вспомогательные объекты (токены) помогут

нам точнее сформулировать, что мы хотим сделать

Передаем их в качестве параметра в методы свободного интерфейса

Можно использовать перечисления или строки

А можно – объекты, созданные с помощью фабричных методов

Активно используем import static

39/57

Page 40: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 4: Исходный код

40/57

Page 41: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 5: Построение отчета на основе шаблона документа При заполнении шаблона нужно выполнить

множество действий: Несколько строк нужно изменить, несколько –

вставить… А потом еще несколько изменить и вставить!

Проблема: шаблон нужно изменять в определенном порядке или вычислять смещения строк и столбцов после вставки

41/57

Page 42: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 5: Builder, токены и отложенная модификация объекта Используем паттерн Builder

для построения сценариев изменения шаблона

Сначала построим все сценарии, а потом их выполним К моменту выполнения нам будут известны все

смещения!

42/57

Page 43: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 5: Исходный код Скрываем сложную логику вычисления смещения

строк в таблице Excel внутри реализации паттерна Builder в классе ExcelTxInsert

Tx – потому что похоже на транзакцию: выполняем вычисления только после того, как вызвали commit

43/57

Page 44: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 6: Настройка оформления Проблемы: Много разных компонентов Заранее не известно, какие возможности

визуального оформления будут использованы

Решение: Используем паттерн Visitor: опишем интерфейс

Стиль, реализации которого будут применять различные стили к компонентам

Это пример расширяемости DSL!

44/57

Page 45: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 6: Исходный код Конкретные стили могут быть

реализованы не только в библиотеке DSL, но и в прикладном коде:

45/57

Page 46: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 7: Декларативное описание логики интерфейса Проблема: запуск простого алгоритма

в интерфейсе очень многословен Нужно создать кнопку Активировать горячие клавиши Подписаться на события кнопки и вызвать метод

с реализацией алгоритма

Решение: Используем аннотации метода для описания

кнопок и событий, его запускающих

46/57

Page 47: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 7: Как это выглядит? Для поддержки таких описаний нужен достаточно мощный фреймворк:

Описание действия «Создать шаблон проводки» включает: Контекст выполнения действия (DEFAULT_SCOPE – по умолчанию) Расположение кнопки на панели инструментов (INHERIT – рядом

с другими кнопками действий, определенных в этом классе) Реакцию на события в таблице шаблонов проводок (нажатие

клавиши INSERT)

47/57

Page 48: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 7: Аннотации как прием для построения DSL Можно использовать вложенные аннотации

Токены – примитивные типы, перечисления

Для сложной логики можно использовать ссылки на классы, например:

@Validator(EmailValidator.class)

Можно сочетать с другими приемами: Декларативное описание задачи на аннотациях Анализ и трансляция в императивный код

в реализации паттерна Builder

48/57

Page 49: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 8: «Дано» и «Доказать» Test-Driven Development (TDD) – сначала

пишем тесты, потом код

Behavior-Driven Development (BDD) – сначала записываем истории пользователей (user stories) и сценарии работы, потом пишем тесты

DSL и BDD – записываем сценарии сразу в виде тестов

given – «дано», assert – «доказать»

49/57

Page 50: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 8: Пишем тесты Используем JUnit или TestNG

Используем Builder для построения объектов

Используем конструкции given и assert

50/57

Page 51: Предметно-ориентированные языки (DSL) в проектах на Java

Пример 8: Исходный код теста Тестируем на конкретной реализации репозитория,

имеющей метод assertExists(Matcher matcher):

С использованием Criteria API из примера 2 можно и так:

assertExists(anyDeal().withKind(TOD)).in(deals);

Строим критерий, после чего вызываем findByCriteria у репозитория и проверяем результат

51/57

Page 52: Предметно-ориентированные языки (DSL) в проектах на Java

Резюме: основные приемы построения DSL Свободный интерфейс (fluent interface)

Зацепление (method chaining)

Токены (tokens)

Аннотации

Паттерны ООП: Builder Specification Visitor

52/57

Page 53: Предметно-ориентированные языки (DSL) в проектах на Java

DSL или API? DSL – разновидность API: Можно оформить в виде библиотеки Подразумевается широкое повторное

использование конструкций языка

53/57

Page 54: Предметно-ориентированные языки (DSL) в проектах на Java

Когда использовать DSL? Перевод в код функциональных требований,

спецификаций и пользовательских сценариев работы, сформулированных по единому шаблону

Замена повторяющихся конструкций в коде, для которых явно определена предметная область

NB: важно наличие «лексического ядра» – словаря, общего для всех повторяющихся конструкций и спецификаций

54/57

Page 55: Предметно-ориентированные языки (DSL) в проектах на Java

Заключение Рассмотренные приемы для создания DSL

достаточны для применения в собственных проектах

DSL делает ваш код читабельнее и короче

DSL избавляет от формализма командную работу

Современные практики разработки ПО поощряют использование DSL

55/57

Page 56: Предметно-ориентированные языки (DSL) в проектах на Java

Ссылки по теме Building a fluent API (internal DSL) in Java,

Gabrielle Carcassi

«Приемы объектно-ориентированного проектирования. Паттерны проектирования», Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес (GoF)

«Предметно-ориентированное проектирование (DDD). Структуризация сложных программных систем», Эрик Эванс

«Предметно-ориентированные языки программирования», Мартин Фаулер

56/57

Page 57: Предметно-ориентированные языки (DSL) в проектах на Java

Спасибо! Вопросы?

Иван Гаммель [email protected] ivan-gammel.moikrug.ru/

57/57