Telegram Web
emacsway-log: Software Design, Clean Architecture, DDD, Microservice Architecture, Distributed Systems, XP, Agile, etc.
Один из способов ввести мозг разработчика в состояние "умственного жонглирования" - это "звездная" ("божественная") модель, которая решает несколько проблем. Или, если сказать по другому, - совмещение нескольких моделей в границах одного компонента.

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

На практике к этому зачастую приводит "проектирование от сущностей" (от "структур данных"). Образуется сущность, которая знает все про всех, и содержит:
- массу тела;
- должность, отдел, обязанности;
- диеты, аллергии;
- платежные реквизиты;
- адрес;
- и т.п.

При попытке компонентного разделения такой "исторически сложившейся" системы, например, при выделении компонента, ответственного за платежи, перед программистом возникает вопрос: а что делать, если для функционирования одного компонента нужна сущность, которая находится в другом компоненте? Возникает то, что получило название "распределенный монолит". Модель размазывается на несколько компонентов.

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

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

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

См. также:
- https://www.tgoop.com/emacsway_log/1152
- https://www.tgoop.com/emacsway_log/1295
Вопрос от программиста из категории "я такие вопросы люблю" (предыдущий был не менее интересным).

Если Domain Event - это факт изменения состояния модели, то можно ли использовать его в качестве сообщения о нарушении инвариантов (т.е. в целях валидации), как это предлагал Udi Dahan?
https://udidahan.com/2008/08/25/domain-events-take-2/

Если используется Task-Based UI + CQRS с однонаправленным потоком изменений, и CQRS-команда не может быть обработана исполнена, потому что нарушает инварианты, то можно ли сообщение канала обратной связи (WebSocket) считать представлением Domain Event?

Нарушение инварианта - это событие предметной области?
На корпоративных тренингах на рабочем месте часто бывает ситуация, когда люди отваливаются: вчера ещё было 3 человека в команде, а сегодня, смотришь — остался только один, остальных растащили на встречи.

И часто этот оставшийся работает лучше и быстрее, чем до этого они работали втроем!

Я и сам сталкивался с таким, как участник: на одном курсе по управлению проектами мы выполняли задание сначала индивидуально, а потом то же задание в группах. По идее, результат группы должен быть лучше самого лучшего индивидуального результата, но моя группа сумела даже ухудшить мой результат :) Инициативу захватила одна участница, которая, видимо, очень хотела показать свои лидерские качества, а проще всего это сделать, если всех критиковать, подвергать сомнению и настаивать на собственной правоте. А то какой же я лидер, если просто прислушиваюсь к другим! Я в это играть не очень хотел, ну и вот. Не хотите ко мне прислушиваться — садитесь в лужу, bon voyage.

У этого явления есть название: эффект Рингельмана. В ИТ мы знаем его следствие, известное как Закон Брукса:

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

Эффект Рингельмана формулируется так:

«Личная продуктивность отдельных членов группы снижается по мере роста численности группы»

Изучал он этот эффект на примере перетягивания каната — в принципе, деятельность, похожая на создание ИТ-продукта.

В исследованиях Рингельмана общая продуктивность группы уже на 5 участниках составляет только 3.5 (считая эталонную продуктивность каждого в отдельности за 1), а после 7 практически перестает расти, застывая на отметке 3.92.

Обычно эффект объясняют двумя причинами: "социальной ленью" и потерей координации.
Если мы видим, что над той же задачей работает кто-то ещё, мы уже не так сильно вкладываемся. Это "социальная лень" или "социальное бездействие".

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

Что тут можно сделать?

Брукс предлагал выстраивать группы вокруг признанного лидера и эксперта, по типу операционной бригады (всегда есть главный хирург, который делает операцию, остальные ему ассистируют).

Agile говорит, что команда должна быть не больше, чем можно накормить двумя пиццами (5-7 человек).

Исследования сообщают, что социальное бездействие ослабевает, если:
1. У людей есть общая разделяемая цель
2. Вклад каждого участника может быть идентифицирован и оценен
3. Люди чувствуют свою незаменимость

В целом, это вопрос мотивации.

А вот координация — вопрос технический, это "харды" организации работ. Сам Рингельман, кроме перетягивания каната, изучал ещё перетаскивание камней при помощи веревок и перемещением тележек мускульной силой (он вообще занимался организацией работ в сельском хозяйстве, и работал примерно в одно время с Тейлором и Гастевым). Измеряя силы, прикладываемые работниками к камню, он выяснил, что основная проблема в синхронизации усилий: то один начинает не вовремя, то тянут в разные стороны. Про мотивацию он как раз не писал.

В общем, эти харды применимы к любым работам:
- нужно обеспечить последовательность работ;
- устранить потери (объект работы ждёт человека; человек ждёт объект работы);
- по возможности убрать одновременные разнонаправленные работы над одним объектом;
- очертить каждому участнику границу работ и поставить точную цель;
- определить, формализовать и ограничить по срокам процесс принятия решений;

В конечном счете это всё про разделение (объектов и выполняемых над ними работ), объединение (результатов работ в единое целое) и принятие решений.

Хм, кажется, опять получилось про анализ и архитектуру.
Наткнулся на интересный набор паттернов обмена сообщениями от Microsoft Azure. Так как это паттерны, реализовать их можно на любых технологиях.

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

Всего там 42 паттерна, они достаточно произвольно объединены в три категории:
— управление данными,
— архитектура и реализация
— обмен сообщениями.

Это "китайская классификация" по Борхесу; мне, например, сложно понять, почему CQRS — это "архитектура", а хореография — "обмен сообщениями". В любом случае, паттерны интересные, а ещё есть анти-паттерны, как делать не надо — тоже полезно знать при проектировании решения; и архитектурные принципы. Повторюсь, что это довольно общие вещи, относящиеся не только к продуктам на основе Azure — они в том или ином виде реализованы и в AWS, и в других системах.

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

Итак, паттерны:

* Асинхронный запрос-ответ.
В общем, простая идея про запросы, длительность обработки которых на сервере может занять больше, чем 100 мс, а очереди/брокера у вас нет или вы не хотите их использовать — и вы используете поллинг. Первым запросом запускаете процесс на сервере, а сервер возвращает 202 (Accepted) и ссылку, которую нужно поллить. Клиент регулярно ходит по этой ссылке и проверяет — выполнился ли запрос. Пока он не выполнен — сервер возвращает 200 (OK). Когда результат готов — 302 (Found) и переадресует на место, где лежит результат.
Как вариант, тут можно было бы использовать WebHook, но далеко не все клиенты поддерживают вызовы к ним — технически или по соображениям безопасности.

* Квитанция (claim-check).
Если вы собираетесь передавать большой объем данных, имеет смысл не загружать систему передачи (например, брокера или очередь), а выслать "квитанцию" с токеном, по которому клиент сможет забрать ответ из хранилища статических данных. Также паттерн можно использовать, когда нужна дополнительная защита, чтобы чувствительные данные не проходили через брокер/очередь/прокси и т.п. Ну и в принципе неплохо бы защищать статику каким-нибудь токеном, чтобы не было ситуаций с утечкой документов, доступных по прямым адресам без всякой проверки — про это следующий паттерн.

* Valet Key — не знаю, как перевести на русский, видел автоматический перевод "ключ камердинера", но не встречал такого употребления в живой среде. Это как раз токен из предыдущего паттерна: когда вы получаете статический ресурс, если он не должен быть общедоступным — защищайте его токеном. То есть, клиент сначала запрашивает ресурс у приложения, приложение отдает ссылку на ресурс + токен (либо токен зашит прямо в URL), и передает токен хранилищу. Хранилище обрабатывает запрос к ресурсу только с токеном — так гарантируется, что запросивший клиент — именно тот, кому выдали разрешение, а ещё можно ограничить действие токена по времени.
Этот паттерн также описан в книге O'Reilly 'Cloud Architecture Patterns' — хотя остальные паттерны в ней в основном низкоуровневые.

Где можно использовать эти паттерны? Например, при запросе большого отчета, который относительно долго формируется. "Заказать" формирование мы можем через асинхронный запрос и поллинг, либо через очередь запросов, потом получим "квитанцию" с токеном доступа, а сам сформированный отчет ляжет в файловое хранилище. Файловое хранилище отдаст готовый отчет только клиенту, предъявившему квитанцию с токеном. Про истечении времени жизни токена отчет будет удален.
emacsway-log: Software Design, Clean Architecture, DDD, Microservice Architecture, Distributed Systems, XP, Agile, etc.
Один из способов ввести мозг разработчика в состояние "умственного жонглирования" - это "звездная" ("божественная") модель, которая решает несколько проблем. Или, если сказать по другому, - совмещение нескольких моделей в границах одного компонента. В таком…
Существует модель ментальная (аналитическая) предметной области, грубо говоря, это представление (знания) стейкхолдеров о тех процессах, которые можно автоматизировать информационной системой. Это модель области проблемы (Problem Space), и существует она независимо от того, автоматизирована ли она системой или нет (например, правила рассчета процентной ставки банка, которую высчитывает клерк на калькуляторе). Она является причиной появления автоматизированной системы.

И существует модель решения, т.е. упрощенная интерпретация реальности с целью решения некой проблемы. Это модель области решения (Solution Space), которая подлежит реализации. Это то, что будет "делать" система, business logic (от англ. "business" - "дело").

Эти две модели могут отличаться. Основная идея DDD заключается в том, чтобы они не отличались, т.е. чтобы эти две модели совместить в одну. А поскольку натуральный язык - это средство моделирования, то для достижения этого используется Ubiquitous Language, т.е. использование единых терминов как специалистами области решения (разработчиками), так и специалистами области проблемы (экспертами предметной области). Таким образом, Ubiquitous Language является моделью как области проблемы, так и области решения. В этом заключается основная суть DDD. Такой подход позволяет существенно снизить информационную нагрузку на разработчиков, а значит, снизить вероятность ошибки. Любой, кто знает логику предметной области, легко разберется в том, как устроена система.

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

Основных целей у Bounded Context две:
1. Снизить когнитивную нагрузку, т.е. модель не должна содержать ничего неревантного решаемой ею проблеме, чтобы не создавать паразитную когнитивную нагрузку.
2. Снизить коммуникативную нагрузку, т.е. модель не должна быть разделена между разными командами, которые не смогут и шагу ступить без коммуникаций, не сломав при этом инвариантов модели.

Основная суть EventStorming заключается именно в том, что он позволяет моделировать решение прямо поверх ментальной модели стейкхолдеров, прямо на тех же стикерах. Результатом ES является модель решения, которая в точности отражает структуру будущего кода. Настолько точно, что по этой диаграмме можно делать автоматизированную сверку кода, или даже генерировать сам код (на Java это можно сделать используя сервис от Vaughn Vernon domorobo.to + xoom-designer).
emacsway-log: Software Design, Clean Architecture, DDD, Microservice Architecture, Distributed Systems, XP, Agile, etc.
Существует модель ментальная (аналитическая) предметной области, грубо говоря, это представление (знания) стейкхолдеров о тех процессах, которые можно автоматизировать информационной системой. Это модель области проблемы (Problem Space), и существует она независимо…
💬 "3.3. Что такое модель?
Мы уже сформулировали два определения модели.

Первое: модель есть средство осуществления любой деятельности субъекта.

Второе: модель есть форма существования знаний.

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

Выразим эту мысль в виде еще одного общего определения: модель есть системное отображение оригинала.

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

💬 "3.4. Аналитический подход к понятию модели

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

Соответственно этому модели разделяются на абстрактные и реальные (рис. 3.5)."

О моделировании посредством натурального языка:

💬 "Более определенно мы можем говорить об абстрактных моделях,
которые являются как бы конечным продуктом мышления, приготовленным для передачи другим субъектам. Это модели, воплощенные средствами языка: они поддаются регистрации (тексты, аудиозаписи) и могут изучаться как объекты, отчужденные от непосредственно мышления, но являющиеся его продуктами, содержащими информацию о нем."
emacsway-log: Software Design, Clean Architecture, DDD, Microservice Architecture, Distributed Systems, XP, Agile, etc.
💬 "3.3. Что такое модель? Мы уже сформулировали два определения модели. Первое: модель есть средство осуществления любой деятельности субъекта. Второе: модель есть форма существования знаний. Можно несколько дополнить каждое из этих определений указанием…
💬 "Главная для нас особенность — то, что язык является универсальным средством моделирования: говорить можно о чем угодно. Из многих свойств языка, обеспечивающих ему это свойство, обратим внимание на расплывчатость смысла слов.

Приведем пример словесной модели некоторой ситуации.
«В комнату вошел высокий красивый молодой человек, неся тяжелый сверток». Так и видится реальная картина. Но «высокий» — какого именно роста? «Молодой» — а сколько ему лет? Не говоря уж
о том, что такое «красивый». «Тяжелый» — какого веса? Практически ни одно слово естественного языка не имеет точного смысла. Можно привести аналогию: «смысл» конкретной ситуации — точка, «смысл» слова — облако. Описывая конкретную ситуацию, мы как бы обволакиваем точку облаками, понимая, что истина гдето в середине этого скопления. В большинстве случаев, особенно в быту, такого приблизительного, расплывчатого описания бывает достаточно для действий, часто успешных. В некоторых видах деятельности такая расплывчатость сознательно используется как важный позитивный фактор: поэзия, юмор, политика, дипломатия, мошенничество…

Однако в случаях, когда необходимо произвести конкретный продукт, достичь конкретного результата, этой конкретности начинает
мешать расплывчатость бытового языка. И тогда те, кто занимается конкретной деятельностью, изживают мешающую неопределенность,
вводя в язык более точные термины. У всякой группы с ее общими целями вырабатывается свой, специфический язык, обеспечивающий
нужной точностью эту деятельность. У скотоводческого африканского племени масаев есть сотни терминов для характеристики коров;
у северных народов — множество терминов, определяющих состояние
снега; на своих языках разговаривают физики, медики, юристы; уголовники «ботают по фене»; молодежь говорит на слэнге, не понятном для взрослых; лондонские «низы» разговаривают на «кокни».

Общий вывод: всякая групповая деятельность требует выработки специального, более точного, чем разговорный, языка; условно назовем его профессиональным.

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

Таким образом, увеличение точности смысла языковых моделей идет за счет добывания и включения в язык все новой и новой информации о предмете интереса.

Есть ли предел этому процессу уточнения? Есть, и это язык математики, в котором термины максимально точны, однозначны.

Правда, полностью изжить неопределенность невозможно, иначе было бы невозможно о бесконечности мира говорить конечными фразами. Есть несколько (и не только вспомогательных, но и базовых) понятий в математике, имеющих расплывчатый смысл: «приблизительно равно», «значительно больше (меньше)», «бесконечно мало (велико)», «неопределенно» и т.д. И все же математический язык является крайним, самым точным справа в спектре языков описания реальности (рис. 3.7)."
emacsway-log: Software Design, Clean Architecture, DDD, Microservice Architecture, Distributed Systems, XP, Agile, etc.
💬 "Главная для нас особенность — то, что язык является универсальным средством моделирования: говорить можно о чем угодно. Из многих свойств языка, обеспечивающих ему это свойство, обратим внимание на расплывчатость смысла слов. Приведем пример словесной…
💬 "То, что какая-то наука недостаточно математизирована (история, биология, медицина, психология, политология и т.п.), означает лишь то, что ее объект столь сложен, столь мало изучен, что до математической точности ей еще далеко. Но есть перспектива.

Для полноты картины отметим еще одну особенность языков.
Культура индивида (мир его моделей) образуется из взаимодействия его врожденных моделей и культуры его социальной среды, в том числе (а возможно, и в первую очередь) языков, входящих в эту культуру. Здесь проявляется свойство ингерентности, совместимости
внутренней и внешней культур. Случается, что их полного согласования достичь не удается: генетики доказали, что иногда неспособность научиться грамотно говорить и писать заложена в генах субъекта; человек, не имеющий абсолютного слуха, при всем старании не может полностью познать язык музыки; люди с преобладающим «правополушарным» мышлением испытывают своего рода аллергию к математике; люди, в зрелом возрасте попавшие в эмиграцию, испытывают чувство ностальгии и т.п. Однако языков так много, что каждый находит возможность сформировать свою культуру так, чтобы в каком-то отношении обеспечить свое посильно успешное взаимодействие с окружающей средой."

💬 "Продолжая рассмотрение отношений между моделью и оригиналом, остановимся на содержании информации в модели. Оригинал и модель — разные вещи. В оригинале есть много такого, чего нет
в модели
, по двум причинам: во-первых, не все из того, что известно об оригинале, понадобится включить в модель, предназначенную для
достижения конкретной цели
(зона А на рис. 3.13 изображает известное, но ненужное, в том числе ошибочно сочтенное ненужным и невключенное в модель); во-вторых, в оригинале есть всегда нечто непознанное, поэтому не могущее быть включенным в модель (зона В на рис.
3.13). Зона 2 на рисунке изображает информацию об оригинале, включенную в модель. Это истинная информация, то общее, что имеется у модели и оригинала, благодаря чему модель может служить его (частным, специальным) заменителем, представителем. Обратим внимание на зону 3. Она отображает тот факт, что у модели всегда есть собственные свойства, не имеющие никакого отношения к оригиналу, т.е. ложное
содержание
. Важно подчеркнуть, что это относится к любой модели, как
бы ни старался создатель модели включать в нее только истину."

Про эволюцию знаний:

💬 "К примеру, полезной бывает классификация моделей на познавательные и прагматические.

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

Познавательные модели (рис. 3.13) не претендуют на окончательность, завершенность: всегда остается что-то непознанное. В познавательной практике принято терпимо относиться к отличающимся и даже противоречивым мнениям. Научные модели подвергаются постоянному сомнению и проверке на правильность, непрерывно уточняются и развиваются."

--  "ПРИКЛАДНОЙ СИСТЕМНЫЙ АНАЛИЗ" /
Тарасенко Феликс Петрович
emacsway-log: Software Design, Clean Architecture, DDD, Microservice Architecture, Distributed Systems, XP, Agile, etc.
💬 "То, что какая-то наука недостаточно математизирована (история, биология, медицина, психология, политология и т.п.), означает лишь то, что ее объект столь сложен, столь мало изучен, что до математической точности ей еще далеко. Но есть перспектива. Для…
Остановимся на моментах, имеющих ключевое отношение к определению границ Bounded Context. Даже в языке математики:

💬 "полностью изжить неопределенность невозможно, иначе было бы невозможно о бесконечности мира говорить конечными фразами".

💬 "снятие неопределенности может быть осуществлено только за счет новой, дополнительной информации."

Немного справочной информации из Википедии:

💬 "В русском литературном языке около 50 тысяч корней и десятки тысяч производных от них слов[1]. «Толковый словарь живого великорусского языка» В. И. Даля насчитывает около 200 тысяч слов. Наиболее употребительными словами, согласно «Частотному словарю русского языка» под редакцией Л. Н. Засориной, являются около 30 тысяч слов, а наибольшую частоту имеют чуть более 6 тысяч слов, покрывающих более 90 % обработанных при составлении этого словаря текстов.
По данным исследования Головина Г. В.[2], пассивный словарный запас у тех, кто получил среднее или среднее специальное образование, составляет в среднем 75 тыс. слов, имеющие высшее или незаконченное высшее образование знают в среднем 81 тыс. слов, кандидаты и доктора наук — 86 тыс. слов[3]."
-- "Словарный запас", Википедия

Однако, вы можете возразить, а как же так, ведь если мы один и тот же оригинал называем разными терминами в разных Bounded Context, то причем здесь ограниченность языка? Отвечает на этот вопрос сам Ф.П.Тарасенко:

💬 "Практически ни одно слово естественного языка не имеет точного смысла."

💬 "увеличение точности смысла языковых моделей идет за счет добывания и включения в язык все новой и новой информации о предмете интереса."

Вернемся к нашему примеру с пассажиром лифта. Какого он пола? Какой его возраст? Как его зовут? Какая его должность? Где он живет? Эта информация утрачена. Нам известно о нем только то, что это одушевленная единица перевозки. Мы знаем о нем только то, "что можно с ним сделать" - перевезти с одного этажа на другой. Сколько терминов нам понадобилось бы, чтобы однословно описать пассажиров обоих полов в возрасте от момента рождения до 100 лет?

Когда покупатель купил некий "товар", который был передан в отдел доставки, то этот товар стал называться "грузом", и информация о характере товара утратилась. Произошла утрата определенности. Собственно, эта утрата (игнорирование) нерелевантных деталей называется абстракцией.

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

Абстракция - один из главных способов борьбы со сложностью реального мира. Это то, что позволяет "о бесконечности мира говорить конечными фразами".

Именно термины натурального языка приводит в качестве примера Steve McConnell в своей книге Code Complete для того, чтобы пояснить суть абстракции.

Таким образом, ключевым в определении границ Bounded Context является то, каким именно термином мы можем устранить избыточную определенность об оригинале моделирования. Важно ли курьеру знать детали содержимого посылки? Можем ли мы выразить это таким термином, который был бы абстрагирован от избыточной определенности оригинала (конкретного содержимого посылки)?
Forwarded from Евгений Козлов пишет про IT (Eugene Kozlov)
Наконец дошли руки сделать 2ю версию гайда по алгоритмическим интервью и изучению алгоритмов.

Как и прошлая версия, он состоит из открытых источников, по которым я учился и учусь. Добавил несколько хороших курсов на русском и английском языках, поправил битые ссылки + добавил иллюстрации.
Перенес на GitHub чтобы была возможность редактировать содержимое самому и другие могли предлагать правки.

Поддержка в любом виде приветствуется😊 Любите алгосики и удачи на собесах😇

https://github.com/beagreatengineer/algo-interview
2025/07/04 19:55:36
Back to Top
HTML Embed Code: