Сервисы с расширяемым функционалом
От: BulatZiganshin  
Дата: 10.07.09 16:16
Оценка:
вопрос настолько классический, что его наверно уже в вузах изучают. есть сервисы (к примеру алгоритмы сжатия данных), реализуемые в одном проекте. есть клиенты, использующие эти сервисы. понятно, что создаются публичные API, описывающие взаимодействие клиентов с сервисами

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

если же клиент и сервис пишутся независимо, тем более когда есть множество реализаций того и другого, реализующих старый API, возникает необходимость расширения API с сохранением совместимости со старым. как этого достичь?


один вариант предлагает нам модель COM: расширение API — это добавление новых interfaces, под которыми понимаются набор binary calls. сервис объявляет поддерживаемые им interfaces, клиент выжимает из них всё что может. это работает, но мне такой подход не нравится своей монолитностью — представьте себе, сколько "стоит" добавление нового необязательного параметра (типа "по возмодности включи кеширование") в уже существующий binary call — объявляем новый interface с одним-единственным call, почти идентичным старому, реализуем его, переадресовываем старый binary call на новый. затем в клиенте проверяем реализацию интерфейса, и в зависимости от неё вызываем тот или иной метод. если у нас многоязычный проект, то не забудьте добавить прототипы нового интерфейса на всех языках. мрак!

в общем, как обычно у MS — много кода из ничего. с другой стороны, модель COM — это строго и надёжно


мне сейчас кажется, что наилучшим способом сделать расширяемый API является передача динамических ассоциативных таблиц (т.е. набора пар ключ-значение, где значения могут иметь любой тип). идеально — и имя самой вызываемой сервисной функции включить сюда же. тогда весь экспозируемый API предстаёт как один-единственный прототип "Hash doit(String service, Hash x)", т.е. проблема расширяемости на уровне формальных объявлений пропадает сразу

далее, на уровне клиента проблема расширяемости решается сразу: если мы хотим добавить новый параметр в существующий вызов или воспользоваться новым вызовом, то никаких проблем. единственное, что нужно делать- поверять при возврате из doit, что не возвращено NOT_IMPLEMENTED, но это нормальная часть обработки ошибок/исключений

на стороне сервиса нужен dispatch по имени вызванной процедуры (если мы всё делаем через doit), проверка того, что переданы все обязательные для данной процедуры параметры (иначе возвращаем NOT_IMPLEMENTED), и дальше к примеру чтение из хеша всех необязательных параметров с присваиванием им значений по умолчанию

и далее: добавление нового необязательного параметра в процедуру — плюс одна строчка пустого кода. добавление новой процедуры — строчка в диспетчере плюс чтение всех параметров из хеша в локальные переменные

в общем, получаем куда меньше boilerplate, большую гибкость, в минусах — меньшая надёжность. например, компилятор не проверит, что вы добавили новый параметр hashable во все вызовы функции readfile. в принципе, это применение идей динамических языков к созданию интерфейсов


отдельный вопрос — как этот Hash реализовывать на практике. хорошо, если вы работаете в рамках одного языка, где такой тип есть. если же у вас простой язык или многояызыковая среда, то Hash реализуется сериализацией в строку. разумеется, всё это — хеш+сериализация — не подходит для миллионов вызовов в секунду


что скажете? конкретной задачей, которую я сейчас решаю, является разработка стандарта API для библиотек сжатия. чтобы кто-то мог написать либу на C++, я написал прогу на хаскеле, и они друг с другом жили, и я мог дальше расширять API, добавляя всё новые функции
Люди, я люблю вас! Будьте бдительны!!!
Re: Сервисы с расширяемым функционалом
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.07.09 16:40
Оценка:
Здравствуйте, BulatZiganshin, Вы писали:


BZ>один вариант предлагает нам модель COM: расширение API — это добавление новых interfaces, под которыми понимаются набор binary calls. сервис объявляет поддерживаемые им interfaces, клиент выжимает из них всё что может. это работает, но мне такой подход не нравится своей монолитностью — представьте себе, сколько "стоит" добавление нового необязательного параметра (типа "по возмодности включи кеширование") в уже существующий binary call — объявляем новый interface с одним-единственным call, почти идентичным старому, реализуем его, переадресовываем старый binary call на новый. затем в клиенте проверяем реализацию интерфейса, и в зависимости от неё вызываем тот или иной метод.

Всего пара строчек, кроме того в WinAPI и COM часто применяются Reserved параметры.
Также один из способов версионирования API (также WinAPI и COM) — принимать структуры данных в качестве параметров, которые элементарно расширяются — в начеле передается размер структуры, который однозначно определяет версию. В случае ОО тоже самое достигается передачей наследника некоторого класса.

BZ>если у нас многоязычный проект, то не забудьте добавить прототипы нового интерфейса на всех языках. мрак!

Фигня, midl давно придумали.


BZ>мне сейчас кажется, что наилучшим способом сделать расширяемый API является передача динамических ассоциативных таблиц (т.е. набора пар ключ-значение, где значения могут иметь любой тип). идеально — и имя самой вызываемой сервисной функции включить сюда же. тогда весь экспозируемый API предстаёт как один-единственный прототип "Hash doit(String service, Hash x)", т.е. проблема расширяемости на уровне формальных объявлений пропадает сразу

Это переход от статической типизации контрактов к динамической.

BZ>далее, на уровне клиента проблема расширяемости решается сразу: если мы хотим добавить новый параметр в существующий вызов или воспользоваться новым вызовом, то никаких проблем. единственное, что нужно делать- поверять при возврате из doit, что не возвращено NOT_IMPLEMENTED, но это нормальная часть обработки ошибок/исключений


BZ>на стороне сервиса нужен dispatch по имени вызванной процедуры (если мы всё делаем через doit), проверка того, что переданы все обязательные для данной процедуры параметры (иначе возвращаем NOT_IMPLEMENTED), и дальше к примеру чтение из хеша всех необязательных параметров с присваиванием им значений по умолчанию


BZ>и далее: добавление нового необязательного параметра в процедуру — плюс одна строчка пустого кода. добавление новой процедуры — строчка в диспетчере плюс чтение всех параметров из хеша в локальные переменные


BZ>в общем, получаем куда меньше boilerplate, большую гибкость, в минусах — меньшая надёжность. например, компилятор не проверит, что вы добавили новый параметр hashable во все вызовы функции readfile. в принципе, это применение идей динамических языков к созданию интерфейсов


такое решение есть в COM и называется IDispatch.

BZ>отдельный вопрос — как этот Hash реализовывать на практике. хорошо, если вы работаете в рамках одного языка, где такой тип есть. если же у вас простой язык или многояызыковая среда, то Hash реализуется сериализацией в строку. разумеется, всё это — хеш+сериализация — не подходит для миллионов вызовов в секунду


Можно посмотреть как маршалятся IDispatch вызовы.



BZ>что скажете? конкретной задачей, которую я сейчас решаю, является разработка стандарта API для библиотек сжатия. чтобы кто-то мог написать либу на C++, я написал прогу на хаскеле, и они друг с другом жили, и я мог дальше расширять API, добавляя всё новые функции


Если на Windows, то COM.
Re[2]: Сервисы с расширяемым функционалом
От: BulatZiganshin  
Дата: 10.07.09 17:43
Оценка:
Здравствуйте, gandjustas, Вы писали:

BZ>>один вариант предлагает нам модель COM: расширение API — это добавление новых interfaces, под которыми понимаются набор binary calls. сервис объявляет поддерживаемые им interfaces, клиент выжимает из них всё что может. это работает, но мне такой подход не нравится своей монолитностью — представьте себе, сколько "стоит" добавление нового необязательного параметра (типа "по возмодности включи кеширование") в уже существующий binary call — объявляем новый interface с одним-единственным call, почти идентичным старому, реализуем его, переадресовываем старый binary call на новый. затем в клиенте проверяем реализацию интерфейса, и в зависимости от неё вызываем тот или иной метод.

G>Всего пара строчек, кроме того в WinAPI и COM часто применяются Reserved параметры.

1. объявить новый интерфейс плюс метод в нём — две строки
2. реализовать новый метод, плюс ещё объявить его — две строки
3. в клиенте проверка и вызов — ещё две

с пустыми/скобочными строками будет строк 15

reserved параметры потому и применяются, что расширять неудобно

G>Также один из способов версионирования API (также WinAPI и COM) — принимать структуры данных в качестве параметров, которые элементарно расширяются — в начеле передается размер структуры, который однозначно определяет версию. В случае ОО тоже самое достигается передачей наследника некоторого класса.


тонкость в том, что расширение структур поддерживает чисто версионное расширение. если в одной программе добавлен необязательный параметр readCaching, а в другой writeCaching, то в одной из них придётся реализовывать их оба. и решение на структурах выигрывает в скорости выполнения, но не проще в использовании — точно также надо заводить локальные переменные для необязат. параметров

BZ>>если у нас многоязычный проект, то не забудьте добавить прототипы нового интерфейса на всех языках. мрак!

G>Фигня, midl давно придумали.

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

BZ>>мне сейчас кажется, что наилучшим способом сделать расширяемый API является передача динамических ассоциативных таблиц


G>такое решение есть в COM и называется IDispatch.


точно, посмотрю



BZ>>что скажете?


G>Если на Windows, то COM.


пока искал midl/IDispatch, нашёл пару релевантных терминов — IDL и RPC. в частности, Protocol Buffers — Google's IDL

как ни странно, часто RPC решает ту же задачу как частный случай
Люди, я люблю вас! Будьте бдительны!!!
Re[3]: Сервисы с расширяемым функционалом
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.07.09 18:31
Оценка:
Здравствуйте, BulatZiganshin, Вы писали:

BZ>1. объявить новый интерфейс плюс метод в нём — две строки

BZ>2. реализовать новый метод, плюс ещё объявить его — две строки
BZ>3. в клиенте проверка и вызов — ещё две

BZ>с пустыми/скобочными строками будет строк 15

По сравнению с кодом для нового интерфейса — мелочи.

G>>Также один из способов версионирования API (также WinAPI и COM) — принимать структуры данных в качестве параметров, которые элементарно расширяются — в начеле передается размер структуры, который однозначно определяет версию. В случае ОО тоже самое достигается передачей наследника некоторого класса.



BZ>>>если у нас многоязычный проект, то не забудьте добавить прототипы нового интерфейса на всех языках. мрак!

G>>Фигня, midl давно придумали.

BZ>проблема с одной стороны в том, что я не очень осведомлён о стандартных велосипедах, а с другой — в том, что я использую малопопулярные языки и ос, типа хаскела и линукса

В линуксе даже близко похожего на COM нету.


BZ>пока искал midl/IDispatch, нашёл пару релевантных терминов — IDL и RPC. в частности, Protocol Buffers — Google's IDL

IDL — язык разметки интерфейса, применяется в COM. midl и аналогичные утилиты по idl описанию создают программный код.


BZ>как ни странно, часто RPC решает ту же задачу как частный случай

IDispatch — не RPC
Re[4]: Сервисы с расширяемым функционалом
От: BulatZiganshin  
Дата: 10.07.09 19:15
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>По сравнению с кодом для нового интерфейса — мелочи.


я не люблю писать boilerplate code

BZ>>проблема с одной стороны в том, что я не очень осведомлён о стандартных велосипедах, а с другой — в том, что я использую малопопулярные языки и ос, типа хаскела и линукса

G>В линуксе даже близко похожего на COM нету.

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


BZ>>пока искал midl/IDispatch, нашёл пару релевантных терминов — IDL и RPC. в частности, Protocol Buffers — Google's IDL

G>IDL — язык разметки интерфейса, применяется в COM. midl и аналогичные утилиты по idl описанию создают программный код.

idl — это interface description language, midl (microsoft idl) — это один из IDL. их много, посмотри страницу в википедии


BZ>>как ни странно, часто RPC решает ту же задачу как частный случай

G>IDispatch — не RPC

"RPC часто решают более общую задачу, чем IDispatch"


почитал ещё немного, и понял что thrift/protocol buffers — это не то. это как раз первый вариант — строгие описания интерфейсов. динамические интерфейсы — это из области xml/binary xml
Люди, я люблю вас! Будьте бдительны!!!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.