Вчера я зарелизил свой первый Python фреймворк. Нет, не еще один. Это в мире - еще один. А для меня пока что первый. И я допускаю, что он первый в своем роде. Это фреймворк для создания кастомных серверов. И создаваться они будут через конфиг. Ух, насоздаем сейчас...
Вначале был конфиг
Итак, конфиг. Поскольку к этому моменту фреймворк мы уже установили. А если нет, то это легко и просто делается командой:
pip3 install idewavecore==0.0.1
Это при условии наличия у вас Python 3.6+, интернета и компьютера.
Сам конфиг при этом выглядит примерно вот так:
# settings.yml
settings:
servers: !inject config_dir/servers.yml
db_connections:
sqlite:
host: ~
username: ~
password: ~
# default mysql 3306, postgresql 5432, sqlite don't need port
port: ~
# currently allowed: mysql, postgresql, sqlite
dialect: sqlite
# supported drivers:
# mysql: mysqlconnector, pymysql, pyodbc
# postgresql: psycopg2, pg8000, pygresql
driver: ~
# to use with sqlite this should be absolute db path
# can be empty to keep db in memory (sqlite only)
db_name: ~
charset: ~
Где !inject
- это специальный тэг для импорта других yaml файлов. Что очень удобно, если нужно разбить огромную yaml-простыню на набор аккуратных мини-конфигов.
# servers.yml
sample_server:
connection:
host: 1.2.3.4
port: 1234
# possible values: tcp, websocket
connection_type: tcp
# optional
proxy:
host: ~
port: ~
# possible values: tcp, websocket
connection_type: tcp
options:
server_name: Sample Server
is_debug: false
middlewares: !pipe
- !fn native.test.mock_middleware
- !fn native.test.mock_middleware
- !infinite_loop
- !fn native.test.mock_middleware
- !fn native.test.mock_middleware
- !fn native.test.mock_middleware
- !router
ROUTE_1: !fn native.test.mock_middleware
ROUTE_2: !fn native.test.mock_middleware
ROUTE_3:
- !fn native.test.mock_middleware
- !fn native.test.mock_middleware
- !fn native.test.mock_middleware
# optional
db_connection: sqlite
Здесь уже побольше тэгов.
!pipe
- это специальный тэг, который создает обертку над функциями в массиве. На данный момент все функции, используемые в разделе middlewares (о них - чуть ниже), должны быть обернуты в пайп. Вкратце - пайп выполняет поочередно переданные в него функции и пробрасывает в них необходимые параметры.
!infinite_loop
- это специальный тэг, который автоматически создает пайп из переданных функций и затем выполняет их внутри вечного цикла. Что может быть полезно для создания непрерывного соединения (например, по websocket).
!router
- это специальный тэг, который создает маршрутизируемый пайп. Фактически, создается словарь пайпов и какой из них выполнить, роутер определяет по специальному параметру (route).
И, наконец, !fn
- специальный тэг, который позволяет импортировать функцию (далее - middleware) по указанному пути. Мой фреймворк предоставляет некоторое количество миддлвэров, но можно написать свои - достаточно создать в корне своего проекта одноименную папку - middlewares - и далее создать там файлы с необходимыми функциями. Далее, чтобы использовать вашу функцию, достаточно будет вызвать тэг:
!fn <имя_вашего_файла>.<имя_функции>
И строка будет преобразована в миддлвэр. Название корневой папки (middlewares) указывать не нужно - оно будет подставлено автоматически при импорте. Если же вы хотите использовать нативные миддлвэры фреймворка (есть! я использовал три заимствованных слова подряд!), достаточно указать префикс native
в пути к функции, например:
!fn native.test.mock_middleware
В целом, большой акцент сделан именно на работу с конфигом.
Middle where
Миддлвэры - это место, где будет сосредоточена вся кастомизируемая логика вашего приложения. Фактически, это - центр. Сердце каждого отдельно созданного сервера.
В общих чертах, миддлвэр - это асинхронная функция, имеющая доступ ко всем хранилищам (о них - ниже). Каждая такая функция должна выглядеть примерно вот так:
from idewavecore.session import Storage, ItemFlag
async def sample_middleware(**kwargs):
global_storage: Storage = kwargs.pop('global_storage')
server_storage: Storage = kwargs.pop('server_storage')
session_storage: Storage = kwargs.pop('session_storage')
session_storage.set_items([
{
'key1': {
'value': 'some_tmp_value'
}
},
{
'key2': {
'value': 'some_persistent_value',
'flags': ItemFlag.PERSISTENT
}
},
{
'key3': {
'value': 'some_persistent_constant_value',
'flags': ItemFlag.PERSISTENT | ItemFlag.FROZEN
}
},
{
'key4': {
'value': 'some_constant_value',
'flags': ItemFlag.FROZEN
}
}
])
value_of_key3 = session_storage.get_value('key3')
Каждый миддлвэр имеет доступ к одному из трех типов хранилищ (storage). Хранилища бывают трех типов: глобальное (global storage), хранилище сервера (server storage) и хранилище сессии (session storage).
Глобальное хранилище связывает все части приложения между собой, позволяя серверам получить доступ к общим данным.
Хранилище сервера используется для хранения соединений и в основном используется для броадкаста.
Хранилище сессий создается персонально для каждого клиентского соединения и хранит данные в пределах этого соединения.
В основном в миддлвэрах будет использоваться именно хранилище сессии.
(После третьего раза я понял, что больше не могу так. Простите за такой топорный перевод термина. Я не придумал, как по-другому. Предлагайте свои варианты в комментариях - возможно, эту ситуацию еще не поздно исправить.)
Запускаем...
Теперь наконец пора применить то, без чего не обходится ни одно нормальное приложение на базе моего фреймворка - Assembler. Не совсем тот, но тоже производит сборку. Примерно так:
# run.py
import asyncio
from idewavecore import Assembler
from idewavecore.session import Storage
if __name__ == '__main__':
loop = asyncio.get_event_loop()
global_storage = Storage()
assembler = Assembler(
global_storage=global_storage,
# ваш путь к конфигу
config_path='settings.yml'
)
servers = assembler.assemble()
for server in servers:
server.start()
loop.run_until_complete(
asyncio.gather(*[server.get() for server in servers])
)
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
loop.close()
Запускаем в терминале - и можем лицезреть (при условии, что вы все сделали правильно) сообщения о том, что серверы запущены. Теперь к ним можно пробовать достучаться всеми возможными способами - браузер, curl, клиент mmo rpg игры...
Что теперь
А теперь я хочу сказать спасибо всем, кто прочитал мой пост до конца. Я буду благодарен за любые конструктивные замечания. Даже если мой фреймворк - не первый в своем роде, я хочу его максимально приблизить к этой планке.
lair
А зачем?
ivanuzzo Автор
Я ждал этого вопроса :) Не хотел раздувать статью. Изначально я писал python сервер для WoW. Но потом мне пришла идея сделать инструмент для создания серверов наподобие такого. Т.к. по сути код WoW серверов мало чем отличается. И мой фреймворк облегчает задачу в разработке таких серверов.
Потом я решил, что с учетом необходимости при WoW сервере иметь еще и веб-сайт, было бы неплохо, чтобы фреймворк позволял создавать вообще любые серверы. А с учетом архитектуры я могу создавать интересные вещи, например, веб-интерфейс для взаимодействия с вов. Или можно смотреть онлайн перемещение персонажей, мобов и так далее.
Идей вообще у меня еще много и если я увижу интерес к данной статье, я обязательно напишу еще. В частности, я планирую переписать свой WoW сервер, приведенный по ссылке в данном комментарии, на базе моего фреймворка.
lair
Ну так с ответа на него и надо начинать. Потому что непонятно, какую же задачу вы решаете.
Нет, понятнее не стало.
ivanuzzo Автор
Изначально фреймворк разрабатывался для создания MMO RPG серверов. Т.е. допустим, у меня есть набор уже ранее разработанных миддлвэров и при замене датасэта (т.е. данных, которые я используя для наполнения сервера — мобы, предметы и т.д.) и, возможно, замене некоторых миддлвэров мы можем получить сервер другой игры.
Зачем это надо? ну а зачем вообще люди код пишут :) Для саморазвития, наверное. Привлечения сообщества, поиска узких мест, оптимизации, масштабирования. И, возможно, в будущем — выпуска собственной MMO RPG игры.
lair
… и вам для этого понадобилась самописная конфигурация на YAML? Печально.
В том-то и дело, что люди пишут код с разными целями. И от целей зависит подход.
ivanuzzo Автор
Это анти-паттерн? Или есть проблемы с производительностью?
Просто я не вижу ничего плохого в том, чтобы имея разные конфиги — получать разный набор серверов, не изменяя при этом код.
lair
То, что вы фактически программируете на языке, который для программирования не очень предназначен.
"Плохое" в том, что вы, на самом деле, изменяете код. Просто этот код теперь не на питоне, а на ямле.
ivanuzzo Автор
Вообще, если посмотреть в сторону других фреймворков, которые используют yaml-конфиги, мы обнаружим немало интересного. Тот же Symfony2 (к примеру), использует yaml со всякими структурами наподобие imports. Или конфиг AWS (template.yml) со всякими доп. командами. Насколько я вижу, это — повсеместная практика и лично я по прежнему не вижу в этом ничего плохого. Не убедили.
lair
Удивительно, но код на питоне тоже "просто указывает порядок выполнения функций".
Но пишете-то вы на yaml.
Вопрос в том, для чего они их используют.
Что за "конфиг AWS"? Вы не про CloudFormation, случайно?
Что — "повсеместная практика"? Задавать поведение системы ее описанием в конфигурации?
ivanuzzo Автор
Да, он. Мы в темплейте объявляем AWS ресурсы.
В качестве примера возьму тот же AWS конфиг (aws cloudformation template.yml). Разве мы не меняем поведение системы, указывая policies, разные набор ресурсов и т.д.?
lair
Новые функции уже создаете — ваш "пайп" уже и есть такая функция. И переменная у вас уже есть — то, по чему вы делаете маршрутизацию.
Вы используете "конфиг", чтобы задать новый сервер и определить его поведение, собрав это поведение из набора разных элементов. Для меня это программирование.
Так вот. Во-первых, я за последние пару лет написал достаточно этих шаблонов, чтобы понять, что писать их, на самом деле, адски неудобно. А во-вторых, я был не единственным, кто это понял, и поэтому даже сам AWS потихоньку внедряет CDK, который, внезапно, сделан именно на языках программирования.
Мы в первую очередь задаем конфигурацию системы. Чем больше ее поведения переезжает в этот конфиг, тем сложнее им становится управлять.
ivanuzzo Автор
Про CDK — это очень интересное замечание! Мне нужно изучить, чтобы я мог в полной мере проанализировать обстановку.
Возможно. Я пока еще не натолкнулся на такие проблемы, поэтому не могу как-то парировать ваше замечание.
lair
Нет, не пора.
В ту же корзиночку: Pulumi.
Вы, скажем, не натолкнулись на проблему, что для CloudFormation-шаблонов нужен отдельный линтер? Что подсветка синтаксиса в них работает не очень, навигация еще хуже, а рефакторинг — вообще никак?
И то же самое будет с вашими конфигами: вы решили переименовать милдварь, и вам нужно придумывать какой-то тулинг, чтобы поймать все места, где эта мидлварь использовалась, до запуска приложения. Вы решили выделить часто используемый набор мидлварей в какой-то компонент — вы не можете сделать это автоматически.
(я уж молчу про типы параметров и возвращаемых значений — эта проблема, по крайней мере, свойственна всем конвеерным архитектурам, а не только конфигурируемым)
Кстати, любопытно посмотреть на пример хотя бы двух реальных middleware, используемых в вашей архитектуре.
ivanuzzo Автор
Спасибо, я гляну. Пока что начал смотреть aws-cdk и навскидку — хотя и переехали в пайтон, там по прежнему много строк. Ну т.е. по сути кусок конфига переехал в пайтон код. В чем суть удобства в таком случае? Со строками, на мой взгляд, проблемы где-угодно одинаковые: пропущенный символ может стать незабываемым дебаг-приключением на пол-дня.
Честно вам скажу, я не фанат AWS. Я с ним работал всего месяцев 8, на высоконагруженном проекте, но не в качестве архитектора, а в качестве пайтон дэва. И общался с AWS на базовом уровне — лямбды, легкие правки в template.yml, деплой, дебаг, бакеты, динамодб и прочие мелочи. Ну т.е. я понимаю, что для деплоя — это крутая штука, но вот работать с ним — это что-то невероятное. Очень неудобная в плане кодинга/настройки вещь.
Да, тут я с вами согласен. Это — хорошее замечание. Более того, я с этим столкнулся еще до релиза и думаю, как решить это минимальными потерями.
Например
lair
Нет, не согласен. Вы не генерируете никакие ноды. Их может никогда не быть.
В том, что можно ресурс запихнуть в переменную, и из других ресурсов обращаться к переменной. Необъявленные переменные прекрасно ловит линтер питона (и компиляторы для компилируемых языков). В том, что можно повторяющееся создание ресурса запихнуть в компонент, который положить в модуль, распространяемый средствами языка, и переиспользовать между разными проектами.
… может тогда не стоит его приводить как пример?
Вы знаете более удобную в плане кодинга/настройки облачную инфраструктуру?
Это инфраструктура, а не прикладной код. Но даже в этом примере хорошо видно, что у вас все взаимодействие завязано на строковые ключи и словари. Знал я один аналогичным образом устроенный конвеер, OWIN, так вот, прикладные разработчики с ним всегда работали через типизированные обертки, потому что иначе уследить за всеми этими константами невозможно. А у вас даже констант нет, вы
kwargs.pop('session_storage')
в каждом методе повторяете зачем-то.Но, повторюсь, эта проблема не уникальна для вашего решения, вы просто тоже ее повторили.
ivanuzzo Автор
Ну а в template есть !Ref и необъявленные ресурсы отлавливаются хуками aws deploy. В чем тогда разница?
Я считаю, что стоит, раз у меня есть опыт работы с ним. Я ведь не умничаю, а правда думаю то, что пишу — и с благодарностью принимаю наставления от более опытных людей :)
Прикладной код появится чуть позже. Как я писал ранее, я планирую переписать свой wow сервер на базе моего фреймворка, чтобы определить, какие вообще есть проблемы и подводные камни.
Да, тут я согласен. Есть такая проблема. Я пока не придумал, как грамотно переместить все строки. Чтобы это было еще и удобно.
lair
DOM — это действительно javascript-представление HTML дерева. Но это не значит, что HTML-узел обязательно становится узлом DOM. Представьте себе браузер без поддержки JS, и вы поймете, почему.
HTML — это "всего лишь" язык разметки. Дальше он отображается визуализатором или редактором, или парсится конвертером, или еще что-то.
Ровно в том, что хуки aws deploy немножко сложнее встроить в CI-процесс (и локальную разработку), нежели линтер для питона или компилятор для C#. И в том, что IDE по
!Ref
ходит немножко хуже, чем по объявлениям и использованиям переменных.Я вам настоятельно не рекомендую публиковать фреймворк до появления какого-то проекта, хотя бы игрушечного, сделанного с его помощью. Потому что пока вы не соберете проблем, возникающих в прикладном применении, вы не понимаете слабых (и сильных) мест вашего фреймворка.
Еще одно свидетельство того, что ваш фреймворк к релизу не готов.
Удивительно, конечно, что эта проблема не остановила вас на, не знаю, второй написанной мидлвари. Меня бы точно остановила, и пока я бы ее не решил, я бы дальше не двигался.
(Точнее, никакого "бы", я по этой дорожке ходил сильно больше одного раза. Только, правда, не в питоне, поэтому не уверен, насколько мои решения для вас применимы.)
ivanuzzo Автор
можете объяснить, что в данном контексте означает «немножко»? И какая IDE приведена в качестве примера? PyCharm?
хорошее замечание. Вообще, 0.0.1 была опубликована с целью сбора как раз таки проблем. Т.е. я никого не заставляю силком устанавливать фреймворк — а на pypi (как и на github) вроде пока нет ограничений по заливке. Моя основная цель как раз — привлечь сообщество. Вот с вами, например, у нас получилась интересная и полезная (по крайней мере, для меня) дискуссия. Я бы вряд ли обратил внимание на многие проблемы, если бы продолжал разрабатывать тихо сам с собой, как я делал примерно год до публикации фреймворка. До этого я его переписал с нуля несколько раз и было произведено множество экспериментов. А wowcore я начал писать еще осенью 2018. На хабре есть несколько статей, где я рассматривал проблемы оптимизации python кода (как раз оттуда)
lair
Нет, не создаем.
В данном примере "немножко" означает, что за (приблизительно) два года работы с CloudFormation я так и не смог ни настроить для него CI-верификацию, ни поддержку в редакторе за пределами подсветки YAML (заметим, именно базового YAML, а не специфических команд CFN). Для питона у меня есть и то, и другое.
VS Code.
Точнее, так: в качестве того, как надо, я рассматриваю Visual Studio + ReSharper или Rider (как несложно догадаться, не для питона, а для C#). Для питона, с которым я работаю в VS Code, я смог получить, не знаю, где-то треть от этой функциональности (или 1/5), но мне больше и не надо, потому что я с ним и работаю в десятки раз меньше. А для CloudFormation YAML — ноль, не считая подсветки, хотя с ним я работаю намного больше, чем с питоном.
ivanuzzo Автор
lair
Нет, HTML рендерится с помощью какого-то программного кода. Но когда мы пишем HTML, мы никак не контролируем код, который его рендерит, и этот код не является нашей задачей. Нашей задачей является отображение.
Для VS Code тоже есть плагины. Но они, как говорилось выше, не справляются на том уровне, на котором мне нужно.
lair
Я, пожалуй, с другой стороны зайду: а что хорошего в том, что для получения "разного набора серверов" надо править не
py
-файл, аyaml
-файл?ivanuzzo Автор
Ну как минимум, если мне могут понадобиться разные варианты — мне придется держать пять папок с разными py-файлами. Либо одну папку — с пятью разными конфигами. Мне кажется, разница — есть.
Но все зависит, конечно, от набора задач. Они могут быть очень специфические, поэтому вопрос спорный.
lair
Это какая-то пренебрежимая разница, надо вам держать пять файлов в одной папке или пять папок с одним файлом в каждой. И это не говоря о том, что в питоне, если я не ошибаюсь, динамическая загрузка тоже есть, а, значит, ничего вам не мешает держать файлы там, где вам хочется.
ivanuzzo Автор
Это действительно пренебрежимая разница. Вообще, основное преимущество использования yaml конфига в данном случае — это целостность в контексте управления приложением. Т.е. у меня один файл, где описан весь функционал. Взглянув на него, можно понять, как работает конкретное приложение. А компактность yaml формата дает +1 к читабельности
lair
И что вам мешает сделать то же самое в одном
py
-файле?(я не буду сейчас вдаваться в философский вопрос, нужно ли в одном файле описывать адреса серверов и порядок функций в конвеере, это вам в 12 Factor App)
Собственно, в каком-нибудь ASP.NET Core это давно и прекрасно делается в классе
Startup
, он же конвенционально файлStartup.cs
, и прекрасно работает.ivanuzzo Автор
lair
… и почему этим местом не может быть
py
-файл?А вот это то место, где мы с вами радикально расходимся во взглядах. Потому что для меня миддлвари — это и есть приложение.
ivanuzzo Автор
И для меня тоже. Я лишь имел ввиду, что в yaml-конфиге я влияю только на то, какие миддлвэры будут использованы и в каком порядке. Пайп, роутер, инфинит луп — это по сути тоже миддлвэры.
lair
И почему это не может быть просто два
py
-файла?ivanuzzo Автор
lair
Ну то есть никакого технического смысла в этом нет, это проистекает только из ваших субъективных ассоциаций с типами файлов?
ivanuzzo Автор
Пока я один над проектом работаю — да, сугубо мои ассоциации. А вы считаете, что данный подход некорректный? (Если да, то можете объяснить, почему?)
lair
Да, считаю. Весь тред выше — это объяснение того, почему.
TL/DR:
Проще говоря, недостатки есть, а преимуществ, кроме соответствия вашим ассоциациям, нет.
kornerr
Мне гораздо интереснее узнать, какой смысл вам мучать автора всеми этими вопросами (и тратить на это своё время)?
lair
Во-первых, есть вероятность, что автор увидит ошибочность своего подхода.
Во-вторых, и это более важно, есть вероятность, что аудитория увидит ошибочность такого подхода.
Проще говоря, смысл ровно такой же, как и в любой другой публичной дискуссии.
kornerr
Я лично вижу то ли желание приземлить (зачем?), то ли какое собственное приземление, которое не даёт вам попробовать что-нибудь своё рискнуть и наваять.
lair
Не знаю, что вы понимаете под "приземлением".
ivanuzzo Автор
Ну почему, при наличии конфига в py-файле все константы будут храниться в памяти, а при загрузке из yaml-файла мы загружаем необходимые данные и выгружаем файл из памяти. Исправьте меня, если я заблуждаюсь.
Пока нет. Благодаря вам, я взглянул на фреймворк с другой стороны. И на документацию к нему, и на его проблемы. Я уже в процессе переосмысления некоторых вещей. И хотя я хочу дойти до конца в плане работы с приложением на базе idewavecore через yaml-конфиг, я допускаю, что возможно релиз версии 1.0.0 будет базироваться уже на других принципах.
Подводя итог вышесказанному, я бы хотел добавить ваш никнейм в раздел благодарностей README моего фреймворка. Если вы не будете сопротивляться.
P.S. я за продолжение дискуссии.
lair
… и почему вы думаете, что я — или любой другой разработчик, который пришел воспользоваться вашим фреймворком — заинтересован менять свои привычки под ваши?
В контексте первопроходцев очень поучительно сравнивать истории Скотта и Амундсена. Первопроходцами были оба. Но с полюса вернулась только одна команда.
Все еще не понятно, зачем это делать, когда есть языки, где это уже реализовано. Вы все время будете играть в догоняющего (как, собственно, в него же играла команда поддержки CloudFormation templates).
Если уж вы считаете память, не забудьте посчитать память, потраченную на модуль загрузки yaml-файла. И, в частности, на все константы, которыми вы описываете узлы в этом файле.
Это такие пренебрежимые спички получатся в любую сторону, что их не стоит даже упоминать.
ivanuzzo Автор
Ваш пример только подтверждает сказанное мной насчет первопроходцев. Еще Аристотель (Метафизика) писал про то, что опыт возможен благодаря памяти (ну т.е. надо обладать каким-то датасэтом, чтобы делать правильные выводы). А из того, что я прочитал об экспедиции Скотта, он, к примеру, в отличие от Амундсена (который запечатал баки с топливом), не знал про «ползучесть» топлива. И там много нюансов было. Но тем не менее, роль первопроходцев велика в первую очередь потому, что на основе их открытий и на основе предыдущего опыта делаются либо новые открытия, либо улучшаются имеющиеся.
Создавая фреймворк, я рассуждал с точки зрения удобства подхода к разработке типов серверов, с которыми лично я имел дело. Опять же, когда у меня будет готов прототип wow-сервера (и он будет удачен) — я напишу об этом пост.
Это надо будет подебажить. Как и константы. Мне теперь стало интересно, остается ли то и другое в памяти постоянно.
lair
Вот и вопрос: а зачем, собственно? Я обычно пробую новые технологии, потому что есть какая-то причина.
А еще он, к примеру, взял с собой пони вместо собак.
Угу. Проблема, однако же, в том, что люди иногда считают себя первопроходцами, и на этом основании отвергают существующий опыт.
Это снова подтверждает мой ранний тезис: вы, фактически, удовлетворяете свою субъективную потребность. Это, конечно, приятно, но вот польза этого для остальных неочевидна.
mvv-rus
Вставлю свои пять копеек в вашу дискуссию — больно уж спорный пример вы привели.
«Прекрасно работает» — это ведь, как ныне принято считать, ещё не все, что нужно от программы. Вообще-то любая хорошо отлаженная программа прекрасно работает, но не любая — хорошо читается, понимается, модифицируется и отлаживается.
И вот как раз ASP.NET Core, на мой взгляд, никак не может служить положительным примером описания функционала программы, который бы удовлетворял этим пожеланиям.
Например — потому что слишком много в нем избыточных элементов: да хоть тех же скобок вокруг параметров методов, которые нужны, потому что описателями конфигурации по факту служат вызовы методов.
А ещё — потому что принятый в ASP.NET Core стиль поощряет писать длинные и сложные, не охватываемые одним взглядом «фразы» — цепочки вызовов методов-описателей конфигурации (через точку). Причем вызовы в этих цепочках могут включать в себя (и часто включают) не только короткие константы и выражения, но и громоздкие лямбда-функции — многострочные, с фигурными скобками и даже с другими лямбда-функциями в вызовах методов внутри себя. А ещё в цепочке вызова по дороге запросто может смениться тип объекта, с которым идет работа, и часть цепочки будет работать уже с другим классом, а не с тем, с которым работа шла изначально. Например, смотрите вы длинную цепочку вызовов методов IServiceCollection, описывающюю конфигурирацию контейнера сервисов, и по невнимательности упускаете, что в этой цепочке парой строк выше того места, где вы сейчас смотрите, стоял вызов не AddOptions(), а AddOptions<какой-то-тип>() (не правдали, это несложно спутать?).
И внезапно для себя вы уже смотрите на описание конфигурирования не контейнера сервисов, а параметра(Option) это самого «какого-то-типа» — а названия-то методов там есть весьма похожие, сразу в глаза разница может и не броситься (особенно если вы ещё не все эти методы назубок знаете).
Так что потребность в хорошо читаемом средстве записи конфигурации приложения — она IMHO есть, и она отнюдь не удовлетворена. Другое дело, насколько эту потребность удовлетворяет работа автора — об этом я, не будучи ни знатоком YAML, ни знатоком Python, судить не рискую
lair
Ну во-первых, я привел это как пример того, что все "описание" приложения может поместиться в одном файле с кодом, а не того, что это лучший подход в мире.
Во-вторых, "хорошо/плохо читается" — это субъективно. Вам плохо читается, мне хорошо читается. Субъективно это. Я вот, например, с описанными вами проблемами не сталкивался.
При этом когда "описание" сделано на том же языке, что и сама программа, вы получаете вполне объективный бонус в виде работающего инструментария — того же самого, которым вы пользовались при, собственно, разработке программы. И этот инструментарий может, например, указать вам, что вы написали несуществующую команду, показать описание команды при наведении мышкой и полную имплементацию — при Ctrl-Click.
Ну а в-третьих, да, действительно запрос на хорошо читаемую конфигурацию — он есть. И действительно, для небольших несложных примеров YAML обычно выигрывает у языков программирования в читаемости (как бы субъективно это ни было). Проблемы начинаются тогда, когда ваши примеры усложняются. В YAML, будучи тем, кто пишет конфигурацию, вы не можете пойти и сказать: теперь вместо
я буду писать
Эту функциональность вам должен предоставить тот, кто разрабатывал чтение конфигурации. А это значит, что ваши возможности по улучшению читаемости резко ограничены.
mvv-rus
Что это пример — это сразу было понятно. Но этот пример сразу же проявил и почти неизбежный недостаток такого подхода — описывать конфигурацию приложения, набранного из компонентов, на обычном языке императивного программирования: такой язык для этого приспособлен не наилучшим образом, не для того он был изначально предназначен.
Тут спорить не о чем — читает-то конкретный субъект. Обсуждать имеет смысл, насколько хорошо или плохо это для массы — но это явно не стоит делать в комментариях к чужой статье. Так что почему конфигурация в ASP.NET Core плохо читается лично мне — это тоже в качестве примера.
Или — не получаете. Например, если вы смотрите исходный код библиотеки где-нибудь в интернете — бонус от IDE вы не получаете, и надо собственными глазами разбираться: для объекта какого типа вызван метод, если в метод передается делегат — каковы типы параметров этого делегата (лямбда-функция ведь типы своих параметров скрывает просто отлично) и т.д.
Что YAML в макросы не умеет — согласен, что это реально плохо для данного применения: сходных наборов компонентов в программе может быть реально много.
В общем, из обсуждения уже проглядывает вывод — проблема с читаемостью конфигурации хоть и есть, но обсуждаемое то-что-должно-стать-фреймворком по факту ее решает «не слишком хорошо».
Думаю, на этом дискуссию пока можно закончить.
lair
Проблема в том, что никакой язык для этого изначально не был предназначен. Будем еще один внешний DSL плодить?
В том-то и дело, что получаете.
Точнее, как. В худшем случае, вы получаете ситуацию не хуже, чем с обычным текстовым файлом. Прочитать все равно можно, а имена надо выбирать говорящие. Но в среднем случае (aka GitHub) вы уже получаете синтаксическую подсветку, а иногда и навигацию. В хорошем случае (VS Code Online) вы получаете то же, что и с обычным кодом.
Но пойнт в том, что когда с этим кодом работает разработчик, у него есть IDE. И в этом случае преимущества прямо сияют. А в случае с самописной конфигурацией везде одинаково плохо.
В том-то и дело, что это не "макросы". Это обычное структурное программирование, позволяющее уменьшать сложность.
Обсуждаемое даже не задумывалось о читаемости. Я такой задачи нигде не видел.
mvv-rus
А почему бы и нет? Делают же ведь так для других целей. Вон, MS впихнула свой LINQ в .NET — и я что-то не слышал, чтобы кто-то огорчился (Хотя LINQ, насколько я заметил, используют чаще не как язык со своим ситаксисом, а как набор методов расширения).
А в где-то в 80-е — 90-е IBM примерно то же самое IBM (а вслед за ней и другие) проделывала и с SQL: был тогда такой Embedded SQL.
Я как вот как раз про наихудший случай и писал. Конкретно — про использование исходных текстов в качестве справочной информации, замены документации. А в нынешнее время торжества открытого исходного кода (и, как следствие — снижения внимания к качеству документации) этим приходится заниматься все чаще и чаще.
Ну а подсветка синтаксиса — она и в текстовых редакторах нынче есть (даже в древнем Far manager). Только вот прослеживать зависимости, что и откуда, в сложной конфигурации она мало помогает.
Нет, не одинаково. В отсутсвие подпорок от IDE приходится решать задачу читаемости другими средствами. В частности — синтаксическими. Другой вопрос, как это получается в реальности, но стимул решать есть. Ну и «самописный» — это в наше время развития плагинов тоже не приговор, инструмент для работы с описанием конфигурации сделать-то можно. Только, наверное, это уже — не про обсуждаемый «фреймворк».
Структурное программирование — это не отсюда, это — форма императивного программирования. А тут мы имеем, если смотреть на семантику, декларативные элементы: в вашем примере мы объявляем, что фраза AddOptions должна пониматься как перечисленный ниже набор фраз. А то, что конкретно в ASP.NET это приходится делать вызовом подпрограммы, которая там тоже что-то вызывает внутри себя — это уже издержки реализации декларативных элементов с помощью императивного языка.
Ну, насчет «не задумывалось» — я бы так категорично не говорил, ибо телепатическими способностями не владею. Но то, что про это толко в статье не написано — это факт.
И это кстати — основание свернуть дискуссию: она ушла далеко в сторону от обсуждения конкретной статьи.
Так что по этому вопросу имеет смысл написать отдельную статью, и обсудить такие вопросы там: там и аргументацию можно изложить почетче, а, глядишь, и участников дискуссии станет побольше, чем только мы с вами тут.
lair
LINQ — это не внешний DSL, а внутренний. Для него изначально была полная языковая поддержка.
Я это делаю либо на гитхабе, где уже есть навигация, либо локально в IDE, где тоже есть навигация. Поэтому ваш "наихудший случай" со мной не происходит практически никогда.
Не, нету. Есть стимул бросить и уйти туда, где есть поддержка.
Можно, но это та самая позиция догоняющего.
Неее, это вы объявляете. А я хочу иметь именно нормальные возможности программирования. Валидировать состояние, принимать решения и так далее.
mvv-rus
Поддержка поддержкой, однако синтаксис и семантика его всесьма чужеродны императивным языкам, вроде C#. Так же, как и встроенного SQL — языкам, куда он встраивался: COBOL, FORTRAN, C…
Впрочем, я если я правильно понял, вас беспокоят прежде всего удобство работы, а не семантический разрыв между частями программы? Тогда да, дла этого интегрированность — это важно, и большая фирма об этом позаботится всяко лучше, нежели всякие сторонние разработчики со своими доморощенными фремворками.
Ну что тут сказать? Вы — молодец, но почему-то не всем так везет: я даже тут, на Хабре видел жалобы, что людям такое мешает code review делать, потому что на телефоне IDE нет.
Есть разные пути. И если вы предпочитаете ходить только по хоженным дорожкам, то есть и люди с другими предпочтениями. И нельзя сказать, что они заведомо неправы — иногда на нехоженых дорожках можно найти что-то очень ценное.
Да, но не всегда это заведомо плохо. В «позиции догоняющего», например начинали свой старт такие продукты — будущие лидеры, как IBM PC, видеокарты NVidia, браузеры Internet Explorer и Google Chrome…
Не, в данном случае это не я объявляю — это разработчики .NET объявляют. То, что вы написали — это изложенное в других терминах содержимое исходного кода метода OptionsServiceCollectionExtensions.AddOptions из библиотеки времени выполнения .NET (только там объявления содержат семантику проверки на дубль и объявление реализации ещё одного интерфейса, кроме перечисленных). И это — именно объявления: они пока записываются «куда-то» и будут обработаны (описанные в них реализации — добавлены в контейнер сервисов) позднее, когда придет время.
Желание понятное. Но другой стороной этого желания является повышенная сложность его реализации, по сравнению с тем, чтобы просто сказать, что «будем использовать то-то и то-то» (декларативный подход).
Впрочем, некоторые аспекты такого желания можно удовлетворить и при декларативном подходе: к примеру, XML можно провертить на соответствие его схеме. Другое дело, что в возможности выбранных авторам статьи средств реализации это не входит.
PS Как-то дискуссия сильно уж оторвалась от предмета статьи. Так что продолжу обсуждение затронутых тем как-нибудь попозже, при случае.
lair
C# — мультипарадигменный язык. Я лично не люблю query syntax, и пользуюсь методами, но это на вкус и цвет.
А где там семантический разрыв?
Где-то что-то перепуталось. Разработчики .NET (хотя, на самом деле. не .NET, а Extensions, но не важно) сделали обычный метод-расширение. Он "декларативен" приблизительно настолько же, насколько любое другое расширение.
А это, кстати, просто неправда. Они добавляются в контейнер именно сейчас (это хорошо видно по тому, что, скажем, следующий
TryAddX
уже ничего не добавит). Другое дело, что контейнер пока не собран, это следующий этап.Странно, для меня это выглядит ровно наоборот: чтобы сделать то, что мне нужно, не надо ничего реализовывать, надо просто взять готовый язык программирования. А "просто сказать" требует разработки DSL, к которому нужен синтаксис, парсер, утилиты и так далее.
… и это никак не поможет понять, что в нем указана существующая в исполняемом коде функция.
mvv-rus
Между декларативным и императивным подходами: семантика у них все-таки разная.
Однако метод этот по факту содержит набор деклараций вида:
«для реализации интерфейса IXxx использовать вновь созданный экземпляр класса Xxx (или заранее созданный объект, или результат возвращяемый указанным делегатом — есть и такие варианты), и экземпляр этот должен создаваться тогда-то и тогда и существовать столько-то и столько-то».
Что это по сути, если не декларация?
IServiceCollection — это никоим образом не контейнер сервисов: интерфейс контейнера сервисов IServiceProvider в объекте, которыый его реализует, отсутствует, а потому получить оттуда реализацию сервиса для каких либо надобностей никак нельзя.
Он предтавляет собой всего лишь список деклараций, о том, что должно в будущем оказаться в в контейнере сервисов.
Ну, и немного занудства: в .NET 5 Options — это уже часть рантайма, и даже в исходных текстах соответсвующий набор файлов перенесен из репозитория extensions.
Предполагается, что DSL и средства работы с ним вам тоже предоставят. Это снимает ваше возражение?
Я же говорю — «некоторые аспекты». Не все — ибо нет в мире совершенства. Кстати, точно так же можно нарваться на несуществующую функцию, и безо всякого дополнительного языка — работая, к примеру, с ASP.NET MVC.
lair
А где вы там нашли декларативный подход?
Я, в таком случае, не понимаю, как вы отделяете декларацию от инструкции.
У нас с вами терминологическая разница. Я называю
IServiceCollection
контейнером, аIServiceProvider
— провайдером. А вы называете первое декларацией, а второе контейнером.Но суть от этого не меняется. (Прямые) операции над
IServiceCollection
выполняются сразу же, а не отложено.Когда предоставят, тогда и снимет возражение. За весь мой опыт работы этого не произошло ни разу, сколько бы ни обещали.
"Нарваться" можно на что угодно. Это не повод пренебрегать существующими инструментами.
mvv-rus
Хороший вопрос, с решения которого надо было бы, вообще-то, было начинать. Декларация, в моем понимании — это описание того, что нужно сделать, без детализации как сделать. И мне нравится определение декларативного программирования из английской Википедии: «a style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow»
В данном случае это практически так и control flow в AddOptions() является чисто вспомогательным: он состоит в занесении отдельных деклараций (о том, какие сервисы будут доступны через IServiceProvider) в список этих деклараций (IServiceCollection), и нужен он там только из-за особенностей языка: иначе в C# сложно сделать создаваемый в несколько приемов список деклараций.
Спасибо, что обратили внимание. Дальше, во избежание путаницы, буду использовать названия интерфейсов.
Операции над IServiceCollection — это операции над списком деклараций. В основном — простое линейное занесение в список, семантически это — полный аналог написания списка в файле. Кое-где — условное включение в список, но средства типичного декларативного языка сделать такое условное включение тоже обычно позволяют. То есть результатом всех этих операций является список деклараций. И особенные возможности императивного языка тут не используются, согласны?
ОК, значит вы не отвергаете возможность получния пользы от такого рода инструментов.
Никто не предлагает вам пренебрегать. Наоборот, вам предлагают расширить круг инструментов. Возьмем тот же MVC — в его лице вы получили новый инструмент: динамическое определение вызываемой подпрограммы обработки запроса по соглашению об именах.
lair
На заборе тоже пишут. А в реальности… Что декларативного в записи
orders.Select(o => o.Amount > 100)
?Эмм. Вы считаете, что операция
Add
на коллекции — это декларация, да?Это просто операции над коллекцией. Что в ней — уже не важно. Они императивны по самое не знаю что.
А потом мы выполняем другую операцию над этой коллекцией и получаем третий объект — провайдер. Тоже императивная операция.
Выглядит как обычный паттерн builder.
Я рекомендую вам задуматься, что порядок операций над
IServiceCollection
имеет значение. Это вы называете "вспомогательным control flow"?Что такое "особенные возможности императивного языка"?
Нет, не отвергаю. Я просто говорю, что сейчас их нет.
Расширить — это когда мне предлагают что-то в дополнение. А тут мне предлагают отказаться от конфигурации через код, которая обладает определенными достоинствами, в обмен на конфигурацию через YAML, которая никакими объективными достоинствами в данной реализации не обладает. Не вижу никакого расширения.
Это не MVC, а роутинг.
mvv-rus
М-м, а вы точно знаете, как это будет выполнено? В частности, если orders реализует IQueryable<тип-о>, то для получения результата вместо банального перебора провайдером данных может быть выполнен эквивалентный запрос на другом языке запросов (например — на SQL): лямбда в C# может, как известно, быть преобразована в дерево выражения, а оно, в свою очередь — транслировано провайдером на нужный язык запросов. И выполнять ваш запрос будет, к примеру, SQL сервер, спрятавшийся за orders — а вы получите только результат. По-моему, это весьма декларативно: вы написали на C#, что вы хотели получить, а результат вам (внезапно) предоставил своим, более эффективным, способом понятливый ORM с SQL Server'ом на пару — хотя вы в момент написания лямбды, возможно, даже не задумывались об их существовании.
И, кстати, вы, наверное, имели в виду orders.Where(o => o.Amount > 100)? Иначе мне трудно предположить, зачем вам этот результат — IEnumerable<Boolean>, т.е., последовательность непонятных true и false. ;-)
В контексте стадии конфигурирования ASP.NET смысл вызова Add обычно — роль разделителя, типа перевода строки и пробелов в ней: так здесь принято оформлять строку конфигурации, операция Add самостоятельного смысла не несет, это boilerplate.
Вы зря сосредотачиваетесь на способе создания списка деклараций — он не важен. Имеет значение сам список, а способ его создания — это некий ритуал, комбинируемый из фиксированных и практически неизменных частей.
Операция императивная, но мы эту операцию не выполняем. Она выполняется библиотекой где-то там за сценой, хотя и по нашей общей команде (которая тоже входит в ритуал). А прикладной программист на ASP.NET может даже не подозревать о существовании этой операции и, при этом, вполне успешно работать: он просто следует ритуалу — описывает конфигурацию, создает хост для веб-приложения командой Build и дальше запускает его одним из предусмотренных для этого способов. Короче, то, что надо программировать императивно — все уже запрограммировано до нас, и потому я предлагаю ограничиваться рассмотрением только тех операции, которые делает прикладной программист.
Я когда это писал, предполагал не ограничиваться рассмотрением фреймворка idewavecore (автор просит называть его так, см. ниже), а обсудить и принципиально возможные гипотетичекие решения на базе DSL-фреймворков: как я понял, вы от них принципиально откзывались. Но если вы отказываетесь от них не принципиально, а чисто в виду отсутсвия подходящих, и даже если вы вообще не верите в возможность существования подходящих, то тут дальше обсуждать нечего, за неимением предмета обсуждения.
Нет, это именно MVC. Не знаю, как точно это реализовано в Core (полагаю, что сходным образом), но в Framework вся магия (там есть много точек, в которые можно вмешаться) выбора класса контроллера, создания его экземпляра и последующего выбором метода действия начинается в обработчике маршрутов из состава MVC — MvcRouteHandler: именно его надо указывать при ручном создании объекта маршрута для приложения MVC.
А роутинг — это общий компонент, в контексте работы MVC он только разбирает путь в URL и выбирает обрабочик маршрутов.
lair
Нет. Точно так же, как я не знаю, как будет выполнено
Logger.Log
илиService.Invoke
. Вот только это для меня называется "сокрытие информации" (иногда это же называют инкапсуляцией, но вокруг этого термина уже есть споры), а совсем не декларативным программированием.Но если
smth.Select(projection)
для вас декларативное программирование, то C# давно и прочно декларативный язык, и обсуждать действительно нечего.Множество гипотетических решений на базе DSL-фреймворков бесконечно. Что конкретно там обсуждать, и, главное, зачем?
Вы нечувственно перешли от MVC к ASP.NET MVC, хотя это слегка разные вещи.
Но даже в ASP.NET у меня есть "динамическое определение на основании соглашения" безо всякого MVC. Самописное, больше чем одним способом.
ivanuzzo Автор
mvv-rus
Подробности, почему эти высказывания, к сожалению, справедливы, вам более менее объянил lair, и мне здесь ему возразить нечего (как, в общем-то, и добавить).
Единственное, что я могу сделать для вас — это называть ваш труд тем словом, которым вы желаете.
Скажите название вашего фреймворка, и я обещаю называть его в дальнейшем только так, как вы пожелали — если это, конечно, не приведет к путанице.
ivanuzzo Автор
название моего фреймворка — idewavecore. Можно idewave-core или idewave core.
мне тоже нечего возразить. Но это означает только одно — мне нужно больше знаний и я понял, в каких областях. И я уже над этим работаю. Я постараюсь, чтобы новая версия idewavecore была безупречной.mayorovp
А что вам мешает держать одну папку с пятью разными py-файлами?
kornerr
Декларативное программирование однозначно вещь. nginx и swiftui как примеры.
lair
Декларативное программирование, действительно, вещь. А вот конфигурация вместо программирования — "ну, такое".