Пишем код, за который не стыдно. Разбираем базу, даем рекомендации и встречаемся с умными людьми
Друзья, привет. Это подкаст Организованное программирование. Я его ведущий Кирилл Макевнин. У меня в гостях Евгений Лукянов. Всем привет. С Жене у нас буквально пару-тройку недель назад была встреча, и мы проводили такую процедуру, которая называется Ншторминг. Это часть концепта DDD до Domin Driven Design, где фактически идёт проработка какой-то темы, какого-то продукта, проекта, вокруг которого мы собираемся потом строить нашу логику, ну, и, собственно, которую мы собираемся программировать.
Это была довольно теоретическая штука. Мы скорее просто обсуждали то, как это в реальной жизни происходит. У меня есть идея. Женя, соответственно, из неё превращал её в какую-то структуру. И вот та структура, которая получалась с небольшими модификациями, собственно, пришла в наш сегодняшний а созвон. Потому что сегодня мы делаем вторую часть, для которой, сразу скажу, не переживайте, первую часть смотреть не надо было. Если вам как как бы не нужно смотреть про сам концепт ивентрминг, то, в принципе, достаточно смотреть сегодняшнюю часть именно про кодинг, то есть как ДД проявляется в коде. То есть мы берём идею того проекта уже разложенную всего. Сейчас мы буквально скоро и вам её повторим. И, соответственно, Женя на практике вот в редакторе в котлине Спринтбуте её запрограммировали. То есть у него частичный код есть, он ещё на накинет. Мы посмотрим, какая структура, подходы, как, собственно, то, что делается в авиненминге и те артефакты, которые получаются, как они непосредственно накладываются на код. Вот такое вот немножко долгое вступление. Жень, привет. Привет. Да, ещё раз представлюсь. В прошлый раз, по-моему, даже про себя ничего не сказал. А меня зовут Евгений Лукянов. Я технический директор компании Сатари и по совместительству автор образовательного проекта Стрин КонкаAT. Вот мы с подельниками, а, специализируемся на, мм, домендвенне. Довольно глубоко его прокопали на чистой архитектуре и, э, строим коммерческие проекты на этой основе. И в прошлый раз мы разбирали evвентрминг, как мы рисуем стикеры, зачем они вообще нужны. Сегодня попробуем немножечко покодить. Вот. Ну, понятное дело, наверное, много не получится, но хотя бы что-то. Плюс я уже сделал некую заготовку, а, которая нам чуть-чуть упростит ээ задачу. Ну, может быть, заодно по вайп-кодим, посмотрим, как пойдёт. Вот я не знаю, на твоём канале вайбкодинг как приветствуется, не приветствуется. Считается это зашкваром или ещё нет пока.
уже скорее наоборот, знаешь, писать код ручками, это уже вызывает вопросики. Всё понятно. Ну ладно, вспомним, да, вспомним, что мы делаем. Я сразу в двух словах скажу, а ты сейчас расскажешь, да? То есть это идея, которая ко мне давдавно пришла. Возможно, учитывая современные тенденции, она станет неактуальна для РФ, как минимум. Это идея того, что у нас есть Telegram-каналы у всех. И было бы классно, если был инструмент, такие для Ютуба существуют, который помогает тебе анализировать контент, в том числе конкурентов, подсказывать, на какие темы можно писать, какие темы востребованы, что заходит и так далее. То есть такая как бы система EИ, которая анализирует всё, всё, что вокруг есть телеги, анализирует конкурентов и, соответственно, помогает тебе выстраивать контент продакш, давай это так назовём. И как ни странно, кстати, с кем бы я не говорил, то, что мы с тобой в прошлый раз разговаривали, с другими людьми, очень многие отмечают, что им вообще такая идея в голову приходила. То есть все те, кто делают свои Telegram-каналы или как-то вообще контентом занимаются, им эта идея приходила в голову, потому что на Ютубе такого полно, в твиттере по такая штука есть, а вот м для телеги как будто бы нет. И и уже не нужно и возможно уже не нужно. Ну, на самом деле, глобально, скорее всего, нужно. А вот а конкретно подрф возможно не нужно. Мы узнаем чуть позже. как раз после выпуска, наверное, этого подкаста или перед выпуском, но точно после записи, потому что речь идёт вот про ближайший А, стой, нет, речь идёт про апрель, а когда там, возможно, всё заблокируется, так что мы выпустим, а потом уже узнаем постфактум, что произойдёт. Я так понял, что Женя немножко, во-первых, там комментарии были, во-вторых, для простоты, чтобы нам сейчас не усложнять, немножко подкорректировал получившуюся схему. Поэтому сейчас мы быстренько вам её напомним, что там, какие есть функции, какие есть сущности и что мы сейчас будем делать, и дальше перейдём непосредственно к кодингу. Спасибо. Ну, на самом деле, э такая система может получиться универсальной. То есть это же короткие сообщения, каналы и так далее. Это, скажем так, общая концепция. Вот не обязательно она должна быть в телеге и, в принципе, где угодно можно использовать. Хотелось бы такую увидеть, но в комментариях написали, что мы вот-вот выпустим, но что-то так и ничего не выпустили, а я бы хотел воспользоваться. Видимо, придётся делать самим. Вот так. Давайте посмотрим, что мы вообще делали в прошлый раз. В прошлый раз мы нарисовали вот такую вот схему. А на самом деле это её модификация уже, потому что, как мы сказали, м ну с первого раза толком ничего не получается. Точнее, не то, что не получается, оно всегда требует уточнения, переваривания и так далее. И я немножечко её подправил, уже прикидывая, как она будет реализована. Что-то опустил. Например, мы не будем сегодня рассматривать агрегат пользователь, потому что для сути он нам не особо нужен. Но остальные сущности, такие как канал, тематика и э что у нас тут есть пост. А вот мы попробуем реализовать что-то. Я уже сделал, а на рекомендательную часть, ну, мы уже посмотрим, хватит ли у нас времени. Это уже больше, скажем так, какое-то чтение. И там немного другие законы царят, скажем так. Это может быть даже отдельный отдельным ограниченным контекстом. Э там может быть отдельная отдельный профиль производительности, нагрузка и так далее. И, э, здесь их просто может не оказаться. Вот. Но это так. В любом случае проект у нас условно э учебный, да, мы просто посмотрим на то, как вот эти стикеры преобразуются в код даже, ну, наверное, в отрыве, не не то чтобы в отрыве от предметки, да, а в отрыве от телеграммов и других вещей. Мы попробуем закодить то, что здесь получилось. Я напоминаю, как у нас всё это выглядело. Сначала мы регистрировали пользователя, потом в оригинальной схеме мы подтверждали, а, по-моему, владение каналом. Здесь я это убрал. Мы просто в данном случае начинаем его отслеживать. Можем перестать его отслеживать. А, добавился вариант, по-моему, его не было. То, что канал с таким внешним идентификатором не существует. А далее, э, после того, как мы добавляем канал в отслеживаемые или убираем, у нас уходит уведомление в некий парсер, который мы предположи предположили, что есть. И в этом парсере уже нам там условно ставятся, допустим, наши каналы на отслеживание, после чего, э, если пост размещён либо же обновлён, к нам приходит некая уведомляшка. Ну, допустим, это webхуhook или как-то ещё. А как только пост размещён, мы его либо публикуем, то есть у нас есть метод, да, публиковать пост, либо обновляем, потому что пост может измениться, как вы знаете, можно его редактировать. И, соответственно, в этом посте будут все, ну, как бы все изменения, точнее, будет его актуальное состояние. Событие мы договорились называть пост опубликован, потому что, ну, это вроде как соответствует концепции предметки, которую мы изучили. Вот. Также у нас есть штука, которая определяет тематику канала. Мы просто будем брать каналы без темы и раз в час пробовать определить э тематику. Вот я здесь сделал довольно-таки примитивную штуку. Я попозже расскажу, может быть, её попробуем и закодить как раз-таки, чтобы посмотреть, как работает чуть более сложная бизнес-логика, а не просто там условно крутовская. И здесь не будет никакой ишки, просто там сравним по ключевым словам, подходит, не подходит, и всё. Ну, это демонстрационный пример, поэтому, собственно, ничего такого сложного здесь мы не будем делать. А дальше уже, ну, не знаю, попробуем, может быть, прикинуть тренды, рекомендации, не факт, что успеем ещё раз. Что ещё изменилось в этой модели? В каноничном вивеншторминге нету данных, точнее, нету атрибутов. А здесь мы атрибуты попытались прикинуть. То есть есть у канала внешний идентификатор, обычный идентификатор и список тематик. У тематики есть идентификатор, названия и ключевые слова. И у поста есть идентификатор, внешний идентификатор, содержание поста и, собственно, ссылка на канал. Больше вроде бы ничего. И в процессе реализации может оказаться, что ещё что-то нужно, чего-то не хватает, когда мы увидим уже какой-то готовый результат. Так, в общем-то, получилось, допустим, с каналом, потому что если вы посмотрите, ну, точнее, когда вы увидите, а в нём нет названия, я его реализовал, там нет названия, но это хорошая штука, чтобы его приделать и откуда- это взять, в общем, продемонстрировать, как это делается. Так, я думаю, с этим всё понятно. У тебя есть какие-нибудь комментарии ещё? Ну, кроме того, что, да, с точки зрения всё-таки главной фичи вот этого проекта, отслеживания подсказок и так далее, там, конечно, нам нужно будет считать engagement rate, а для этого тебе нужно все реакции, количество просмотров и так далее, потому что вся аналитическая информация, она обязательно без этого просто мы не сможем опять же человеку подсказать, на какую тему стоит писать или нет. То есть вот такие, ну, хорошее, кстати, замечание. Мы можем добавить, э, вот здесь же, да, отслеживание какой-нибудь статистики по каналу. Допустим, не знаю, там раз в x времени нам присылается условная стата, и мы, э, смотрим, распределяем по энгейджменту, по охватам там, по лоисам и так далее. Тоже сейчас попробуем это прикинуть. Причём как пост, так и канал, потому что мы смотрим динамику по каналу и динамику по конкретному. Дада. Ну, эйджмент у нас по постам же идёт, да? А мы потом уже можем свести по каналу, да. Ну, я говорю, тут сильно много, что называется, губу не раскатывайте, потому что времени очень мало, и мы сильно много не успеем. Я бы хотел больше продемонстрировать, наверное, какие-то принципы из чистой архитектуры домен древен дизайна, как это выглядит в коде. Вот. а бизнес-логику и прочее. Ну, тут можно до позелене накручивать, делать какие-то, э, более сложные вещи. Вот, давайте, наверное, перейдём к реализации и посмотрим, что здесь вообще такое есть. Меня Единственное, пока мы переходим к реализации, смотри, такой вопрос. Вот сейчас нас смотрят ребята, в том числе, которые там либо котлин не знают, либо Спрингбд. Насколько то, что мы сейчас показываем, завязано на фреймворк и язык? М, вообще нисколько. Фреймворк поменять здесь не стоит ничего. Я это, в общем-то, продемонстрирую. И если вы смотрите и не знаете котлин, то, э, в принципе, здесь используются какие-то очень Спасибо, до свидания.
какие-то стандартные конструкции. Вот. То, ну, любой, кто знает всеобразный язык, в принципе, может в этом разобраться. Это правда. Ну что, погнали. Расскажи про структуру папочек. Да, что у тебя? Да, давайте рассмотрим структуру папочек. Аэ, есть у нас любимый всеми коман. Это помойка, в которой лежит абсолютно всё. Да, и мы начнём с него. Почему? Потому что с него, в общем-то, всё, э, начинается. И начинается прежде всего такой наш micрофреймворк, который позволяет нам работать с паттернами домедринг дизайна. Вот тут даже, вы видите, есть база, а есть domain entity. Что это такое? А здесь очень много чего написано, но суть его заключается в том, что это некий базовый класс для сущностей. У него есть типизированный идентификатор, есть время создания, есть версия, которая нужна для отслеживания оптимистичной блокировки. Есть potenty ID, который мы не используем, но это нужно для идемпотентных вызовов. Есть часы для того, чтобы генерить какие-то какое-то время, да, допустим, если нам нужно ну, допустим, дата создания там. И есть генератор, который генерирует UИД версии 7. Вот на основе этих уидов у нас, в общем-то, сделана генерация айдишников, поэтому генератор мы тоже приложили сюда. Что интересно у него есть ещё? Есть события. Это просто обычный лист. И это доменные события. Сейчас мы до них тоже доберёмся. В списк событий мы можем добавлять события. При этом, если события событий до этого не было, то у нас меняется версия на следующую. Почему так? Э, потому что, э, при любых изменениях агрегата мы добавляем события. Вот. И если он изменился, соответственно, нам нужно переключить версию для того, чтобы обозначить, что агрегат был изменён. И точнее сущность, это ещё пока сущность. Тем самым у нас сработает какая-нибудь блокировка. Вот. Или не сработает. Есть метод выпинывания этих событий. В чём суть? Мы отдаём эти события и создаём новый список. Ну, то есть очищаем этот список. по сути работает немножко похожим образом, как стек. Для чего это нужно, тоже потом покажу. Есть, на самом деле, несколько вариантов реализации, э, как мы можем обрабатывать и хранить эти события. Этот наименее убогий. Вот. Все остальные выглядят ещё хуже. Есть версия. Это просто счётчик, который вот мы говорим next previous, и он нам возвращает эту версию. Собственно, ничего такого. Есть вот agгate root. Что он нам даёт? Да, в принципе, он по сути предоставляет всё то же самое. Просто иногда есть
есть потребность разделять agregate root и entity. То есть внутри агрегата може могут быть entти, и мы таким образом их разграничиваем между собой. Есть маркерный интерфейс value object чисто для того, чтобы обозначить, что это value object. Потом для всяких статических для проверок стаанализатором бывает полезно. Ну или просто, чтобы обозначить ну чтобы это лучше читалось, лучше понималось, разрабо. Используется не всегда. Вот на него частенько забываю, забивают, но пусть будет. И есть события предметной области. Это такой класс, который тоже содержит некий идентификаторы, время создания и прочее. Суть его заключается в том, что, э, ну, мы от него наследуемся, и у нас как бы есть конструкция предок для того, чтобы создать уже более специфичный класс или, в общем, отнаследоваться у него, не буду умничать, и задать какое-то конкретное событие. Ну вот можем посмотреть, а, допустим, вот topic created, removed it и так далее. То есть внутри уже всё задано, всё создаётся как нужно. Иквалсы работают, хэш-коды работают и так далее. Вот это не дата-класс. Ну, в общем-то, у него очень похожая реализация. Вот это такие вот строительные блоки, которые нам нужны. А можно вопрос понять сразу? Да, конечно. Мы это сделали сейчас для демонстрации, потому что понятно, что здесь ты во многом реализуешь логику, которая есть в самих Рэмках. То есть в реальной жизни это была бы у Рэмка, да? Или ты предлагаешь вместо? Нет, здесь нет никаких уэмок. Я вот прямо покажу, как это сделано. Вот это всё выдрано из реального проекта. То есть это Я понимаю. Здесь я просто тому, что это ещё и с Ормкой потом связывается. Нет, вот с ОРМ здесь нету никаких Ормок. Я покажу, как это реализовано. Вот. Я, конечно, тебе обещал, что мы сделаем inмеory, но я что-то психанул и сделал всё на постгресе. Я просто покажу, как это можно заменить. А, то есть я просто пытаюсь понять концепт. Концепт не в том, что мы это с Сауремкой дружим, а в том, что мы в принципе её не используем. То есть у нас есть вот эта история, мы её не используем, да? А нам нам вообще в целом пофигу, что использовать. Вот в чём прикол. Здесь есть. Так, у тебя ещё есть вопросы? Угу. Ну я скорее хотел, знаешь, наверное, уточнить, грубо говоря, если бы мы вместо того, чтобы всё это пилить, просто взяли ОРМ, что ты скажешь? В чём проблема? Здесь суть не в Орм, вот, а в том, как организована бизнес-логика, насколько нам это удобно. И сама чистая архитектура вот этого вот концепта, он говорит, что мы должны держаться подальше от каких-то деталей реализации. Вот это основа для бизнес-логики. Вот то, что я показал. И у нас, ну, вот прямо в реальных проектах у меня что бывает. Мы сначала у нас вообще inmemory база данных, да? То есть не то чтобы H2 мы берём, а там просто шmap. И чтобы проверить, как работает бизнес-логика на её адекватность, вообще мы поняли её или нет, мы храним просто в хэшмапе, да, даже не concurent hchшmap, а обычный, то есть обычный словарь. Я не знаю, ты из какого мира, из какого стека? Не, для меня все варианты как бы валидные, потому что я как раз на большом количестве разных фреймворков, на разных языках пишу. И скорее, тут, наверное, в чём вопрос. То есть понятно, что можно так, но при этом тебе приходится ручками делать вообще всё. Ну, допустим, ты показываешь там вот для оптимистической блокировки или там система событий, потому что в большинстве фреймворков, если мы берём Rich фреймворки, да, что называется, не какие-то микрофреймворки, допустим, Springbot, Rails, Larвеel какой-нибудь днo, то там это является, то есть, например, у тебя в УRM встроена работа с оптимистическими пессимистическими блокировками, там Create это вообще из коробки, ты даже можешь не знать, что автоматом всё создаётся, да. Например, те же самые ивенты - это прямо тоже отдельная подсистема. Они точно так же наследуются, создаются классы и так далее. Поэтому я к чему? К тому, что в определённых экосистемах, где у тебя это, ну, заложено во многом, то то, что ты здесь показываешь, это идёт прямо сильно большое дублирование. И вот я пытаюсь понять, скажем, с точки зрения того, что если бы, допустим, мы взяли РМ, то есть я понимаю, что разные люди выбирают разные, но, допустим, были бы какие-то ограничения, что ты бы сказал: "Ну нет, это совсем не то, у вас бы там не получилось одно, пятое, десятое". Потому что берём Spring JP JP дата, да, у тебя нету наследования, у тебя на аннотациях там же есть, соответственно, валидации и так далее. И привязки как бы к таблицам-то по большому счёту нет. То есть это отдельная схема через аннотации, которая прикручивается. И я вот так вот с ходу бы сказал, что, ну, такой, знаешь, я адвоката дьявола немножко играю. И я большой разницы не вижу, поэтому вот мне интересно, то есть видишь ли ты большую разницу? Потому что часто люди говорят: "Ну вот у тебя там протекать начнёт, что-то такое появится, что вот прямо сломает всю систему". А я скажу вот как то, что ты говоришь - это ээ устройство хранения. То, что показываю я - это устройство бизнес-логики. Я бы, наверное, не согласился. Знаешь почему? Потому что, а, вот ты говоришь, я там версию, да, вставляю, но вот у тебя в Урмке тоже работа с версией. И как бы на уровне твоей модельки, которую ты определишь в JPA, да, у тебя не будет никакой базы данных. Ты просто определяешь действительно версию, и ты понимаешь, что ты будешь использовать её как версию. Просто он ещё дополнительно потом при сохранении уже, когда репозитории будут работать, он это всё сам сделает. Или, например, ты говоришь: "Вот у меня ивенты". Но если ты откроешь опять же тот же самый СНБ или опять же Lвеel, вот эта система ивентов, она вообще с базой, ну, не связана, кроме того, что сами ивенты можно сохранить в базу. Там тоже это чисто бизнес-логика, и она для этого и создана. У тебя вся документация про бизнес-логику. То есть вот мы вс с подобной системой работаем, но мы там вообще о базе не думаем, грубо говоря. Ну, так есть упрощать, потому что по факту, конечно, там, в конце концов, когда ты сохраняешь, там важно, потому что у тебя есть асинхронность, ещё всякие вещи, но ты не базой занимаешься. То есть оно не просто ровно для этого, это по сути то же самое. Просто там это из коробки, а здесь ты это делаешь ручками. М, ну я говорю, мы вот именно разделяли концепцию, то есть хранение и бизнес-логика - это отдельные штуки. Это, во-первых, возможно, меня ещё укусила работа с всякими джавовскими хибернейтами, джипашками и прочими. А я вот сейчас конкретно не вспомню, но очень много было, скажем так, препятствий для реализации бизнес-логики именно в таком виде, как ты хочешь, да? То есть, ну, давай посмотрим тогда, да, просто чтобы увидеть, потому что интересно, в чём ограничение ты там видишь. Вот я скажу так, по крайней мере в Java мире, да, наверное, и в других тоже, то, как мне делать бизнес-логику, диктовал мне фреймворк или библиотека, чего мне очень сильно не нравилось. И из-за этого возникали, ну, довольно-таки существенные костыли. В конце концов, ну, наверное, не знаю, в половине проектов, где я участвовал и где был Берней или, ну, какой-то ОРМ там ДПА или его там другие реализации, мы думали, как бы от него избавиться, потому что, ну, там просто костыль на костыле становился. Вот. Ну, ты покажешь, да? То есть мы, когда дойдём до точки уже, где ты покажешь логику, можно будет в двух словах это проговорить. Да, можно будет показать. Ну, суть в том, что мы просто отвязываемся от фреймворков. Нам абсолютно всё равно, какой фреворк, как мы делаем под свою бизнес-логику и то, как нам удобно. В Джаве и в других, ээ, ну, наверное, да, в других экосистемах там возникали постоянно проблемы. Я, в общем-то, даже если вот мы не используем, ну вот представь, что у нас нету никаких агрегейт рутов, мы делаем классическую слоёнку, да, там, э, с сервисами и макаронами, я стараюсь не использовать никакие ОРМы. Я стараюсь использовать что-то промежуточное, ну, типа там джук, может быть, знаешь, такую штуку, э, или что-то в этом роде. просто потому, что, э, неоднократно уже напарывался на то, что я не могу реализовать, э, то, что мне нужно средствами уба. Там, где начинаются какие-то сложности, вот сложные запросы и так далее, там приходится сбоку городить э какую-нибудь хрень, которая будет работать с нужной производительностью, не вытаскивать всё на свете, потом затаскивать обратно это всё в базу и так далее. То есть на простых кейсах где-то джипа, да, о'кей, можно использовать чуть сложнее, где начинаются связи, там сложные связи, какие-то фкашки, там, ну, начинается веселье. Вот. И мы от неё стараемся избавиться в первую очередь, ну или вообще не брать, скажем так. Ну, тогда следующий, последний, наверно, вопрос. Смотри, что у тебя получается. То есть ты создал некую базовую структуру, которая, ну, вообще там много нюансов, которых здесь нет, и тебе придётся ручками их делать, потому что те же самые события, там их рассылка, там есть нюансы, связанные опять же с тем, что часть событий может быть асинхронной, как ты на них реагируешь. Это всё, ну, по факту фреймвор, то есть, если ты смотришь на подобные событийные штуки, которые крем, кстати, не привязаны не отдельно, да, а это довольно большие пакеты с кучей функциональности, да, мы мы можем э взять уже вот именно для механики доставки событий и прочего что-то уже готовое. Вот я здесь продемонстрирую, там супер простая вещь, которая у нас работает. Где-то мы, мм, допустим, используем на подобных вещах реализации даже transactional аутбоксов даже внутри приложения, просто потому что между сущностями нету транзакционной связи. И, ну, а тебе нужно сделать операцию, которую там делает 1 2 3че и вот приходится городить такой огород. То есть там, ну, чуть сложнее, чем просто даже реализация фреймворка. А вопрос-то мой был вот именно в этом. у тебя, грубо говоря, вот ты скопировал, говоришь: "Вот, э, некая база, она универсальная". У меня сразу возникает вопрос: "Ну тогда очевидным образом вот это должно быть фактически неким таким, ну, пакетом, фреймворком, отдельной библиотечкой, которая, собственно, даёт? Или ты не видишь в этом смысла?" Потому что, ну, человек, который делает несколько проектов, мне кажется, первое, что в голову придёт: "Ну давайте это не будем копировать, тем более там же как-то функциональность расширяется, вынесем это в какой-то пакет". Или вы даже так не делаете по каким-то причинам. Да, и мысль, конечно, такая есть. Вот, э, другое дело, что руки не доходят. Это во-первых. А, во-вторых, в каких-то проектах есть, ну, условно, ээ, некие особенности, свои костыки, да, свои костыльки, и думаешь: "Ага, это сейчас надо делать супергибко расширяемую фиговину". А здесь реально, ну, по сути, вот сколько? Три класса. И, ну, этого достаточно. Проще местами скопировать, чем делать супергибкую какую-то штуку. Ну я последнее время, да, задумываюсь, может, вынести это как-нибудь и сделать в виде библиотеки. Вот, э, оно уже плюс-минус, э, как-то устаканилось. Ну, не знаю, мне, честно говоря, может быть, просто лень. О'кей. Да, хотя, кстати, я бы, знаешь, глянул, потому что, ну, прикинь, сколько лет, сколько народу этим занимается. Что-то мне подсказывает, что есть такие библиотеки. Вопрос только в том, у кого получилось сделать что-то успешное. Да, вот в Джаве почему-то с этим проблемки, как ни странно. А я знаю, что в дотнете есть хорошие примеры ещё где-то, но в Джаве как-то это особо не принято, что ли. Может, в комментах нам накидают, скажут: "Вот, бери, юзай". Но мы решили, что вот сделаем минимальную такую штуку. Она, в принципе, простая, и можно пере переиспользовать, править в случае чего. То есть, ну, вот как-то так, наверное. О'кей. Ну, погнали, давай. Так, это у тебя common types, да? То есть types, да? Это types. Тут тут есть ещё различные, скажем так, базовые классы, которые нам иногда нужны. Э в, ну, допустим, вот идентификатор, да, есть Уит V7. Мы тут приделали. А седьмой версии, наверное, большинство знакомы, а, может быть, даже где-то использовали. Есть различные типы строк, которые, например, можно тоже там наследователь переиспользовать. Допустим, у меня есть где-нибудь ограничения, что строка не должна быть пустой. Ну, то есть not blank, да? То есть она не должна содержать пробелов. Ну, не должна состоять из пробелов, из ещё чего-нибудь, э, там, из стабуляции. И, собственно, зачем 100 раз её делать, да, когда можно сделать вот такую вот одну и её переиспользовать. Ну и какие-то другие штуки, там, региксы, номер телефона и так далее. Здесь где-то может какой-то, э, там, специфика проектов встречаться. То есть вот здесь вот, например, у нас торчит Passпоort numer. Это я не выпилил. А но прикол в том, что этот Passпоort используется в нескольких контекстах, в нескольких, э, модулях, и у него одинаковая бизнес-логика. То есть там по сути логика валидации и больше ничего. Мы его и вынесли в common. Вот, в принципе, для нас это нормально. Вот. А что тут ещё есть? Есть каунт, например, который просто счётчик, э, положительный, а не отрицательный, тоже где-то мы его переиспользуем. И все эти тайпы, они не зависят ни от чего. То есть здесь просто, ну вот arrow мы используем библиотеку, да, и в общем-то всё. То есть что-то ещё там для тестов. Ну уж хорошо, давай, давай. Да. А с событиями здесь вот как раз-таки ты говорил, да, есть всякие листинеры и прочие прочая инфраструктура для рассылки. Делаем простейший свой тоже интерфейс. Один умеет слушать, другой умеет публиковать. Вот это опять же такой вот бизнесовый интерфейс. Реализация его может быть абсолютно разный. Можно взять что-нибудь из фреймворков, а можно колхозить э transactional аутбоксы, как это мы и делали, для хранилищ, у которых нетрансакционных механизмов. Вот. То есть тут как угодно можно делать, просто я покажу, у меня есть простейшая реализация inmemory, так называемая. просто последовательно вызываются листинеры и всё. То есть это как было бы в обычных вызовах, когда операции над сущностями вызываются одна за одной. И давай посмотрим э на вот этот вот modду example. Внутри, собственно, содержится вот эта вот матрёшка. Ну, точнее, если помнишь, в чистой архитектуре есть такая такие концентрические круги. Вот. И самый центральный из них - это Добин. Да, он тоже ни на что не ни от чего не зависит, разве что от вот этих вот типов. И внутри домена реализовано два агрегата. Это топик, да, это вот, наверное, сейчас давайте верну картинку с с реализацией. Вот у нас есть тематика, у него есть идентификатор, название, ключевые слова. Вот этот агрегат. То есть у него есть ID, name, keyword, ну и, соответственно, все служебные поля, типа создан, когда версия и так далее. А есть Telegram-канал.
У него есть идентификатор, есть external ID, есть набор топиков, которому он принадлежит, есть статус отслеживаемый или неотслеживаемый. Вот, можно тоже посмотреть. И, в общем-то, всё. То есть, ну, какие-то служебные вещи тоже. А может немножко объяснить код? Я потому что, ну, понятно, у тебя конструктор. А что там дальше ниже? Да, сейчас я покажу. Я просто хотел показать сами агрегаты пока что. Вот. То, что они существуют и они соотносятся с тем, что мы сделали. Так, давай смотреть. Есть топик, есть у него конструктор, да, ну, обычный. И есть фабричный метод, наверное, можно его так назвать. Это статический метод. Называется он added. Что в нём происходит? Он возвращает либо ошибку, либо сам топик. Это вот такая вот конструкция из библиотеки Ier. То есть он возвращает либо левую часть, либо правую. Всё, он вернёт, точнее, либо вот такого типа штуку, либо вот такого. И мы проверяем, что имя ещё не занято. Вот если имя занято, возвращаем ошибку. Если всё хорошо, возвращаем сам топик и добавляем в него события, что что топик создан. То есть очень простая бизнес-логика. Проще некуда. Есть удаление. То есть мы говорим, что если топик ещё ни на кого не назначен, то мы можем удалять, да, просто добавляем события. Потом покажу, как это реализовано. А если же назначен, то мы возвращаем ошибку, что он уже кому-то назначен, и мы его не можем выпилить. Ну такая вот логика. То есть мы опираемся как бы, ну тут можно сказать: "Ну, у нас же есть там внешние ключи и так далее. Нахрена ты такой такие проверки делаешь?" А внешние ключи есть не всегда. То есть иногда у нас хранилище само не позволяет такое сделать. Можно пару вопросов по этой штуке тебе задам? Да. Смотри, если ты ниже перемотаешь, вот где ты проверяешь, что типа нейм такой там не занят, да? А покажи, пожалуйста. То есть фактически я правильно понимаю, что вот этот метод статический, который у тебя здесь есть, это, ну, в Крудах, грубо говоря, будет использоваться там при создании топиков, да, вместо new, потому что у тебя здесь есть кастомная логика с валидацие, да, то есть валидация у тебя внутри, и она может быть довольно сложной. То есть здесь всё-таки у нас пример простой. Вот этот ад может быть там, не знаю, строк 100 вполне себе кода, сложеный бизнес-логики, проверок, всяких инвариантов и так далее. Это мы таким образом конструируем объект. То есть это не сохранение ещё как таковое. То есть здесь есть как раз-таки разделение. Вот это вот это бизнес-логика. Дальше мы перейдём клике-логике, которая управляет всем этим. Да. Дальше вопрос мой. Смотри, у тебя получается из topic assigned по сути, несмотря на то, что это выглядит просто как функция, это же это функция или это метод? Я потому что с котлин хорошо знаю. Это это интерфейс. Вот. А это функциональный интерфейс, который содержит один метод. Этот интерфейс реализован кем-то. -э, самое главное, что мы здесь передаём, ну, по сути, выполняем вот это вот правило, что если ты на кого-то назначен, да, дружок, мы тебя удалить не можем. У тебя вывалится даже вот в добавлении открой, пожалуйста. Я именно про добавление. Я сейчас просто пытаюсь в голове картинку сложить. То есть, грубо говоря, откуда, а вот как это происходит. То есть ты фактически делаешь это через инверсию зависимости, грубо говоря, у тебя завязка на функциональный интерфейс. То есть получается, что я при добавлении сущности должен буду передать. Ну, допустим, у меня, ну, там валидаций, ну, много, может быть, да, скажем, десяток, а, которые связаны именно с базой. То есть я должен буду десяток фактически лямпт туда передать, поскольку функциональный интерфейс, которые, собственно, занимаются этими проверками. Правильно я понимаю? Ну, с десяток-то редко когда бывает, но какое-то количество может быть вполне себе, да. Это вопрос. У Владимира Хорикова, по-моему, есть статья про частоту модели, называется ДДДМА. И вот она решается разными способами. То есть там суть в чём? Мы не можем в каких-то случаях принимать решение об выполнении того или иного правила просто потому, что данные для этого правила не поместятся к нам в память. Ну, грубо говоря, вот здесь такой же пример, да, у нас может быть 10000 этих топиков, и мы не можем их загрузить в память, чтобы сказать, что он занят. Вот мы можем сделать интерфейс, который нам скажет, что типа чувак такой топик занят. Можно здесь, на самом деле, остановиться пока немножко, да, я тебе прямо позадаю вопросы, потому что здесь вот такие типовые вещи, с которыми я всегда, когда смотрел такой код, у меня возникали вопросы, и мне некому их было задать. Вот сейчас есть ты, как я говорил, я играю в адвоката Дьявола. А в первую очередь смотри ensure там если тра-тата create еро. То есть понятно, что Кстати, у нас предыдущий выпуск был промонады, так к слову, да, что айвер мы, естественно, там разбирали, ты, наверное, не видел, но мы как раз Хаскеля разбирали и там, собственно, про Монады говорили. Айвер, естественно, тоже. Во-первых, я понимаю, что всё-таки Айвер вряд ли является, ну, типа таким индустриальным стандартом. То есть это именно твой выбор использовать эту штуку, правильно я понимаю? Да. Ну, я м, скажем так, видел использование в Айр в в промышленном коде. Вот. И, ну, это, скажем так, редкая вещь, но, э, не исчезающая редкая. То есть я даже в процедурной лапше видел вот это вот. Ier вообще про то, почему здесь именно таким образом сделано, а не, допустим, исключение. У нас и на Ютубе есть ролики. Я отдельно даже выступал с докладом на подлодке, почему такое, э, почему оно сделано именно так. Если коротко, то есть исключения, которые мы никак не можем предусмотреть. Ну, грубо говоря, у нас отвалилась база данных. А есть ситуации, когда, ну, мы с этим что-то сделать можем. Грубо говоря, мы можем завершить, э, операцию корректную. То есть то, что у нас там что-то занято, да, имя наше занято, то это ничего страшного. Мы корректно завершаем операцию и выдаём пользователю ответ, что типа: "Ну ладно, чувак, так уж и быть". А исключительная ситуация - это когда у нас всё отвалилось и мы уже ничего поделать не можем. Да. Вот как раз-таки это наоборот хорошо, потому что действительно я часто видел, что для валидации пользуются, особенно когда там ручками, где пишут, да, там прямо валидация полностью такая на исключениях. Но даже несмотря на то, что здесь не исключение, здесь есть штука, которая вот с точки зрения Юикса не сработает. То есть вроде бы мы говорим про бизнес-логику, но у тебя UX потребует изменения её. Смотри, в чём прикол. У тебя стоит вот name already exist. И это вот прямо конкретная ошибка, которая возвращается, да, в зависимости от ситуации. Но когда мы говорим про вот просто представь себе форма, да, какая-то, в которую ты заполняешь, там имя, фамилия, пароль, тра-та-тата-тата. У тебя не бывает такого, что валидации показываются по одной. А это не валидация. Ты не путай. Тогда покажи, как бы, как это будет сосредоточено, как это будет связано. Да, это интересно. Да, я думаю, мы до этого чуть позже дойдём. Вот. Потому что валидация - это отдельная штука. Вот эти ошибки, это мы всё-таки относим к бизнес-ошибкам, так называемым. Да, она выглядит как будто бы, ну, типа что-то что такого имя занято. Это на Юае тоже требует определённого Ну, тебе обязательно надо будет показывать, если это создаёшь, да, определённых доработок. То есть ты, когда пишешь что-то, да, заполняешь, у тебя на Uае может быть ээ штука, которая сразу идёт и проверяет. Она не пытается создать, а просто проверяет, доступно или недоступно это имя. Вот. Но когда ты попытаешься какую-то ересь сюда отправить, нам тебе нужно отлуп сделать. То есть ты мы не можем создать, мы должны тебе ответить и сказать, что типа чувак такое имя уже занято. То же самое может быть, ну как более такой бизнесовый пример. Не хватает денег на счёте там или аккаунт заблокирован или что-то в этом духе, там счёт заблокирован. То есть это ошибка бизнесовая. У тебя не может быть их там сразу несколько. Обычно их так не бывает. Ну, в в каких-то случаях всё-таки есть, в большинстве случаев это, э, просто одна ошибка, которая блокирует тебе работу. И это не то же самое, что ты просто фигню вбил. Ты не можешь выполнить операцию по каким-то другим признакам. Всё-таки в формах чаще всего у тебя, я не могу себе представить форму, если у тебя есть разные там валидации, да, что у тебя показывается, типа ты жмёшь и у тебя ошибка сначала на одном поле показывается. Просто когда ты говоришь, что это не так будет. Вот у меня есть Kйword, допустим, да, вот здесь уже валидация. Допустим, что строка не должна быть пустой. И здесь, я вот не помню, я в пример приложил в этот или нет. В этом случае у тебя будет несколько, вот если ты засылаешь несколько вот таких вот кийвордов или ещё что-то в этом духе, то у тебя будет прямо полный список ошибок по каждому полю что-то ввёл не так. Это валидация пользователя. Ну, то же самое topic name taken. То есть у тебя получается как бы он на двух уровнях существует. То есть ты как бы и внутри проверяешь. И это очевидная бизнес-история, но валидация тоже её требует, потому что тебе, ну, с точки зрения любого нормального человека, если имейл существует, да, при регистрации, е, надо показать, что он уже существует и ты не можешь зарегистрироваться. Соответственно, получается, правильно я понимаю твоей логики, что у тебя есть, грубо говоря, внутренние проверки прямо вот жёсткие, и снаружи всё равно её придётся продублировать, потому что ты не можешь их смерть как бы снаружи проверки и вот эту проверку. Не, подожди, почему? Ну, э ты же на фронте всё равно делаешь. Вот вот эта вот штука, да, когда мне приезжает невалидная, я же её тоже отбиваю и показываю 400. Но на фронте у тебя при этом реализовано, э, скорее всего будет вот, да, в сингle page наших современных у тебя будет написано написан валидатор, который повторяет эти же проверки просто для того, чтобы не Я не про фронтенд. Нет. Смотри, вот то, что там на фронте реализовано, даже если оно реализовано, это не имеет значения, потому что мы всегда можем напрямую послать. То есть давай я упрощу тогда, грубо говоря. Давай пишка будет, пусть у тебя будет. Я просто не понимаю. А нет, обычная форма. Вот спа или не спай, не имеет значения. У тебя может быть серверный рендеринг, как у нас где прямо просто шаблонизация. Смысл в том, что это не в смысле каждое поле проверяется отдельно. У тебя просто приходит форма, формдата какая-нибудь на сервер. Он проверяет и, соответственно, возвращает одним единым там массивом, хэшом, неважно, объектом там по-разному может быть ошибок. То есть у тебя все ошибки в куче. И, допустим, у тебя регистрация, у тебя есть там пустые поля, у тебя есть, например, email, который может прич, давайте, чтобы усложнить и сразу довести, да, например, ты регистрируешь компанию и у тебя там уникальность по двум идёт вещам. Там, например, уникальность сразу идёт по имейлу, который ты указываешь. И по нейму компании именно с точки зрения, знаешь, в урле слаг это называется, который обычно при регистрации часто просят, чтобы, знаешь, какой-то под тебя даже под домен сделать. Под домен, да. И всё это делается одним запросом. То есть это один http запрос, и он тебе возвращает, и он прямо пишет, что там email такой уже есть, а вот эта штука, соответственно, тоже уже есть, надо поменять, ну и так далее. И вот вопрос. Я всё равно не понял, как в твоей системе ты получишь ответ со всеми этими вещами, потому что у тебя так реализовано вот сейчас внутри топика, и если это переложить на регистрацию юзера, то, ну, он всегда возвращает что-то одно. То есть у тебя этих иншуров, я правильно понимаю, будет несколько тогда. Ну, типа на email и на слаг компании. Да, я я да понял, про что ты говоришь. Если там действительно, допустим, нам нужно, ну, по пользовательской логике удобно показать, что вот у тебя там всё занято, то о'кей, мы можем вернуть несколько. Но обычно вот мы здесь не усложняли, обычно как бывает, вот у тебя заблокирован, допустим, счёт, всё, он заблокирован. Идти дальше нет смысла. У меня проверка дальше не пройдёт. У меня всё у меня закончится и выйдет. Понял идею? Слушай, ну в моей жизни ровно наоборот, в моей жизни это формы на 10, 20, 30 полей, в которых у тебя валидация у каждого поля. Вот это в моей жизни. У меня, То есть, если у меня заблокирован счёт и я не могу создать какую-то форму, ну, например, не знаю, ввести карточку, я, скорее всего, это будет скорее полиси, который не даёт мне, например, зайти, что-то там сделать, в принципе. Но если у меня, например, есть форма создания лендинга, вот, например, на Хекслете, да, у меня там полей штук 40 и валидации, естественно, много. Они они должны показываться все одновременно. Но валидация, смотри, валидация, э, если ты не ввёл какую-нибудь, э-э, там, не знаю, обязательное поле и, э, у тебя прилетит ошибка 400, там все поля, которые вот, ну, которые невалидны, они будут, а, они будут м показаны все сразу. Ну там вот именно такие проверки, я имею в виду. Мы давай, мы очевидно все понимаем, у меня так сказать пост тоже на эту тему как-то был, что у тебя валидация - это многоуровневая система. У тебя есть просто корректность введённых данных, например, там, а, совпадение паро Password, password confirmation, да, очевидным образом это к бизнес-логике не имеет никакого отношения. Это вопрос именно внешней ввода. То есть у тебя есть дальше бизнес-логика и так далее, но, например, там наличие или уникальность - это бизнес-логика, да, и, соответственно, оно у тебя внутри там должно проверяться. Ну или не внутри, зависит от того, как какой ты принцип выбрал, да. Вот мы здесь такой принцип выбрали. И поэтому в моём кейсе, ну, не бывает такого, что у тебя есть только одно какое-то бизнес-правило или они типа взаимоисключающие. То есть, как правило, многие из этих правил применяются к одной и той же форме. Я, например, даже ещё тебе усложнюю момент. Вот давай тебе скажу по поводу наших тильдов в, ну, мы их тильда называем лендингов, да. Вот на хексате лендинг, когда ты создаёшь, он ещё, знаешь, в чём там прикол? Мало того, что у него куча всяких полей, а которые вот должны проверяться, он ещё и не просто лендинг создаёт, а он при определённом выбранном там выбрать можно брать лендинг стильды. То есть, грубо говоря, у тебя появляется ещё поле, в котором ты вводишь идентификатор лендинга на тильде, то есть не в базе данных даже, да, а вообще в другой системе. И он его подгружает, потому что а мы фактически по опишке оттуда ещё грузим эту штуку. Угу. То есть у тебя ещё и внешний запрос существует. Да, я просто пытаюсь объяснить, что, допустим, вот мы делаем немножечко по-другому. Мы делаем То есть у вас пользователи не вводят большие формы? Не-не не, они могут вводить большие формы, но вот у тебя есть форма, допустим, я не знаю, ввода там данных автомобиля, там дохренища полей. Вот. И причём не одного автомобиля даже, а сразу нескольких. Вот мы делали систему такую влажной формы, кстати, да, когда ты это это можешь жмякать, у тебя Да. Дадада. Да. Вот много много. И смотри, когда ты вводишь э какую-то, ну, допустим, вин-номер автомобиля, у тебя на фронте срабатывает проверка та же самая, которая потом будет работать на бэкэнде. Фронendд тебе сразу говорит пользователю, что этот вин неверн. Дальше он вводит ещё одну, да, какую-нибудь штуку там, ГРЗ, ну, Го го гос государственный региционный регистрационный знак. Ему тоже подсказывает фронт, что здесь что-то не так. Он заполняет, заполняет. Потом, ну, смотри, он он вводит опять какой-нибудь вин. Ему говорят: "Такой вин уже существует". То есть, грубо говоря, чтобы вся эта логика сломалась, да, чтобы вернулась тебе ошибка, это надо накосячить на фронтде и пропустить эту всю фигню внутрь. Просто смотри, если у тебя вин существует, то ты мне хочешь сказать, что у вас не просто проверка формата идёт, а у вас ещё фронт на реально каждый изменения, каждый ввод, например, он смотрит: "Ага, вин поменялся". У вас реально есть ручка в Апи, куда он бьётся, чтобы конкретно вин проверить по конкретному правилу. Ну там не на каждый уж, но допустим, он заполнил форму, переходит к следующему по какому-то событию, там есть проверка, да, что типа вот такая вот фигня. Ну это как бы особенность реализации. А сколько вы тогда ручек делаете в опишке, чтобы все эти, то есть вам же получается по сути на каждую валидацию, которая требует, например, взаимодействия с базой или, как в моём случае, с внешней ещё системой, вам же по сути надо ручку делать в АИ, чтобы фронтенд там ходил. Ну то есть то есть я пока заполняю, получается, он просто постоянно там долбится, чтобы каждое конкретное поле валидировать. И оно, во-первых, не каждое конкретное, да, а там какие-то только определённые. Ну, вин, вот ты говоришь вин, например, что он дублю, да, по какому-то событии, естественно, это всё минимизируется, насколько можно. Вот. Но и я говорю, мы стараемся не допускать, чтобы в целом невалидный вод приехал на А если всё-таки приедет, то тогда у тебя проверится только вот первый иншур, который попадётся. То есть, грубо говоря, он выдаст только первый. Ну да. Ну это в конкретно в этом случае. А бывает, например, вот хороший хороший пример - это создание пароля, да, вспомнить там, сколько нужно всякой фигни учесть, типа не должно быть не должен быть пароль, допустим, коротким, не должен быть там таким сяким, не должен ещё быть похожим на предыдущий пароль. И вот эту вот ошибку уже можно собрать целиком и прислать пользователю. Ну, потому что мы ожидаем, что он пришлёт херню, условно. Ну, у тебя фронттенда может не быть. То есть, грубо говоря, если у тебя есть просто опишка какая-то, СДКшка, то твой подход вот здесь, который, да, и у которого нет фронтенда, который, собственно, все эти проверки делает, он приведёт к тому, что всё равно ты не можешь построить опишку. Ты просишь, что придираюсь, потому что я всё равно хочу понять, потому что для меня это был один из камней претновения всегда, потому что я видел много раз такие имплементации, и я никак не могу понять, как в такой системе я получу, ну вот, например, в Страйпе, да, ты посылаешь в опишку, у тебя там просто может ерров сразу 500 вернуться, там как там то не так, не так, ты это не указал, то не указал. Но получается, что в твоём случае, если я в опишку, вот ты сделал опишку, добавление, например, этих там машин проклятых, да, я не получу список весь, потому что всех ошибок, чтобы сразу его увидеть и сразу с этим что-то сделать, потому что у тебя так устроена вот эта система, что Ишур вот чётко, то есть либо его переписывать надо, либо это физически как будто бы невозможно в этой схеме. Ну если если мы говорим про уже конкретную опишку, то, ну, то есть про публичную опишку, там может работать по-другому, да, мы можем реализовать по-другому. А как обычно, знаешь, как я видел там, например, знаешь, как делается? Просто вначале формируется типа структура errors, дальше у тебя просто всё вот эти проверки не то есть, грубо говоря, иншур у тебя один, внутри просто erorс формируется и дальше, собственно, этот возвращается. То есть, грубо говоря, да, заполняется и возвращается. Вот что-то типа такого, да? Ну, можно так, да, сделать, можно так. То тут, я говорю, зависит от того, как будет использоваться, насколько это там нужно пользователю или не нужно. Вот в первом подходе про фронт я говорил то, что мы стараемся на фронте это всё прочекать заранее, да, проверить, что там всё хорошо, и только потом отправлять запрос. Если у нас какое-нибудь публичное IP, то там подход может быть другой. Действительно, там аккумулируются вот эти вот ошибки. А тут даже в этом в примере будет эта история, если мы доберёмся что-то мы уже просто сидим долго и всё, мы солим одну и ту же тему. Я, кстати, могу тебе сказать, что у нас в этом плане наоборот, то есть мы для у нас супертонкий фронт в этом плане, то есть, например, все формы, обработка, ошибки и так далее, это полностью бэкэндовая история. То есть фронт вообще в этом не участвует. Кроме HTML5, вот когда require, да, ты ставишь там вот какие-то минимальные вещи, связанные с работой самого htмля.
это решения сильно ниже. То есть представь, вот то, что я сейчас слышу от тебя говорит о том, что на фронтде нужно реально делать много работы, дублируя очень сильно ээнд. У нас же сделано так, что есть некий контракт того, как обрабатываются собственно ошибки. То есть, грубо говоря, у тебя с бэкэнда приходит, ну, там, правда, ещё используется специальная библиотека, которая этим занимается. Не, мы её написали, это вот современные подходы. Он возвращает тебе в определённом формате ответ. Собственно, там ошибка 422, это стандартный процесс быланности, когда у тебя ошибки, и возвращает тебе er, в котором, собственно, все эти ошибки. И дальше они с ключами, и он сам знает, где в форме это подставить. Понимаешь, у нас, грубо говоря, вообще ноль логики кроме вывода формы, подстановки лейблов, переводов. То есть у нас вот акцент на этом. А всё остальное ты жмякаешь сохранить, и тебе моментально прямо показывается по всем полям ответа. Вот я поэтому и спрашивал, потому что то, что ты рассказываешь, для меня этого вообще не существует. То есть мы немножко в другой парадигме существуем концептуально. А знаешь, почему может быть так? Мм потому что вот эта вот валидация хитрая, она, ну, какой-нибудь requкварет - это недостаточно. То есть вот в, не знаю, там, по-моему, в Inнн или вот подобных вещах там есть контрольная сумма. Вот. И как ты вычислишь эту контрольную сумму в require? тебя у тебя пользователь будет постоянно нажимать эту кнопку, и ему будет постоянно приходить: "Типа, чувак, ты ввёл фигню". Ну вообще постоянно у тебя так вся форма в интернете работает. Вообще форма, которая вот прямо по ходу дела тебе подсказывает, я тебе честно скажу, я крайне редко вижу. А особенно знаешь, что касается, например, что уже что-то существует. То есть, как правило, при отправке это происходит, что-то существует. Это, скажем, такой крайний кейс, да. Но всё равно мы стара стараемся обработать просто потому что там у тебя 100500 вот этих вот машин нужно вбить одновременно. И если ты будешь нажимать и тебе будут перекидывать на вот эти вот ошибки и ты заполнял, заполнял, у тебя контекст потерялся, там, не знаю, тебя ножом пырнут, просто найдут за такое. Ну реально там вот несколько десятков машин так водят, нажимают кнопку и тебе говорят: "А здесь у вас уже есть". Вот. И тебе надо скролить, разбираться, в чём там прикол. Вот сложная валидация. Вот про то, что я говорил. Есть контрольная сумма. Рекваредом ты не сделаешь. Вот тебе хочется, чтобы сразу Ну, потому что на беке, да, да. И приходится дублировать. Ну, мы пришли вот к такому подходу, потому что данные, ну, там чуть сложнее, чем чем required, опять же. Ну, я правильно понимаю, что у вас при таком подходе фактически, ну, нужен рядом плотная работа, постоянная с фронтендерами, потому что им, по сути, всё это полностью надо повторять. И ещё и говорю, в опишку ходить, если ты хочешь уникальность по базе проверить. Да. Да. Потому что одна у нас очень часто требования - это удобство интерфейса. То есть если мы покажем вот такую вот историю, типа отправляйте форму, нам придёт ответ. Вот. Ну, скажет: "Это что за хрень вообще?" То есть мы хотим сразу, чтобы всё свистело и пердело. Вот, чтобы сразу показывались ошибки и так далее. Мы не будем нажимать кнопку "Отправить для того, чтобы получить ошибки назад". То есть в каждом проекте? Ну, в большинстве, да. Хорошо, я тебе тогда сразу вопрос задам. Ты подскажешь мне хоть одну форму регистрации, в которой у тебя показывает, что email занят до того, как ты отправишь в форму? Вот чисто интересно, потому что она есть просто везде. Я давно не не регался, если честно. Не могу тебе сказать. Я просто никогда не видел, поэтому я и спрашиваю. Вот мне очень интересно ещё. Там, знаешь, такой есть прикол. Ну ладно, это уже тонкость, связанный вообще с тем, как работают интерфейсы. Такие вещи часто раздражают, если они сделаны неправильно. То есть там надо очень-очень их продумывать, да, чтобы вот как раз эта подсветка тебе мозг не выносила, потому что у тебя же ты можешь не вбил до конца, то есть, грубо говоря, там применять одну. Да, да, там есть этот прикол. Да. Да. И с масками какая-нибудь шляпа, они там криво, да? в процессе, пока ты набираешь или ты стирать начал, а у тебя поле всё равно красное, хотя на самом деле он ещё даже валидировать и него не должен. Поэтому когда ты про этохалось, да, это выглядит как экстремально сложная задача там где возможно поэтому я и думаю, что у вас фронт должен просто вот очень глубоко, очень серьёзно этим заниматься, прямо выдрачивая каждую детальку, чтобы это получилось. Прости, что столько времени мы это делаем, потому что всё-таки, мне кажется, это одна из самых базовых вещей, с которой вот человек столкнётся, ему надо это понимать. То есть это, мне кажется, вообще фундамент в плане взаимодействия. Я вообще делал видос про валидацию на час, наверное, на YouTube, который никто не смотрел, естественно, потому что душные не смотрят. Там вот этот вопрос тоже, э, поднимается, но такая штука решается какими-то стандартными компонентами. То есть у компании есть там дизайн-системы, привязаны к ней компоненты и так далее, где типовое поведение уже реализовано. Вот. И действительно, ты правильно говоришь, что реализовать такую штуку, чтобы она корректно работала, это надо хорошо постараться. Но когда появляется вот эта вот базовая фигня, уже гораздо проще. Мы вот буквально сегодня только столкнулись с тем, что маска ввода при копировании маска ввода телефона работает неправильно. Вот. и пошли к соседям посмотреть как у них реализовано -э потому что ну у них вроде та же самая дизайн-система. А что, что они сделали-то? Почему у них работает нормально, а у нас не работает? А при этом бывает валидация, которая вот не здесь находится, но она должна быть продублирована, потому что она не только на фронтенде. Например, если у тебя есть confirmation email или confirmation password, а в вашем случае это только фронтенд. Грубо говоря, если ты пошлёшь напрямую, то на вке вообще никто не знает про существование конфирмейшена. Что-то, если честно, не понял вопрос. Смотри, давай я попробую паспор, то есть будет ли он существовать на беке или логика, типа, когда вводят пасворд и confirmation password существует только во фронтнде, то есть в backк это даже не приходит. Не, ну confirmation password-то, наверное, и в к тоже придёт. Я, честно говоря, я почему спрашиваю? Потому что confirmation Password не является бизнес-логикой. Это всего лишь логика проверки корректности данных. У тебя в поле нет в базе поля такого нет сущности, да? И тогда получается, что у тебя, грубо говоря, ещё одно место, где происходит валидация. То есть фронтENД, контроллер, допустим, и ещё дальше внутри модели. Как будто бы так выглядит. Вот смотри, я тебе просто хотел более абстрактно ответить. Э такие ошибки можно ещё поделить на две части. Я их называю контекстные. И, блин, я уже забыл, как и, а, и структурная. Вот это чисто такая наша классификация. Но суть её заключается в чём? Когда у тебя есть структурная валидация, тебе достаточно того, что тебе достаточно самих данных и каких-то правил его их проверки. Ну, грубо говоря, там AIL, ты можешь там региксом провалидировать, вообще всё будет отлично. Вот. А контекстное - это когда тебе нужен уже контекст. То есть целиком контекст системы, чтобы проверить, что email, допустим, не уникальный или, наоборот, уникальный и не повторяется. Вот, соответственно, если валидация структурная, то это можно оставить на фронтде. Вот то, что ты привёл пример, там confirmation password там в целом она как бы структурная, но, э, по требованиям безопасности может быть придётся отправить её на бэкэнд и там всё такое. То есть это отдельный такой случай. А в целом, если у нас нету требований безопасности, и это просто там мы берём и вводим повторно, чтобы не ошибиться, вообще там есть же ещё кнопочка, да, показать пароль, то мы можем проверить и на фронте, и на бк передавать не надо. Такой ответ тебя устроит. Он немножко про другое, да. Я имел в виду, что, грубо говоря, даже если ты на БК отправляешь, да, а что у тебя не может быть внутри модели, потому что внутри модели этого понятия не существует. Оно исключительно про ввод данных, потому что внутри модели у тебя может проверяться опять же вот это качество твоего пароля, допустим, да, если ты говоришь, что он нор только для понимания, да, я я понял. То есть у тебя нету действительно в твоих сущностях там, ну, условно, да, в модели понятие confirmation password. У тебя есть просто существует, да. Ну и да. Если мы это реализуем на фронтде, если у тебя какие-то там вот эти вот проверки отличные от бэкэнда, потому что они у нас с БКОм не связаны, мы форму не отправляем и не ждём ответ от неё, условно, да, вот как в твоём примере, который ты приводил. А в этом случае м нам, ну, не составит никакого труда сделать отдельное поле на confirmation password. Ну и проверить его прямо на фронте. Если у нас отправляется форма и мы ждём ответ, чтобы потом показать его пользователю, тогда тебе нужно действительно сделать внутри там условно контроллера, да, вот эти вот поля Password, сравнить их и вернуть ответ. Вот я именно про это и говорил. То есть получается, что в общем случае у тебя три уровня: фронтENД, контроллер и моделька. И моделька. Да, да, да. На самом деле, как раз вот, чтобы картинка, вот теперь картинка уложилась, да, я не говорил, что я с ней согласен, но она уложилась в рамках той концепции, которую ты вот сейчас рассказываешь, да? А, хорошо, теперь всё понятно. М, есть один ещё нюанс, вот последний, перед тем, как пройти дальше. А меня очень смущает то, что у тебя мы же сущность создаём, да? Там сущность, которая вот занимается собой, что у тебя каждое действие вообще, что сущ что ивенты являются находятся внутри моделей, а не снаружи по отношению к ним. Я потом покажу, куда они деваются. Я не зря сказал, что это наиболе наименее убогий способ работы с событиями. Вот там их есть несколько способов, и они все довольно-таки так себе выглядят. Их действительно можно оставлять снаружи, но потом их всё равно надо будет запихивать в эти, ну, всякие листенеры и паблишеры и так далее. И это всё выглядит тоже довольно-таки странно. Но здесь события вынимаются из, э, сущности, и происходит это в определённом месте, до которого мы дойдём, я надеюсь. А то мы уже что-то долго сидим, блин. Я думаю, что мы вообще ни до чего не доберёмся. Давай дальше. Давай. Так, а с Telegram-каналом здесь примерно похожая история, но логика чуть сложнее. У нас есть метод трек, да, который, ну, как будто бы должен создавать э сущность. И это действительно так и есть. Если мы посмотрим на нашу вернёмся к ивентрмингу, у нас есть начать отслеживать и перестать отслеживать. Ну, собственно, вот эти вот методы трек и стоп-трекинг у нас как раз-таки сделаны. Причём я чуть усложнил и сделал следующее. У нас нет отдельного метода создания как такового, да? Мы начинаем трекать канал, а, и у нас есть external ID, там бла-бла-бла, всё это передаётся вовнутрь. Мы проверяем, есть ли уже, э, канал с таким с таким external ID. Если есть, то мы начинаем его трекать. То есть мы прямо вызываем startтреcking. Это приватный метод, а, который меняет статус и добавляет события. Если же нет, то мы его создаём, тоже добавляем события и начинаем его трекать. То есть вот такая логика. То есть как бы как это называется-то? Тут реализован конечный автомат. А вот и ты, соответственно, его там переводишь в какое-то состояние. Нене нет. В том в том плане, что я его как-то create or update. Вот типа такого логика. Но здесь бо больше, потому что если бы у тебя был просто кто а upпдейт, у тебя главная же фишка именно потому что в таком случае тебе бы не нужно было делать стартрекинг. Это бы просто называлось вот как ты сделал, что-то в духе, да? То есть у тебя по-другому, а здесь акцент идёт на то, что ты состояние переводишь его. То есть, грубо говоря, то, что он создаётся ещё по ходу дела - это типа дополнительный побочные эффект. Основной эффект всё-таки это перевод в состояние начать проверять. Но, кстати, здесь тогда, ну, типа он может быть добавлен, но не отслеживаем. Ну, ладно. У меня, конечно, вопросики тоже к этой логике, потому что он же не сам по себе отслеживается. Это же привязано к какому-то пользователю. Но это давай сейчас не Ну мы по мы пользователя здесь ээ игнорируем условно, да, потому что я вот говорил, он нам не особо нужен для критического пути. Ты имеешь, что, грубо говоря, если у нас просто канал добавлен, то есть мы в этот момент хотим из него, собственно, по трекингам ты понимаешь извлечение из него постов, правильно я понимаю, да? То есть мы отправляем событие в наш парсер. Вот оно будет отправлено. И парсер нам будет присылать посты из этого канала. О'кей, давай поехали. То есть нам событие нужно вот чисто для этого. Ну, соответственно, есть стоп-трекинг. А, и если он уже не трекается, то мы ничего не делаем. Если трекается, то переводим его в состояние и добавляем соначик, да, у нас обычный. Ну да, это не сейчас посмотрю, как это сделано. А это прямо выглядит как целый класс. Да, можно по-разному делать. Я тут ещё одну штуку хотел продемонстрировать, но по сути силит класс, ну, это условно разновидность инамом. Можно сделать и инамом. Иногда в такие статусы а можно ещё помещать данные. То есть, ну, допустим, отклонена заявка и можно поместить, а почему она отклонена? То есть для других статусов это не нужно, а вот для конкретно этого нужно. И это прямо очень удобно. Ну, с точки зрения именно модели. Вот что идём дальше. Давай. Есть у тебя ещё какие-нибудь каверзные вопросы? Ой, я бы, знаешь, есть Да, нет, тест- это, кстати, даже хорошо. Давай просто парочку посмотрим по тому коду. Для меня, честно, это, конечно, такой прям, ну, очень-очень рукопашная история, которая хорошо показывает какие-то моменты, но я бы просто так не писал, потому что, мм, ты тут, считая с нуля всё сам переизобретаешь. То есть у тебя там конечный автомат, полностью ручная имплементация, система валидации, полностью ручная имплементация и так далее, и так далее. То есть я, конечно, из из таких ребят, которые сами не пишем. Я просто не знаю, насколько это ручное, потому что, ну, статусы, если ты будешь делать там в любой системе, ну, конечный автомат тебе имеет смысл описывать или нет? Если у тебя там два-три статуса, э, как они переходят? у тебя само наличие этой штуки, это уже более того, смотри, тебе, например, если есть ограничение по автомату, это стандартные вещи, когда из какого стояния какой вент может произойти, тебе придётся всё это делать ручками, и, соответственно, Иншур у тебя будет расти, потому что у тебя там будет реально И, кстати, то есть мы же Ишур сейчасделали только на добавление, а теперь представь, ты делаешь апдейт, и у тебя, например, то есть, а, грубо говоря, чем классно выходить за рамки просто енамов, когда у тебя есть специализированная, например, библиотека по автоматам, кстати, она есть, например, в сприн Будете прямо встроенно. Да, только послушай отзывы про неё. Как согласен? Это другой вопрос, да, потому что если ты можешь вот можешь сейчас открыть, я тебе просто покажу для того, чтобы был контраст. А я просто, знаешь, ты амбассадор ДД, я амбассадор автоматного программирования. А я тебе хочу прямо для контраста показать. Попробуй найди такую штуку, называется на GitHubбе. GitHub GitHub в Гугле, а, AASM, ASM мы его называем GitHub. Пробел, да? A только о видишь, а то он тебя ам нашёл два раза. A, да, A, да. Вот всё набирай. Вот посмотри.
Вот. А, ну тут ты грамматики как будто бы прописываешь. Ой, не грамматику состояние. Конечный автомат. Да. Ты не только состояние, но ты описываешь возможные переходы. Он тебе из коробки даёт все методы, в том числе все предикаты. с проверками. Дальше ты это делаешь. Эта штука ещё на фоне связана с базой данных. Она даёт тебе лиснеры, она даёт тебе гарды, она даёт тебе колбеки различные и так далее. Ну, ты видишь там 50 экранов документации. И поэтому, конечно, когда каждый раз я вижу, когда автоматы полностью ручками делают, а мне немножко как тебе сказать, я понимаю, сколько работы придётся сделать для того, чтобы всё это заимплементировать. Тут даже есть интеграция Сай 18, чтобы ты понимал, чтобы переводить стейты в, ну, текстовые названия. Вот. Но это уже, конечно, не обязательно, но мы это используем. История, наверное, интересная, но модель состояний, наверное, не такая здесь развесистая, но она у тебя не реализована, да? То есть у тебя в Иншур придётся всегда проверять вариантов, да, и можно добавить вот в эти вот классы, даже если это будет просто, а можем ли мы перейти в следующий? И вот даже описание получится очень похожее, то, что мы сейчас видели. Вот если добавить метод, ээ, допустим, или там, да, да, да, типа не кен лучше не делать, просто типа тут такой-то статус и всё, и тебе вылетает ошибка, что нет, чувак, нельзя. Ну вот типа того, да. Так, ну что, э тестики тоже можно посмотреть кратенько. Они довольно простые. Э-э, они проверяют все исходы, которые есть у нас вообще, э, в нашем агрегате, в наших методах. То есть все ошибки, всё-всё будет проверено, будет проверено взаимодействие между, допустим, вот этими функциональными интерфейсами. Мы не знаем, кто их реализует. Бу будет проверено, что айдишники там сгенерились правильно, и всё в этом духе. Прости, а вот я вижу мог. А какую проблему решает? У нас вроде же тут чистый код, там вообще нету ничего. Мок решает, ээ, сейчас покажу. Базы нет, ничего нет. Вот мы мы макируем вот такие вот функциональные интерфейсы. То есть они же что-то нам должны вернуть. То есть это самопальный мог, который ээ это даже не фреймворк, это просто там интерфейс, который возвращает что-то гвоздями прибитые и всё. Ну, грубо говоря, ты мог просто тупо лямду туда передать. В общем, тупо лямду. Да, да, да. Но в лямбде, знаешь, в чём минус? Иногда бывают эти интерфейсы чуть более сложные, и мне хочется проверить, что параметры, которые были переданы внутрь, они, э, те, которые я ожидаю, да, да, да. Не, не вызывались, а которые, например, они могут вычисляться на ходу. Вот. И таким образом я проверяю и выходное значение, допустим, а, ну да, выходное значение э-э, который мне, допустим, что-то создаёт или же изменяет состояние. Также проверяю ну щупы вокруг зависимости, то есть всё ли в зависимости уехали в правильное направление с нужным значением. Вот. Ну супер. Теперь можно, наверное, переходить к кейсам использования, да? Уже да, кюзкейсам. US CASE - это для нас сценарии использования, скажем так. По-разному они называются у разных авторов. Кто-то интеракторы, кто-то ещё как-то. Но суть заключается в том, что если в домене у нас была бизнес-логика, а то в этихсах вызов этой бизнес-логики. То есть вот у нас есть ad topic use case. Что здесь происходит? Мы берём топик, пытаемся его вызвать. Если не получилось, то переводим это в ошибку. То есть здесь, а, а потом сохраняем в некий опять же интерфейс, да, мы не знаем его реализации, он выглядит вот так вот. Соответственно, это суперпростая штука, которая нужна только для того, чтобы, ну, если говорить прямо совсем грубо, достать что-то из базы данных и упихать это обратно. Вот. Причём мы не знаем, где это всё находится. У нас есть зависимость вот Save Topic. Как она реализована, мы не знаем. Ой, прости. А вот этот из topic nameen про прокидывается ещё выше, да, получается? То есть он по цепочке, да? Мы тоже не знаем, откуда он. То есть в данном проекте он будет реализован э через базу данных, потому что ну это по сути запрос, да, там сделать select exist и всё. А так это тоже становится зависимостью юзкейса. И где оно реализовано, мы не знаем. Вопрос. Угу. Я правильно понимаю, что CASE - это уже такой высокоуровневый слой, который уже потом используется, собственно, в контроллерах или есть ещё более высокий, потому что здесь очень больше нету, да? То есть там может быть от банального фактически просто объединения и так уже вот как здесь метода с тупо сохранением, так и уровень, когда у тебя там вообще выполняется любой сложности логика, которая вот должна выполниться в рамках одного ну юзкейса, скажем так. Сложная логика. Короче, прикол этих юзкейсов в чём? Здесь логика довольно-таки простая обычно. И даже вот у меня были агрегаты, которые, блин, я сидел, наверное, месяц пилил. Там очень сложная логика, какие-то хитрые вычисления и так далее, но, ээ, юзкейсы получились вот такусенькие, то есть вообще ни о чём. Ну, потому что, скорее всего, у тебя логика ушла в эти статические методы, да, которые там не это не статические методы. Это вот то, что ты называешь статическими, это методы агрегат - это бизнес-логика непосредственно. Вот. И да, действительно, всё ушло туда. То есть мы достаём агрегат из базы, мы вызываем э метод, который меняет его состояние, там какие-то сложные действия, вычисления выполняются. И дальше это всё как бы состояние изменилось. Мы это состояние убираем куда-то. Мы даже не знаем, куда. И всё. То есть, смотри, в классическом случае, в случае обычной там слоёнки с сервисами и так далее, у нас был бы вот здесь вот написано там topic сервис какой-нибудь, была бы вот эта логика извлечения топика, ну, точнее создания. Давай, давай другой пример возьмём. Давай возьмём вот этот пример какой-нибудь стоптреcking, да? То есть вот здесь видишь, да? Тоже какая маленькая фигня. У нас была бы логика извлечения этого несчастного канала. Затем логика, которая находится вот здесь, вот она бы была вытащена вот сюда. Вот здесь вот всё написано. А затем мы бы просто сохранили. То есть здесь просто ну немножко по-другому сгруппирован код. А по сути, ну его количество было бы примерно такое же. Мне, кстати, интересно, не перебор ли на каждый notфу свою собственную штуку делать? Я даже, кстати, не очень понимаю. Это опять же, поскольку я на котлине, ну, писал немножко, но давно это было. Вот этот channel not found - это вообще что? Это вызов метода какого-то? Да нет, это просто ошибка. Нет, само по себе. Что такое channel found с точки з А это это класс. Это класс. А мы прямо возвращаем класс. А, да, да, да, это же тот же Acerйer. Ну да. Просто data object - это, по-моему, это это в смысле значение этого класса или нет? Data object - это тип, то есть это дата объект. Object - это знаешь что? Это силтон. Ну я это и хотел сказать, потому что я помню, что там object, как в скале, там означает конкретное значение одно, что у тебя есть, но оно же не у класса. То есть channel not found возвращает, что в итоге? То есть это прямо сам класс или объект этого класса в единственном экземпляре, который это это сил тон, он будет создан единственный на всю систему. И чтобы каждый раз там условно не создавать на один и тот же объект, вот он будет возвращён вот именно вот так. То есть всё-таки объект этого класса, да, просто считаешь, что да, что это инстанс этого класса один и тот же всегда. Вот вот вот это инстанс этого класса. Я так вот я поэтому и спрашиваю. То есть прикинь, у тебя получается фактически на каждый фаoуund нужно эту штуку делать. Хотя, ну, во-первых, у тебя в принципе может быть одинный нофаунд без разделения, потому что ты это знаешь из выхода, да? А во-вторых, это просто дженерик может быть, который как раз параметризуется, чтобы было понятно, что было. Нет, параметризовать его особо не получится. Почему? Потому что мы делаем, я где-то рассказывал про эту историю, мы делаем такой вот запечатанный кейс, когда ты точно знаешь, что на выходе, что на входе. У тебя все значения валидные, потому что ты в обкте, а на выходе тоже всегда понимаешь, что и вот этот вот эр erрор, он на самом деле, ну, в реальных проектах он сильно больше. То есть там и то, что он не найден, и ещё какая-нибуд фигня, и маппинг ещё бизнес-ошибок. Причём они не всегда один в один мапятся. из агрегатов. В общем, у тебя получается такая большая портянка. И когда ты этот дёргаешь, допустим, уже в контроллере прикручиваешь, ты совершенно точно понимаешь, что тебе нужно передать на вход, а что у тебя будет на выходе. И воспользовавшись конструкциями языка, э, типа там when, ты можешь даже полагаться на компиляцию, когда ты добавишь новый исход и у тебя просто компиляция развалится, потому что ты не обработал какую-то конкретную ошибку. Понял идею? Ну, это, скажем, типизация, да. Да, это типизация. А во многих проектах этого нету. Здесь просто, ну, хорошо, если какой-нибудь эпtionн кинут, вот будет вообще отлично. Обычно он прорывается где-то из недр, и ты в контроллере не можешь предсказать, что он будет вообще. Ну, это не с ДД связано. Я бы, наверное, вот что сказал. Это нед тем ты как организуешь логику, да. Это это просто ортогональный вопрос тому, что мы рассматриваем. Но сразу сюда могу сказать, вот ты показал вот этот Insurer not now для find Telegram channel, но, например, я не вижу такого для save Telegram channel. Тут смотри, какая логика. Мы сначала находим это тоже функциональные интерфейсы. Обычно делают там репозиторию, условно. Э, я же разделяю на а отдельные функциональные интерфейсы, потому что так удобнее в итоге тестировать и эти интерфейсы разрабатывать. Вот. А что здесь у нас будет? Find Telegram channel. Он вот такую вот штуку возвращает. То есть это опциональная фигня. Тебя может не вернуться ничего. Ты ты тебе могли передать сюда несуществующий ID. Вот, соответственно, мы проверяем, что он существует. Если есть, то отлично. Мы делаем стоп, потом сохраняем. Если, ээ, не нашли, ну, мы должны как-то завершить операцию, сказать, типа, чувак, ну, ты передал какую-то фигню. Свайна понятно. А сейв он почему здесь проверки нет? А почему она должна быть? Эта штука поэтому я поэтому спрашиваю, да, может быть, здесь вопрос заключается в опциональности. Вот если мы написали not и вот здесь такую штуку, мы гарантируем, что channel вот этот, он не будет нул. А если нул, то значит мы отсюда уже вывалились. Вот в своём коде вижу прямо, знаешь, какая ошибка будет. Смотри, значит, save Telegram channel там ретрна нет, он подразумевается, да? То есть для тех, кто не знает, в трубе, в скале, в котлине у тебя последнее выражение всегда возвращается, да? А в в Хаскеле то же самое там, ну, вообще в функциональных языках во всех, наверное, так. А так вот грубо говоря, у тебя несохранение может произойти по нескольким причинам. Давай допустим, что в данном случае все причины исключительно связаны с самой базой, там конект или ещё что-то, и он у тебя кинет эксепшн. И тогда получается, что, несмотря на то, что ты прикрутил айвер, ты не избежи, потому что давайте пример скажу, вот классический пример, который всегда происходит. У тебя тот же самый уникальность, ты же не можешь 500 человеку показать. Вот если у тебя база отвалилась и ты ему показываешь 500, это нормально, потому что, ну, реально не работает. И человек видит 500, ты ему говоришь: "Блин, у меня что-то сломалось". Но если у него уникальность, он должен увидеть нормальные как бы сообщения об ошибке. А у тебя здесь этого нет. Да, но здесь метод называется стоптрекинг. Даже если бы ты это добавил, у тебя всегда в базе может сработать констraйн. Ну потому что не может сработать констraйн, да, но Да, и выглядит исключение. У нас, смотри, бизнес-логика а построена и логика приложения таким образом, что мы в целом эту ошибку нарушения констрей не не ожидаем, да? То есть мы вытащили канал, мы сказали: "Хватит его трекать". Мы его сохранили. Ну то есть мы у нас бизнес-логика выполнилась. Почему мы э почему у нас должно вывалиться какое-то исключение? Да, если база отвалилась, оно вывалится и а мы перехватим его где-то там, да, сверху. Там есть специальный обработчик, который этим занимается и не показы не показывает там стекрейс, да, выпавший, а просто красивую ошибку. Здесь же мы, ну, мы не ожидаем никакой больше ошибки. Всё, что здесь есть, да, всё, что мы выполнили, мы считаем корректным. Если с базы что-то пошло не так, ну, всё, мы мы А давай откроем другой кейс. Кейс именно добавление канала. А где вот этот предыдущий? Да. Вот смотри. Вот. А здесь это, э, может произойти, и это не является исключительной ситуацией, потому что у тебя уникальность опять же, если даже вот мы открываем документацию по любой библиотеке даже, да, с валидациями, всегда там написано, что, ребята, вы всегда должны учитывать, что если вы какой-то там не делаете, там есть абсёрты такие хитрые или что-то с проверкой с проверками, да, но если, короче, вы просто делаете такую вставку, а, а там уникальность, ваши любые проверки, если у вас достаточно интенсивное приложение, да, здесь здесь есть, да, Да, здесь есть такая штука. Действительно, вот тут из topic name taken. И вот тут здесь может между этими двумя штуками произойти как раз-таки то, что ты говоришь. А в интенсивных приложениях мы действительно можем написать, что у нас возвращается ошибка, что уникальность нарушена. Вот, поскольку мы здесь не успели условно, да, сохраниться, но почему я здесь вот эту штуку дублирую? Для того, чтобы, во-первых, не во всех хранилищах есть такая возможность. Ну, то есть у нас может быть, представь, у нас какой-нибудь HBASE, да, и там уникальность, ну, по сути, есть только по ключу, а нам нужно как-то проконтролировать вот эту вот фигню. Я уж так фантазирую, но вот бывает так, что нету возможности на уровне с УБД это сделать. Вот. Не, ну это тогда вообще это тогда какая-то у тебя тогда будет корапшные данные. Я вообще тогда не понимаю, как работать, если ты не можешь гарантировать уникальность. Ну это просто гарантируется другими способами. Вот можно гарантировать. У тебя всё равно как есть concurrнency, которая Ну давай, допустим, у нас позгрессу, чтобы не усложнять сейчас, да? Вот всё-таки мы типовой случай рассматриваем. А я просто пытаюсь понять, на каком уровне ты будешь это обрабатывать. То есть у тебя есть два варианта, правильно я это понимаю? Что либо ты прямо здеськеч делаешь, чтобы всё-таки viбе это завернуть и снаружи об этом не знали, либо если ты делаешь снаружи, для меня это выглядит с как бы для меня это выглядит уже как не очень дизайн. Объясню, почему. Потому что ты с одной стороны говоришь: "Ребята, мы возвращаем Айвер с ошибкой, но есть некоторые ошибки, которые вам всё равно придётся ловить". Нет, смотри, если оно будет действительно интенсивным, и такое бывает, когда у тебя база не успевается, то мы в интерфейс можем заложить ошибку нарушения уникальности. Сам сейфтопик внутрь. Да, да, да. Но при этом тогда у тебя здесь должна быть обработка. То есть, грубо говоря, ты проверяешь будет здесь. Здесь ну не сделан. А, я понял. А он сам тогда авер что ли получается тоже вернёт или в данном случае? Потому что TRS тебе явно не хватит. Тебе же нужно всё-таки ошибки эти сами наружу вытащить. Да, можно вернуть Айзер в зависимости от того, какое количество этих ошибок. Вот можно действительно вернуть IS. А, но, допустим, вот эту штуку я отсюда тоже не убираю. Я уже говорил, да, почему. Потому что не везде есть. Во-первых. Во-вторых, бизнес-логика тогда фрагментируется. Это опять же к вопросу о чистых моделях и так далее. Ну, есть такая проблемка там, потому что ладно, знаешь, то есть у тебя, грубо говоря, сейф топику нужно будет знать обо всех кейсах. Прикол же в чём? У тебя, когда база бросает исключение, она тебе не будет разделять, по какому полю это исключение возникло, да? То есть вот не получилось вставить, это где-то там в месседже будет. То есть, фактически, если ты будешь обработку ставить вот именно в сейф топике, который работает со всеми полями, которые могли добавить, да, и в любой момент там что-то, ну, ну, типа уникальность по трём полям, тебе придётся тогда внутри делать не просто трайкеч, а ещё кетч, в котором ты берёшь и прямо по текстам сообщений пытаешься понять, к какому полю это относят. Ну, может быть, так, да. Ну, в зависимости от реализации базы. Вот где-то вообще, ну, скорее драйвера. Драйвера, да, там что может ещё, может быть, он действительно там за тебя частично это делает. Почему спрашиваю? Потому что я действительно хочу увидеть всё это такое, потому что для меня история, вот то, что мы сейчас обсуждаем, это не история супер прямо интенсивности. Это вообще очень легко может получиться. И поэтому, например, такую штуку мы, а у нас она сразу встроена. Но опять же, тут спасибо фреймворку, он за нас это делает и за нас всё это красивенько обрабатывает. Да, блин, только код за вас не пишет, но это уже как посмотреть. А это уже агенты пишут. Я поэтому я пойду со стороны халявщика на всё это смотрю, за которого всё это делается. Да, да, это отличный вопрос. И действительно, такое может быть. Вот. Ээ, но тем не менее мы бизнес-логику дублируем, чтобы она не фрагментировалась. Вот. Есть у тебя хоть один кейс, который бы сейчас посмотрели, где уже есть комбинация? Потому что интересно, потому что там ты уже, как ни крути, тебе придётся на этом уровне уже транзакцию включать в кейсе, потому что у тебя будет save toпиic, дальше save Telegram-канал, ну и так далее, правильно? Вид или у тебя будет единый какой-то сейв, который под под кейс будет создан, в котором это происходит. Нет, у нас здесь пока, ну, не пока что здесь единый сейф. Вот мы его, ну, не делали отдельно каким-то. Вот он басгрисовый, а в нём есть транзакции и так далее. Вот. Ну, я до него плавно хотел добраться, да. Ну да, да. Показать, как это может быть реализовано. Я не знаю. У меня такое ощущение, что мы уже не успеем написать ничего нового. Поскольку мы А слушай, я уже почувствовал, что даже особо и не надо, потому что на самом деле разбор круто, что ты заранее сделал вот этот код, потому что раз, ну, фактически я-то как себе представлял, что вот именно этот код мы писать и будем. И поскольку он уже есть, то, в принципе, новое нам особо не нужно. А вот разобрать все эти нюансы нужно. Объясню, почему. Потому что, если мы будем просто писать код, какая главная претензия к ДД, почему, собственно, я хочу всё это рассказывать, да? Именно к тому, что, ну, немало инженеров, и я считаю, что обоснованно во многом считают это овер инжинирингом. И мне вот как раз интересно, когда ты вот эту систему уже построил, побыть адвокатом Diья и, соответственно, позадавать вот эти каверзные вопросы, которые вот сейчас мы с тобой разбираем. Соответственно, здесь уже достаточно кода для того, чтобы можно было понять, а стоит ли это писать или нет, потому что использование как раз-таки оно очевидней. То есть всё, ты вот этот каркас показал, дальше очевидным образом, ну, я у себя в контроллере это дёрнул, да, а дальшето понятно, что происходит. Поэтому я бы, знаешь, что ещё вот разобрал. То есть мне интересно, конечно, посмотреть сохранение баз данных, вот топик аля свой такой репозиторий, который ты написал, да, я тебе тоже задам несколько каверзных вопросов. И кейсы сложные, где у тебя сущностей изменяется много. То есть, во-первых, можно ли комбинировать кейсы? Как-то это с транзакциями связано, ну, понимаешь, да, вот эти вот все моменты, как это соединяем, это классические вопросы вообще, да, и что у тебя там происходит внутри. Вот, вот это супер интересно, поэтому я бы забил на написание нового кода. Ну ладно, как, как скажешь. Я так готовился, не зря писался. Ну, ты классно приготовился, вообще офигенный код. Я, честно тебе скажу, я вот так ДД никогда не разбирал, потому что вот когда я начал погружаться, как правило, ты такой в какой-то момент думаешь: "Блин, ну тут перебор". Вот поэтому вот смотри, э если мы смотрим вот прямо конкретно вот этот код, да, где мы просто что-то добавили, там убавили и так далее, это м довольно-таки примитивная штука. Вот. То есть, ну, это как будто это действительно выглядит как крут. Я, конечно, пытался тут что-то привнести, но, э-э, выглядит как крут. Если мы говорим про коммерческие системы, где там сложные правила и так далее, ДД он спасает спасает твою жопу, потому что там код настолько путаный получается, что ты его нормально, если ты возьмёшь классический подход, вот слоёнку, ты его не протестировать нормально не сможешь, а со временем не сможешь уже и поддерживать. У тебя сложность увеличится настолько, что там будут просто сплошные макароны. Вот я, к сожалению, не могу показать коммерческий, хотя у меня есть сложный, прямо очень сложные куски. Может быть, ещё в конце покажу немножечко такого, знаешь, учебного, но он чуть-чуть посложнее, не то, что мы здесь делаем. Вот попробую его открыть. Если всё ок будет, то продемонстрирую тебе. Ну вот минимально то, что мы могли бы увидеть, что нам помогло бы. Ну, знаешь, хоть какая-то комбинация, чтобы не просто как крут. Давай вот попробуем. Если мы хотим написать, можно попробовать это сделать. Я не уверен, что здесь это есть. То есть, допустим, а у нас есть сейчас топик в вот давай прямо в топик, да? То есть у тебя, когда остаётся топик, что топик? Мы под топиком, кстати, в если мы говорим про Telegram, то слово топик не совсем верное. Знаешь, почему? Потому что топиком там есть название топик. Это когда ты в чате делаешь вот эти вот слева, как как каналы в слаке, они там попиками называются, поэтому конечно, да, поэтому правильно было пост назвать, да, но здесь мы по Не пост, не это не пост, это же название тем, которые есть у нас непосредственно. То есть это наш классификатор тема. Я почему-то А пост - это пост, но я его не не сделал. А ты его не сделал? Давай. Я его как раз и хотел сделать. Супер. Давай тогда, как насчёт того, чтобы сделать следующий кейс, чтобы и то, и то было связано. То есть у нас Telegram-канал связан с топиком, правильно я понимаю? У нас же есть там такая связь, да? Да. А один один ко многим, да? То есть один топик, много каналов. М у канала один, может быть многие ко многим даже. Сейчас А ты как реализовал? Се, посмотри. Соображаю. Так, ну у одного канала может быть много топиков. Вот так вот реализова. А ты реализовал как-то это или этого нет? Да, да, я это реализовал. Собственно, как у нас это сделано? А я имел в виду, в смысле, в сущности, в самой, не в базе, да, в сущности, потому что у меня сейчас как раз есть идея, как мы сейчас с тобой более сложный кейс сделаем. Да, вот есть канал и вот есть сетic ID. Это называется, если не ошибаюсь, detched агрегаты, когда ты ссылаешься на другой агрегат по ссылке, то есть ты не можешь его сюда запихать. Ну это просто как обычно to это именно да, но ты его назвал, кстати, tops, а не топикID ids, да? Да. Ну, в целом, как бы пофигу, да. А кто кто мне что предъявит-то за это? Да. Так вот, собственно, вопрос. Давай предположим такой кейс. Хочется говорю более сложный. когда мы добавляем Telegram-канал. Угу. то, а-а, с одной стороны ну, допустим, нам приходит сразу там неважно какой, как, допустим, что можно при добавлении канала указать топики, к чему он относится, да, и вот они к нам приходят. Угу. Как тебе такая логика, что в топиках при этом хранится количество каналов, которое к ним привязано. И получается, что мы создание нового Telegram-канала и одновременном добавление топиков, потому, ну, допустим, форма прямо такая, да, добавляешь канал и должен выбрать эти топики. Мы в этих топиках плюс один делаем накаунт. То есть прямо три топика было типа channels count. Вот такая вот была штука. И тогда получается, что твой кейс как бы обязан менять две сущности и состояние. И у тебя получается действительно комплексная вот эта проблема. Грубо говоря, ты это сделаешь двумя кейсами или тогда это будет один кейс, но тогда внутри тебе придётся вызывать два метода сохранения, а значит, нужна транзакция. И возникает куча интересных вопросов, которые вот здесь вот не видны из-за того, что всё очень прямолинейное. Да. Смотри, давай попробуем разобраться. Во-первых, нужно будет понять, как мы будем хранить эти каунты э в самом топике или ещё как-то. То есть там есть давай для простоты channel прямо вот назовём такое поле, пусть там будет, да. Э, второе, что хочу сказать, возможно, это решалось бы как-нибудь, ээ, как-нибудь по по-другому, и мы бы, например, сделали какой-нибудь запрос. То есть мы бы не хранили внутри топиков, сколько к ним каналов приделано, да, а просто сделали каунт по связям и всё. Понял, да? Но при этом это, да, в простейшем случае, как мы с тобой тогда и обсуждали, это так, но у тебя, допустим, вот мы уже дошли до точки, когда выводится, ну, нужна, во-первых, сортировка и выводится это всё сразу как бы, например, там в каком-нибудь каталоге. То есть в таком случае, если у тебя объём большой, тормозить начнёт однозначно. То есть надо будет это так называемые мы их называем в рельсе кэширующие счётчики. То есть когда у тебя есть вот каунты, там даже есть, во-первых, это функционально встроено, мы вообще это ручками не делаем, то, что вот мы сейчас собираемся сделать. Во-вторых, там есть прямо стандартменования. Ты просто берёшь название сущности и подчёркивание каунт туда добавляешь. Вот оно так работает. А, но самое главное, что это показательная вещь в плане чего? То есть какая разница, каунты - это или не каунты. То есть у тебя же концепция не поменяется. Грубо говоря, если у тебя одна сущность приводит к тому, что нужно изменить вторую. Есть, например, даже, знаешь, часто бывает такое понятие тач, то есть, грубо говоря, если у тебя у агрегата хоть одна сущность меняется, тебе нужно сам агрегат отметить, что он его меняли для того, чтобы там, не знаю, где-то в событии, ну, короче, в какой-то активности это проверять. Логика же одна и та же будет, правильно? Поэтому мы сейчас можем с тобой вот эту концепцию на счётчике реализовать, потому что она тупая, как дрова, а заодно будет видно, как ты соединяешь вот разные элементы, да, можно сделать следующим образом. Действительно, э, есть вот эти вот цепочки вызовов, допустим, ну вот там, если если я не умру до конца эфира и покажу всё-таки, э, как мы э сделали определённую там определённый проект, там, допустим, у тебя заявка на получение кредита, если она подтверждается, тебе создают счёт автоматически. То есть там вот вызывается э слушатель, который это всё связывает между собой. Дальше вызывается ещё что-нибудь. А там сначала создаётся пользователь, потом создаётся счёт для него и для счёта ещё какая-то фигня. То есть там такая прямо цепочка вызовов. Ну давай здесь тоже покажем, как это может выглядеть. Давай сделаем такой вот. Э, я хотел на самом деле сделать, даже у промпто написал для того, чтобы реализовать процедуру назначения топика, точнее функцию назна метод назначения топика, но сделаем его чутьчуть по-другому. Давай вот так вот назовём assign topic, да? Ну и пусть здесь топик вот так вот, да, он будет. Аган.
У нас есть топики. Мы пущ добавим сюда. У нас, кстати, должно быть какой-нибудь даже mutable set, может быть. Так. И тогда
так вот. Понятное дело, мы его не должны ни в коем случае, если он мутабл, в таком виде палить. Но это ладно, это уже детали. Мы сейчас туто тут до утра будем сидеть. Вот у нас есть топик. Мы говорим тебе
ifics
contains, да, у нас topic id
не contents, точнее, то мы топик добавляем. Может быть, кстати, есть какая-нибудь специальная операция для добавления отсутствующие элементы, но я просто хочу, точнее проверки и добавления, если его нету. Да, дада. Add if что-нибудь в таком. Дадада. Я я не помню. И здесь мы должны создать какое-нибудь событие типа а Telegram
Telegram channel events. Ну, допустим, топиicт. Сейчас давай мы его сделаем. Блин, я тоже руками что-то давно не писал. Всё, вайбкожу.
Топик, топик. Пока пишешь, кстати, скажу, что я вот для себя разделяю, что для меня вайп-кодинг всё-таки это когда ты вообще, ну, в код не смотришь, не включаешь мозги, да? А ты просто в код не смотришь. У тебя просто вот тебе нужна задача и всё, и ты можешь, не знаю, проехать. А когда ты программируешь именно с помощью Ишки, то у меня это это больше на парное программирование похоже. То есть нет того, что я типа ну похер, что пишет, главная задача решена была. Нет, ты нормальный код пишешь. Ну вот, кстати, я тут тоже ещё хотел показать, но, наверное, мы уже не успеем. А по каждому из этих элементов у меня написаны правила вот, а которые берутся потомишкой для реализации. То есть вот, например, как мы там файлы и обжекты пишем. И такая штука позволяет писать быстро. При этом ты, ну, хотя бы понимаешь, что происходит. Если у тебя нет никаких правил, никакой структуры и ты просто там чудесная машина, сделай мне что-нибудь, тут можно случайно чего-нибудь натворить.
Так, вот мы сделали класс такой. Я тебе один вопрос хотел задать. Давай. А mapст будет работать э с твоими штуками? То есть дшки как ты будешь делать вот в этой системе? Мабстракт мы не используем. Мы вручную это всё делаем. С мабстрактом тоже есть есть вопросики к нему, скажем так, потому что натыкались на очень интересные вещи. Потом мы теряясь переделывали всё это. Так, иди. Так, сейчас, да, evпик у нас ID и, ну, там клок и генератор нам нужно передать. Всё. То есть у нас вот появился такой метод, который нам говорит вот topic. Мы, если его нету, назначаем всё. Дальше нам нужно сделать в топике Что там у нас должно быть-то, господи? Channel count. Channels count. Да у тебя может быть, ну заодно и события посмотрели. А у нас будет channels нам важно знать, к какому каналу сколько привязано или просто сколько у тебя есть? Ээ не, ну в данном случае просто общую сумму смотрим, да. Угу. Ну вот так вот пускай будет. Назовём это count. И при создании мы просто указываем, ну ноль. Да. Что на каунт
Так, а идём дальше. Нам нужно будет создать пару юзкейсов, которые будут заниматься вот таким вот непотребством, то есть назначать топики. Ну, я думаю, мы сейчас откуда-нибудь скопипастим и просто протащим их сюда. А реакцию на ивент, а вот мы потом это свяжем. То есть, ну, я не знаю, стоит отдельно делать, да? Давай нам тоже нужен. Да, да, да, давай вот трек channл там. Ой, нет, давай лучше стоп. Вот мы его прямо скопипастим и назовём assign topic use case.
Оно сейчас не будет компилироваться, потому что мы там кое-где что кое-что испортили. А и вот так вот кейсы. А испортить это хорошо? Да, сейчас испортим немножко. Так, а что он ругается? А где у нас?
Хорошо, когда код писать не надо, сидишь, не нервничаешь. Я говорю, хорошо, когда код писать не надо, сидишь, не нервничаешь. Ну да. Так. А так вот и found. Ой, пропал. Соответственно, здесь вот что нам нужно ещё сделать? Нам нужно channel
topic сделать. Для этого нужно взять откуда-то топик. И давай поищем его. Prevate W find topic. Вот у нас есть уже такой интерфейс готовый.
И здесь тоже мы вот так вот напишем сначала.
Попик. Ой, а здесь будет ещё одна ошибка, которой нету. Не было. Это topic not found. Topic not found. Блин, что он на Declaration ругается, я не понимаю никак. Ну ладно. И здесь, а, topic not found у нас возвращается. Топик. И здесь будет не просто ID, а топик ID.
Стопкинг Telegram. У меня знаешь какой? Наверное, сразу Ну ладно, давай ты пиши у меня там вопросики, давай, а то я уже этот, я уже хлебушек.
А, подожди, а почему стоптрекинг-то? А сигна топик же.
Тут ещё он добавил channel. Дадада. Да, CAS сейчас уберём. Вот он чтоation. Я уже настолько ээ не того, что вот вот так же оно должно быть. Во. Ну и, соответственно, мы делаем вот так вот назначение и у нас уходит сохранение. А дальше мы пишем такую штуку, называется она листинер, который просто свяжет это событие, назначение и А, подожди, мы ещё же в топике не сделали ничего. Мы же не мы не сделали инкремент. Вот что, допустим,
channel countт. Ну, соответственно, там декремент можно тоже сделать. И мы просто берём, а,
channels count.
Он, кстати, варом должен быть у нас.
Но channel count. А инкремент. Инкремент может вернуть тоже ошибку, кстати, Эйзер, но мы на неё сейчас забьём, потому что мы, э, тогда забивате никогда не сделаем, да? То есть здесь мы тоже можем добавить, что типа мы не можем инкрементировать, потому что достигли там максимального уровня, вот максимального значения, чтобы не было переполнения. Но вот в каунте мы сделали такой такую ошибку. Вот. А и здесь тоже мы добавляем события. Event Telegram. Что это? А у нас topic event даvs. Допустим, channels in
только channels channels
так. И да, он что-то не предлагает создать класс. [ __ ] такая. Ну ладно,
соответственно, у нас будет так вот. Ну, можем ещё в событие добавить количество, если нам это нужно. Обычно хорошо для логирования, когда мы это всё вводим.
Вот у нас получилась такая история. И мы сюда запишем,
а, channels countic ID, это будет айдишник наш. Клок. И
вот это событие, на самом деле нафиг никому не нужно. Разве что для логирования и для метрик. Сколько у нас добавилось? А потом мы пишем а такой вот листенер. Мы сделаем,
а инкремент вот для м канала мы сделали, а для топика у нас будет листинеer, который по сути тоже являетсякейсом. Просто более выразительное название.
Lister.
Вот абстракт. Ой, почему абстрактнер.
Всё, я окончательно уже погас.
Red Bull должен был быть спонсором сегодняшнего выпуска. Нет, это для бумеров вроде нас это уже не работает.
О'кей. Так, домей. Это домей ивент листинер у нас, который слушает, а,
который что у нас слушает? Channel event. Channel, да. channel event,
а, который топик осигнет. И здесь нужно два метода реализовать.
Здесь будет такая вот штука. То есть понимать, а, ну, к какому классу это относится. Я думаю, во фреймворках то же самое сделано. Прописать. А здесь непосредственно, ну, та же самая логика. Э должны взять сначала
м так, fн toпик. Ну и вот мы берём этот финтопик
ID. А откуда мы возьмём ID? Из ивента.
Подожди, а мы не добавили, что ли?
Давай сделать. А вал надо вал написать то тогда он просто не видит.
Вот
так. Да, топик. Угу. И дальше мы что делаем? Мы проверяем, если топик Авто никаких нет. А что? Ну, я имею в виду F topic может nл вернуть ведь нам. Да, да, да, да, да. И вот мы должны проверить, что это check note, nal или сейчас, подожди, не чек есть, да? Чек. Check not now. Здесь пишем ошибку. Сейчас я поясню, почему здесь нету никаких айзеров и прочий фигней. Не, ну тут очевидно у тебя асинхронная логика. вызова нет как такового, чтобы Да-да, да. тебе, ну, ты у тебя неконсистентность данных фактически случилась каким-то образом. Топик, а инкремент count. Я написал count, представляешь? Вот. Ну, почему нет? Пусть будет count. И дальше save toпик.
А в Лисенерк там инъекция происходит, откуда он вообще знает? Да, он это настраивается уже непосредственно в при конфигурации контекста. А, ну я понял, понял. Да, да-да. И когда мы публикуем события, эта штука понимает, что надо вызвать вот эту вот фигню. Вот. Ну вот это уже интересно, да? То есть вот получилась такая связь. Там, на самом деле, знаешь, что там вот в этих событийных штуках, да и не только на самом деле событийных, есть ещё огромное количество граблей, вот которые пока не начнёшь щупать, не наткнёшься. А я вот как раз хотел сказать, ну, во-первых, в твоём случае это всё абсолютно синхронно, а, и вызывается прямо здесь, прямо сейчас. Во-первых, мм, мы даже про синхронность пока не говорим, просто говорим про транзакционность. Ээ, где здесь транзакци транзакция здесь обеспечивается уровнем ниже. Мы до него с тобой не дошли. Вот у нас есть такой слой Postg persistance. И что он делает? А у нас есть различные методы типа save, да? То есть это то, что на изменения мы проверяем там, что события у нас только на исерт, там не на апдейт, ну это уже как бы в частности. И открывается транзакция, которая будет держаться до тех пор, пока не закроются все лисценеры. Вот здесь вот мы публикуем события, и здесь же они вызываются. То есть у нас А давай-ка я тебе сейчас нарисую, потому что, ну, я понял идею. То есть у тебя, грубо говоря, это происходит именно за счёт того, что ты вот сейв там топик сделал или там save channel, с него всё началось, и именно он у тебя внутри вызывает ивенты и делает. А, слушай, эта система прямо я сразу могут я понял как бы идею, что ты сделал, но сразу вопрос. Но у тебя же, смотри, допустим, мы же сначала что вызвали save channel, да? В свою очередь он привёл к событию. То есть у тебя транзакция открылась внутри save channel, он это начал делать, но у тебя же внутри вызывается save топик, который в свою очередь делает ровно то же самое. У тебя появляется такая матрёшка, что приводит к большому количеству вложенных транзакций. Если у тебя транзакция здесь не открывается, это спринговая фишка, ты можешь говорить, это propagation называется в спринге. То есть, если транзакция уже есть, он не будет её вызывать, открывать заново. Ты ты более того, ты можешь ты можешь даже настраивать, то есть в каких-то случаях ты можешь открывать, в каких-то нет. Угу. Вот. То есть это прямо гибкая такая штука. Хорошо. А вот допустим, что у тебя вот тоже давай прямо сразу классную вещь. Допустим, у тебя мм улетает письмо ещё при этом при инкременте, да? Вышла событие. Давай вот представим, что на это событие, что у тебя добавился именно не чаanл, а вот инкремент этого. Да, мы улетает письмо. Правильно я понимаю, что в твоей системе, учитывая, что она не умеет сама со всем этим работать, единственный способ - это transaacion outбок, собственно, реализовывать. То есть тебе надо именно в том лисенере, по сути, ну, там создать ещё табличку ивентов и в том лисенере, собственно, это добро сложить, чтобы потом уже асинхронно это добро обрабатывать. Ну, здесь просто здесь в примере нету сохранения событий именно в базу. А так, есм вот этот вот лисенер, э, взять реализацию, который уже прихранивает события, да, и отправляет их, э, ну, дальше, если это нужно. Ну, я имею в виду просто грубо говоря сама эта система твоя может, конечно, технически там либо отправить письмо, либо положить что-то в кавку, допустим, но она не имеет права это делать, потому что у тебя в рамках транзакции получится, она это делает. Во-первых, у тебя сеть там и всё такое, во-вторых, всё потеряется. Поэтому здесь только база. Только база, да. Ну, только база. А и дальше уже, э, если мы события это все сохранили, Аутбокс понимает, что для него есть новые события, вытаскивает и идёт уже рассылать письма. О'кей, я понял. Ну или там в кавку перекладывает. Понятно. Можно, можно сделать, наверное, какую-нибудь, э, двух как-то двухфазную транзакцию, но там тоже есть свои приколы. Я не знаю, к чему ты ведёшь, как ещё можно реализовать по-другому. А может быть, просто всё из коробки. У нас, грубо говоря, на имплементация, которую мы используем, она снаружи. То есть она не как у тебя здесь внутри, она снаружи. И там просто именно у тебя, когда сущность, ну, допустим, создаётся и неважно, любое, что ты хочешь.
опять же интегрированное решение уяt там в принципе встроен из коробки там очень много функций это вообще большая довольно библиотека я бы даже её целым фреймворком назвал например у тебя обработчик сразу может быть асинхронный то есть он сам сразу всё прокладывает и сразу даёт тебе асинхронный там способ взаимодействия и при этом гарантирует целостность и всё всё остальное я просто к тому что в твоём случае надо во-первых этот аутбокс реализовывать самому. И дальше, ну, вообще, ну, не обязательно самому, можно взять готовый. Ну, я имею в виду, тебе надо явно как бы с ним взаимодействовать. Вот я скорее про что. А в нашем случае нет, потому что я могу в принципе сразу написать об асинхронный хендлер. То есть, грубо говоря, вот эта вся цепочка положил в outбокс, вызвал хендлер и так далее, происходит автоматом. То есть я пишу асинхронный хендлер, в котором я сразу отправляю письмо. Вот. Ну, так здесь то же самое будет. Здесь будет какой-то условный листенер, который он не знает, откуда к нему пришло событие. Лисенеры у тебя вызываются сразу во внутри транзакции. Это это потому что здесь супер простая реализация. Ну, условно для демонстрации. В реальных проектах там может быть на базе аутбокса сделано. А, да. Ну так, а, ну, короче, тебе, да, либо тебе всё равно придётся это реализовывать и придётся какую-то логику там вставлять с этим связанную. Ну, я просто к чему, кто у нас и из коробки вот о чём. А, а готовую. Ну, у тебя лисенеры сами здесь вызываются. Опять же, э, то есть для того, чтобы у тебя сам работал, тебе, грубо говоря, это придётся воткнуть прямо в свою систему лиснеров, либо тебе придётся вызывать его явно. Вот что я имею в виду. Ну да, ну здесь точно также фривок прикручен. То есть вот эти вот все штуки, которые я показал, до сих пор они фриворка независимы в целом. Угу. Ну, речь опять же про явность вызова. То есть у тебя, как ни крути, ты в лисенере должен прямо явно написать э-э вызов, либо тебе придётся вот кишки вот эти исправлять. Понял? Ну я может не знаю, может я уже не понимаю, про что ты говоришь. Вот. Ну я просто к тому, что, грубо говоря, в ту шину, которую ты построил, а для того, чтобы у тебя это работало из коробки и тебе не надо было явно знать про существование вот этой инфраструктуры, тебе надо вот именно ядро это менять. Если ты подразуваешь, что да, именно там происходят изменения, то да. Но если у тебя нужно, даже если ты используешь готовое решение в лисенере, самому явно его вызвать, то это всё равно с точки зрения того, что объясняю, это рукопашка. Просто, да, ты используешь готовое решение, но ты явно им управляешь. То есть ты прямо явно внутри Лысенера говоришь: "Там положи в outтбокс". Вот. А в случае автоматизированного решения я даже про его существование не знаю, грубо говоря. Это проходит на фоне полностью. Не, смотри, вот у меня есть некий паблиisшер. Вот мы событие вытащили из агрегата, положили его в паблишер и там в каких-то реализациях можно настроить, что типа вот эти вот события проходят через outбок, вот эти проходят напрямую. Всё. То есть сама вот эта вот логика, она не знает, где что должно быть. Э просто что-то прикладывается там в табличку, условно. И листенеры точно также берут, ну, к ним откуда-то прилетают события. Хер знает откуда. Вот. Ну ладно, я говорю, тут есть на самом деле ещё моменты, связанные с тем, когда это асинхронно выполняется, но я думаю, действительно, это надо прямо сейчас сидеть копать, не будем в эту нору. Ну вот, понят чисто технически, если ты можешь вот саму всю эту систему править, то да, ты можешь там навернуть, но придётся, конечно, по адаптеров понаписать, чтобы это заработало, да? То есть потому что, грубо говоря, есть ещё слой интеграции с тем инструментарием и тем фреймворком, который ты используешь. Ну да, но это делается один раз, и это, ну, я бы не сказал, что там супер сложно. Ребят, мы подошли к концу. Большое спасибо. А я ещё, кстати, пока показать, я же обещал, как это может выглядеть, да? Если если я выжил, то я бы хотел продемонстрировать чуть более сложную бизнес-логику для м создания допустим, транзакции, вот где исходов ну чуть-чуть побольше. То есть вот, например, такое количество. То есть э что здесь происходит? Здесь у нас используется, мм, по сути, это схема двойной записи бухгалтерской. Вот есть транзакция и есть есть операция списания. И в ней есть куча-куча проверок. То есть нужно проверить, что это не тот же самый аккаунт, что они активные, что валюта разрешена. Получить баланс, убедиться, что с балансом всё хорошо. Затем нужно, э, пересчитать по валютному курсу, добавить комиссию и что-то ещё сделать. И только потом у нас получается транзакция. То есть здесь уже чуть-чуть побольше логики. Вот. Ну, это может быть более удачный пример, чем просто там возвращается, грубо говоря, один исход. Вот это то, что я могу, по крайней мере, продемонстрировать для того, чтобы у вас было понимание, какой что может пойти не так. Вот. И здесь же, мм, по-моему, здесь как раз-таки реализовано таким образом, что если у нас чего-то не хватает, то мы дальше уже, ну, идти нет смысла. Поэтому возвращается тоже одна ошибка. Вот. А и всё в этом духе. Короче, вот это последнее, наверное, что я хотел показать. Ну, вообще, кстати, было бы классно, если бы ты у себя, например, на гитхабе выложил вот эту штуку и можно было в неё окунуться, поизучать, посмотреть. Ой, не знаю. Ты сказал, что всё это шляпа, поэтому зачем зачем выкладывать? написано написано красиво, но как раз вот имеет смысл обсудить и посмотреть и сравнить, кстати, потому что у меня open source мой выложен и где можно как раз эти подходы посмотреть и посравнивать по Да, будет здорово, если скинешь. Вот очень интересно изучить, что у вас за волшебные фреморки такие. У нас же всё это скатывается к тому, что ты из бизнес-логики энтити выковыриваешь, вот, и борешься с кибернейтом вместо вместо борьбы и со сложностью. самой логики. В этом плане, кстати, Хибернейт и Ормки не отличаются в разных местах сильно. То есть я склонен всё-таки считать, что чаще всего это происходит по другим причинам. А там, когда люди жалуются на запрос или ещё на что-то, я каждый раз, когда копать начинаешь, там, знаешь, выясняется, что неправильно построена работа с автоматами. Вот то, что мы с тобой сегодня смотрели, да, ещё что-то. Поэтому м я обычно на такие вещи просто говорю, ребят, ну, честно, без кода, без вот прямо конкретно кейсов, которые мы рассматриваем. Это м просто кто кого переубедит словами. Это не реальность, что по факту есть. Поэтому, к сожалению или, может быть, к счастью, я хочу просто подвести в этом плане итог. А я по-прежнему не согласен с тем, что там причина в хоббирнейте или ещё в чём-то, но мы не можем сейчас посмотреть, а поэтому я не могу как бы не опровергнуть, не, понимаешь, не подтвердить, да, мне это всегда интересно, и мне бы, конечно, хотелось глянуть, но это как-нибудь в следующий раз. В любом случае, ребят, спасибо за то, что смотрели. Я надеюсь то, что кто-то Ну, давайте так. Вы делаете выводы для себя. Мы друг друга не убеждаем, просто показали, что вот есть А, подход Б и какие-то могут быть проблемы. В свою очередь напишите, пожалуйста, что вы об этом думаете. Так ли вы это видели? Так ли вы пишите, с какими проблемами сталкивались, какой подход вы в своей жизни выбираете, а, и вызываете ли вы сервисы из сервисов и вообще, есть ли они у вас, потому что сервисы-то есть, куда же без сервисов. Ты что? А есть везде? Нет, есть не везде. Я понимаю, что в Джаве есть везде, но не во всех экосистемах восприятие мира такое. То есть, скажем так, да, есть немножко другое. Вот. Жень, тебе большое спасибо, что пришёл, дожил до конца. Да, спасибо, что обосрал.
Я, ты говоришь, что этот подход работает, я уверен, что он работает, но для себя, наверное, да, я выберу немножко другое. А, но по крайней мере мы сегодня вот хорошо в него копнули. Честно, я даже сам лучше вот увидел все взаимосвязи и решения тех проблем, которые вот мы сегодня обсуждали. Как вы их обходите? Некоторые интерес. Как обычно граблей там вагон, маленькая тележка. Вот это не вместить ни в какие 2 часа. Да. Ну, как и везде, наверное. Всё, всем спасибо, всем пока, до новых встреч. ДД. Не забывайте, мы ещё про него поговорим.
เฮ