Как стать автором
Обновить

Альтернативная архитектура СУБД и подход к разработке приложений

Время на прочтение22 мин
Количество просмотров11K
Всего голосов 21: ↑12 и ↓9+3
Комментарии64

Комментарии 64

"еще один конструктор", еще один стандартный набор вопросов:


  1. что с использованием удобной мне системы управления версиями всех артефактов разработки?
  2. что с автоматизированным тестированием и CI/CD?
  3. что со средствами рефакторинга?

И еще я поискал в статье, но что-то не нашел: а как же и на каком языке писать пользовательский код (например, мне надо, чтобы при смене статуса с А на Б и еще пяти условиях происходил такой-то набор действий)?

… в эту же кучку типовых вопросов: а что вообще с коллективной работой и переносом решений между средами (разработческая/тестовая/боевая)?

Я проглядел руководство разработчика, и похоже этого просто нет.
Можно делать все, что умеет конструктор Интеграла, плюс можно использовать HTML-шаблоны и класть на них JS, плюс по каким-то событиям можно дергать внешние УРЛы.

Ну тогда до гордых званий "технологическая платформа" и "информационное ядро" еще прыгать и прыгать. Я уже давно не встречал в реальных бизнес-приложениях end-to-end-задачи, где можно было бы обойтись без пользовательского кода.

По руководству разработчика выглядит так.
Нужно создать ОТЧЕТ все сущности со статусом А.
В нем создать волшебную колонку, которая присвоит значения
image

Не решает задачу "по нажатию кнопки "выставить счет" проверить n условий, слазить во внешний сервис за налогами, создать счет, сформировать в нем строки, сгенерировать ссылку на оплату".

Но подождите, написано же "разумеется, при желании вы можете вмешаться и скорректировать работу ядра в той части, которая касается бизнес-логики". И как же?

Еще есть экспорт и импорт структуры и данных. То есть, можно набросать модель, выгрузить её и загрузить в другой экземпляр базы.

Как говорится АААААА


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

Кажется, забыли одну важную деталь: "оперируя терминами бизнеса", выраженными средствами Интеграла. В этот момент внезапно появились накладные расходы на трансляцию из бизнеса в Интеграл (и обратно), а вся формулировка перестала отличаться от любого DSL.

Это не конструктор из кубиков, это конструктор самих кубиков. То есть, по вашим трем пунктам: разработчик всё это может сделать сам здесь же, в удобном ему виде.
Версии и тест-план с соответствующими скриптами можно хранить в этой базе, как в любой другой. Пульт управления придется сверстать, да, как в любой другой среде разработки.

Как я оговариваюсь в статье (пару раз), пока сервис не предлагает наработок по визуализации всего этого, а предоставляет чистое поле, как если бы вы взяли Python, PostgreSQL и Apache, но упрощает работу с базой данных и разворачивание самой среды.

Пользовательский код реализуется Запросами, которые могут многое, в том числе запускать другие запросы по заданным событиям (смены статуса А на Б). Запросы можно запускать кучей способов, в том числе из интерфейса пользователя, собранного согласно процессу и параметрам, описанным в базе данных.
То есть, по вашим трем пунктам: разработчик всё это может сделать сам здесь же, в удобном ему виде.

Как я сделаю хранение версий в git? Это удобный мне вид.


Пульт управления придется сверстать, да, как в любой другой среде разработки.

Вот как раз "в любой другой среде разработки" это уже есть из коробки. Окей, не в любой, но в разумном большинстве.


Пользовательский код реализуется Запросами, которые могут многое, в том числе запускать другие запросы по заданным событиям (смены статуса А на Б).

Приведите пример "запроса", реализующего описанную выше функциональность:


  1. по нажатию кнопки "выставить счет"
  2. проверить n условий (валидность заказа)
  3. слазить во внешний сервис за налогами
  4. создать счет с учетом налогов и дисконтов и сформировать в нем строки
  5. сгенерировать ссылку на оплату
  6. отправить ссылку на email клиента

Как я оговариваюсь в статье (пару раз), пока сервис не предлагает наработок по визуализации всего этого, а предоставляет чистое поле, как если бы вы взяли Python, PostgreSQL и Apache,

Если бы я взял питон, у меня были бы текстовые файлы, которые я могу положить под VCS, на которых я могу гонять написанные мной же тесты, и для всего этого уже есть инструментарий. Потом я могу это же положить в пайплайн развертывания, и для этого тоже есть инструментарий.

Приведите пример «запроса», реализующего описанную выше функциональность:

  • по нажатию кнопки «выставить счет»
  • проверить n условий (валидность заказа)
  • слазить во внешний сервис за налогами
  • создать счет с учетом налогов и дисконтов и сформировать в нем строки
  • сгенерировать ссылку на оплату
  • отправить ссылку на email клиента


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


Примеры есть в тексте статьи, и из них можно собрать тот запрос, о котором вы говорите.
Примеры есть в тексте статьи, и из них можно собрать тот запрос, о котором вы говорите.

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

Это не конструктор из кубиков, это конструктор самих кубиков.

Что-то вы себе противоречите:


Здесь вы можете собрать веб-приложение, не изучая язык программирования
Пользователь заходит в систему, получает меню согласно своей роли, просматривает информацию, вводит данные, запускает запросы, двигая процесс по заданным этапам, получает отчеты.
В первую очередь это сервис небольшого бизнеса, в котором зачастую владельцу приходится самому вести разработку или как минимум управлять ею.
Переизобретение EAV. Что кстати является антипаттерном и на больших объемах данных можно поиметь немало проблем с производительностью БД.
Это не EAV, хоть и похоже местами, не спорю.
А начали мы как раз с того, что решили проблему производительности: деградация её с ростом объема и сложности должна быть не хуже линейной.

Линейной, серьезно? Обычный индекс в БД дает логарифмическую, а некоторые СУБД обещают и вовсе константный доступ.

В идеальном мире — да, логарифмическая. В Интеграле тоже логарифмическая.
Однако, с ростом сложности и объема системы в какой-то момент начинаются проблемы, и часто даже случается коллапс. Для привязки к конкретной метрике мы и говорим «не хуже линейной», имея в виду, что линия не будет пересечена.

Интеграл решает рутинные проблемы, на которые обычно не хватает времени, предотвращая часть причин коллапса.
Для привязки к конкретной метрике мы и говорим «не хуже линейной», имея в виду, что линия не будет пересечена.

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

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

Логарифмическая зависимость, говорят же вам.
Вот так выглядит, например, тайминг импорта 4+ млн объектов в Интеграл, начиная с пустой базы — ведется подсчет вставленных записей каждые 15 секунд:



Сервер попутно еще загружен чем-то, поэтому всплески на графике. Еще при загрузке идет поиск родителя записи, там до 6 уровней иерархии.
Здесь вы можете собрать веб-приложение, не изучая язык программирования: мы оперируем только бизнес-терминами и формулами, не сложнее, чем в MS Excel

Может не очень внимательно прочитал статью, но не заметил, а сценарии, бизнес-процессы, воркфлоу как-то реализованы? Ну хотя бы такой:


  • Менеджер по продажам заполняет заявку на отпуск товара клиенту
  • Система проверяет наличие на складе
  • Если на складе товара достаточно, то создаёт резерв под заявку
  • Если товара на складе недостаточно, то отменяет заявку на отпуск
  • После создания резерва менеджером создаётся счёт на оплату с конкретным сроком оплаты
  • При неоплате в течении срока или явном отказе резерв и заявка отменяются
  • При оплате товар из резерва переходит в службу доставки
  • После доставки заявка на продажу закрывается как исполненная

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

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


Я помогаю сейчас знакомому рекрутеру делать стартап для массового подбора персонала.
Там есть приличный бизнес-процесс, включающий переходы откликов кандидатов по статусам, планирование интервью, рассылку email и смс, работу с вакансиями (набор этапов, трёхуровневые специализации, скрипты, тайм-слоты встреч, права, и т.д. и т.п.) и сайтом Хэдхантер (HH). Фрейм HH написан отдельно, используется как сторонний сервис, внедренный в интерфейс, остальное — в Интеграле. Так вот, есть формы вакансии, отклика и прочие, состоящие из блоков, которые включаются/выключаются и совершают действия (вызывают запросы, запрашивают и сохраняют данные) в зависимости от статуса отклика или иного контекста. Приложение «ведет» пользователя, ограничивает, не дает спотыкаться.
Могу показать как это работает по скайпу, со всеми запросами, исходниками и формами. В целом, всё как в обычном приложении, но без python, php, nodejs.

Построителя workflow из кубиков пока нет. Пока нет.
В целом, всё как в обычном приложении, но без python, php, nodejs.

А как же? Накликиванием в интерфейсе? Или свой язык?

Нет языка.
Интерфейс свёрстан в HTML (css, js для оформления), программирование делается запросами Интеграла.

Ну то есть накликано. Что возвращает нас к вопросу версионирования, совместной разработки и переноса артефактов.

Статья про архитектуру и подход, если что.

Да, к сервису также прикручен очень примитивный интерфейс, позволяющий сделать всё остальное. Он такой топорный, потому что задумка про архитектуру и подход, повторюсь.

Ну так описанные мной проблемы — это как раз следствие вашего подхода, интерфейс тут ни при чем.


А уж если говорить об архитектуре… про нее в статье минимально. Ну да, мы поняли, что у вас там РСУБД. Есть полтора слова про то, как вы данные храните (хотя и без важных деталей). Дальше, судя по комментариям, просто монолитный PHP. Это статья про архитектуру?

Вы перечислили не проблемы, а задачи. Git вам нравится? Так и грузите всё в Git, никто не мешает.

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

Пока же наработок немного, поэтому я и не говорю, что матёрые программисты найдут здесь готовые решения. Но я аккуратно записываю всё, что мне здесь говорят, на будущее.
Сейчас это, в основном, сервис для «самых маленьких», кто работает в экселях, в одиночку, пока не имеет понятия о версионности и хочет сделать всё быстро и «на живую».
Вы перечислили не проблемы, а задачи. Git вам нравится? Так и грузите всё в Git, никто не мешает.

И как мне "грузить в Git" ваши запросы? Особенно так, чтобы сделал git checkout <другая ветка> — и вся система заработала иначе?


Это такая фича — данные, метаданные и «код» хранятся в одной базе

Знаем мы эту фичу, configuration over code, съели и подавились.


Пока же наработок немного

Ну то есть все-таки статья не об архитектуре.


Сейчас это, в основном, сервис для «самых маленьких», кто работает в экселях, в одиночку, пока не имеет понятия о версионности и хочет сделать всё быстро и «на живую».

Опять не сходится:


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

Спасибо за ответ. Что приложение ведёт пользователя, это хорошо. Но есть ли механизм нотификации пользователей о действиях других пользователей? Грубо, форма "ваш заказ в обработке" автоматом меняется на форму "ваш заказ утверждён, оплатите счёт в течение трёх часов" и, крайне желательно, без опроса сервера каждую секунду. А таймауты для действий? Та же форма "оплатите счёт" меняется на "извините, ваш заказ отменён, поскольку вы его не оплатили в течении трёх часов".


А интеграция с внешними активными сервисами, теми же платежными системами, как-то поддерживается? Когда пользователь внешней системы в её интерфейсе инициирует ту же оплату.

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

Нотификацию можно сделать множеством способов, например:
а) выбирать из базы и отображать в интерфейсе (форме с меню) напоминания о подошедшем сроке запланированных событий
б) отправлять email или СМС в момент наступления события для побуждения к дальнейшим действиям (менеджеру об оплате заказа)
в) запустить планировщик, выбирающий и отправляющий напоминалки или вызывающий сторонний сервис раз в 1-2-5 минут

В том стартапе, о котором я говорил, применены все эти способы для разных событий.

Посмотреть как это сделано
Пример для отправки СМС и показа текущих активностей

Пульт, перезагружающийся раз в 45 секунд:



HTML-код пульта:



Отчет intSmsPult, использующийся в пульте:



Колонки отчета (Поля запроса, их можно назвать в Редакторе типов как угодно):



Присвоение пустого значения, отмеченное красным кругом, сбрасывает флажок запланированного события, чтобы СМС отправлялось 1 раз.

Напоминалки в пользовательском интерфейсе

Действия, срок совершения которых наступил, будут отображаться на форме (отмечено красным):



Потому что в шаблоне главной формы, под меню находится такой код, вызывающий отчет о запланированных действиях:



Для интеграции обычно берется код, предоставленный поставщиком сервиса. Поставщик потом вызывает указанный ему URL и отчитывается о проделанной работе. Тут вообще проблем нет. Вот, например, как это выглядит при эмуляции мобилы в браузере: youtube
(вызывается форма оплаты Сбербанка в середине третьей минуты ролика)

Ну то есть пульт перегружается клиентским кодом. И весь интерактив тоже сделан на клиентском коде, который вашей платформой, на самом деле, никак не производится, а пишется человеком вручную, да еще и опираясь на очередной птичий язык.

В этом случае просто нет необходимости организовывать события, инициированные сервером. Не значит, что невозможно; просто не нужно.

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

… а если можно — то как?


А интерактивные вещи быстрее и красивее делать руками нормальных дизайнера, верстальщика и программиста. Не конструктором же.

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

… а если можно — то как?

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

Там написано в первом же абзаце, для чего этот сервис:

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

Дадада. После этого мы получаем две несогласованные системы с кучей веселья.

Судя по всему элементы UI нельзя наполнять текущими данными, они могут наполниться сами, сделав запрос к серверу, но вот именно наполнить их по какому-то событию на сервере, не получится. По карйней мере средствами системы.

Можно прикрутить Server Side Events или WebSocket, но по сравнению с обращением к URL такая функциональность нужна на порядок-другой реже, и обычно оказывается, что «мне только спросить». Пока никто не хотел этого настолько, чтобы оплатить несколько часов соответствующей работы (день возни в ядре).
В форме логина self-xss. Некритично, но показывает качество кода.
Спасибо! Исправим.
На чем это написано?
Сейчас это PHP и MySQL.
Фреймворк?
Интеграл сам и есть фреймворк (т.е. его концепт, прототип).

Ядро его достаточно компактно, это всего один скрипт, начинали его писать давно, а сейчас его почти не трогаем, поэтому обходимся без фреймворка.
Очень жаль

Судя по многочисленным точкам входа (login.html, index.php, register.html, register.php) и половине шаблонов, начинающихся с тега <html>, вы не знакомы с фреймворками и не умеете их писать.

Да вроде сразу видно что велик
А чем это лучше Access, Fox Pro, OpenXava, etc?
Не говоря уже о настоящих технологических платформах типа Axapta или 1С?
Это не «лучше» или «хуже», это «другое».
Ответ также сильно зависит от целевой аудитории. Для пользователей Access и FoxPro здесь легче начать работать.
В сравнении с Axapta и 1С для несложных задач заметно легче будет само решение. Для сложных задач — будет значительно дешевле менее трудоемко.
Для сложных задач — будет значительно дешевле менее трудоемко.

Вот для этого, конечно, хотелось бы какую-то аргументацию. С примерами.

В сравнении с Axapta и 1С для несложных задач заметно легче будет само решение


Насчет этого тоже не уверен.
Тоже хотелось бы на каком-нибудь примере разобрать.
Давайте попробуем разобрать на примере. Часто встречается: пользователю нужны 2 жалких журнала: приходы и продажи. И сумма в кассе. (Онлайн-касса маячит на горизонте, но пока мало кто чешется)

Дистрибутив 1С для этой несложной задачи (Торговля и склад) весит 180МБ, потребует компьютер и настройки под себя руками специалиста. В результате, гипотетически, будет решен любой набор задач, если знаешь когда какую кнопку нажимать (а их в меню больше сотни). Практически же люди так и сидят с бумажками. Сложно им.

Простейший аналог в Интеграле не потребует ничего, кроме планшета или мобилы.
Выгрузка конфигурации — схема данных, отчеты, справочники — занимает меньше 15 КБ (так, к слову). Само программирование заготовки этого кустарного аналога заняло около 5 дней, и он точь-в-точь может воплотить требования среднего пользователя еще за 4-8 часов.
За 40 минут (мы пробовали) можно сделать импорт номенклатуры откуда угодно.
Ну вы же понимаете, что за 5 дней можно 10 раз убрать из интерфейса 1С-ки все лишнее, разграничить роли, обучить ведению простых бизнес-процессов сотрудников. В чем профит для конечного заказчика?

Размер дистрибутива, конечно, хорошо. Да и у современной УТ база будет под гиг в развернутом виде, даже пустая. Но в 2018 вряд ли для кого-то будет решающим фактором «зато у нас дистрибутив на дискету помещается».
Ну вы же понимаете, что за 5 дней можно 10 раз убрать из интерфейса 1С-ки все лишнее, разграничить роли, обучить ведению простых бизнес-процессов сотрудников. В чем профит для конечного заказчика?

Вы преувеличиваете — за полдня не выйдет (10 раз за 5 дней).
А вот скопировать нужный функционал 1С с нуля за 5 дней — это совсем другое дело.
Профит заказчика в том, что он получает простую систему, в которую прикручивает нужный ему функционал CRM, KPI, долги физлиц и всё что угодно остальное без интеграции с дополнительными системами. Ровно в том виде, в каком нужно (и за ту же цену). Как раз со скоростью 1 система за полдня.

Размер дистрибутива, конечно, хорошо. Да и у современной УТ база будет под гиг в развернутом виде, даже пустая. Но в 2018 вряд ли для кого-то будет решающим фактором «зато у нас дистрибутив на дискету помещается».

Тут мы подходим вплотную к тому, ради чего создан Интеграл. Коллеги выше не верят мне про возможности создать самодокументирующуюся систему, о чем будет моя следующая статья. Так вот, Интеграл позволяет описывать и экспортировать только бизнес-данные, без привязки к чему-либо чужеродному*, поэтому позволяет создавать что-то вроде ТЗ, которое можно распечатать единым документом и оно сразу будет работать.

* Функции языка SQL, которые можно использовать в отчетах, нужно будет описать для бизнеса, да, есть такое ограничение. Функции SUM, MIN, MAX, да пусть даже CONCAT вряд ли кого сильно озадачат. Ориентир по сложности восприятия у нас — MS Excel.
Вы преувеличиваете

Преувеличиваю. 10 раз нет, но вот 1-2 — точно да.
Ну а профит от настройки 1С, а не вашей системы в том, что найти фирму, которая будет готова взять на поддержку на 1С — очень легко. Найти кого-то, кто будет готов поддерживать Интеграл кроме вас… Не думаю, что легко, скажем так.

В любом случае, я все это говорю не ради того, чтобы вас демотивировать. Я не верю в ваш проект, но в любом случае, респект что занимаетесь тем, что интересно и созидаете.

Статья же написана действительно плохо (не в обиду, а как критика). Почему плохо? Потому что не уверен, что был хоть кто-то, кто прочел ее полностью.
Вам бы для затравки что-то легкое, продающее, вызывающее вау-эффект описать. И при этом короткое, не дольше, чем минут 5 чтения.
Такое, чтобы люди, листая ленту, увидели и прониклись, по крайней мере, заинтересовались.

У вас же статья получилась сугубо техническая и при этом огромная. Это зачастую не особо интересно и в профильных хабах, а уж для неизвестной технологии — тем более.
Ок, вот классическая задача — склад, товары на складе, движение товаров, отчет по остаткам, время построения отчета не зависит от количества записей о движении товаров. На 1С это делается (включая UI) без кода вообще.
В 1С, если не ошибаюсь, используются регистры для хранения данных об остатках товаров, поэтому отчет время построения отчета слабо зависит от количества движений. В Интеграле можно сделать точно так же, точную функциональную копию нужных возможностей, включая логику работы базы.

Про UI не совсем понятно, о чем. Вот так выглядит построение простых отчетов в приложении, без кода, но в некоторых случаях с применением арифметических формул:
www.youtube.com/watch?v=F_w5KvfmqzA&feature=youtu.be&t=210

Если сформулируете задачу про остатки, могу сделать тестовый стенд, как этот.
1С тоже вначале декларировала «доступно и всерьез». Каждый бухгалтер сможет запрограммировать процессы под себя. А вышло вон оно как: на все программисты 7.7 могут освоить клиент-сервер 8.3.
Если бы люди не пытались сделать мир лучше, несмотря на летящие в них помидоры, то было бы в мире меньше всего хорошего.

Мы хотим попробовать, имеем право, стараемся. И будь что будет.
Это очень громоздкая и бестолковая статья, написанная типичным программистом. Больше напоминает документацию по продукту со вставками пояснений, чем вводную статью. Кто вы такие? Правильно — «никто» с продуктом «Интеграл», о котором мы впервые слышим. Тогда к чему всё это размазываение про роли, таблицы?.. Столько никчемушных подробностей, будто прям щас все бросятся писать формы в вашем ЛЕГО! Увы, для того, чтобы реально заинтересовать людей, нужно не сыпать терминами и заваливать деталями, а высокоуровнево и доходчиво объяснить, ЧТО вы сделали, для кого и почему ваш стотыщный велосипед лучше других. Не лучше тем, что у вас есть «больше фич», а чем принципиально вы превосходите другие конструкторы. Ничего этого в статье не увидел — такую портянку дальше первой страницы не осилил. Материал — отстой.
Будьте справедливым к себе. Это вы никто, а те к кому вы обращаетесь — разработчики и авторы публикации. У хабра одна из функций — дать разработчикам, платформу для представления своих идей. Даже на празднике драконьей коллективной рецезии человек поучающих «типичных программистов» что они не теми уродились будет лишним.
Ответить на вопрос можно только сравнив приложения, написанные во всех этих решениях.
Не «Hello, world!» и «table1 — table2», а что-то используемое людьми. За деньги.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории