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

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

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

Поправьте конечно если я не прав, я не эксперт в Go, но из своего опыта я не понимаю зачем идти поперек языка? Вот к примеру зачем на каждый чих городить столько файлов? Ну в Java то для этого причина есть, там в файле не более одного публичного класса может быть. В Go нет никаких классов, как и в Rust философия модульно-пакетная, где концентрация логики идет по пакетам а не по отдельным файликам. Да и в целом язык очень ограниченный и призван брать простой и нужно использовать сильные стороны инструмента. А с таким подходом это будет пародия на Java так как в Go даже нет инструментов поддержки такой навороченности.

У меня основной опыт тоже в Java/Kotlin, и сейчас пишу немного на Go. Я наоборот пришел к тому, что пакетная видимость очень удобна для дробления файлов.

Когда я вижу пакет, я мысленно рассматриваю его как один "обобщенный" файл. Т.е. я могу хоть по функции в файл дробить, но за счет того, что они в одном пакете - все "приватные" методы доступны. Если же мне надо что-то инкапсулировать (а ля создать новый Class), я запихиваю это в отдельный пакет.

Т.е. условно получается, что один class-file из java становится пакетом+файлом(пакетом+файлами) в Go...

Сейчас подумываю о создании базовой структуры с методом toString, hash, equals (joke)

Неправильно. Хватит демонизировать джаву. Го не создавали, чтобы он был НЕ КАК джава. Го скорее создавали для других типов задач, вроде инфраструктуры, где он себя кстати отлично чувствует (докер, кубер).

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

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

А на файлы разделять за тем же, за чем и просто что-то большое разбивать на что-то мелкое - декомпозиция сложности. Все паттерны, все солиды, все эти DRY, KISS, GRASP и т.д. про совладание со сложностью.

Неправильно. Хватит демонизировать джаву. Го не создавали, чтобы он был НЕ КАК джава. Го скорее создавали для других типов задач, вроде инфраструктуры, где он себя кстати отлично чувствует (докер, кубер).

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

Но на го начали писать энтерпрайз приложения, а они обладают большой сложностю, которая продиктованна сложностью предметной области.

Большей чем чем Кубер в котором больше 2 миллионов строк кода?

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

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

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

Мир не крутиться вокруг Java и ее проблем а также вокруг проблем ООП языков. Не нужно если у Вас в руках молоток видеть все вокруг как гвозди. В мире есть не только ООП парадигма и проекты пишут в разных стилях и как то борются с этой вашей сложностью.

А на файлы разделять за тем же, за чем и просто что-то большое разбивать на что-то мелкое - декомпозиция сложности.

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

Все паттерны, все солиды, все эти DRY, KISS, GRASP и т.д. про совладание со сложностью.

И еще раз, совладали то со сложностью по итогу? Например с той сложностью что SOLID уже несколько десятилетий пытаются объяснить в сотнях статей. Или ООП которым как выясняется за несколько десятков лет так никто толком пользоваться и не научился. Хороший же результат, что может пойти не так если такой подход внедрить туда где от этого решили отказаться и пойти другим путем?

К сожалению, вы понимаете это неверно. Фраза «не как Java» не означает, что любое сходство с Java кодом автоматически является злом или дурным тоном. Она указывает на ряд фундаментальных различий, заложенных в сам язык, — например, в обработке ошибок или модели конкурентности.

Однако архитектура - это не про Java. Многие архитектурные принципы вообще не зависят от конкретного языка программирования. Речь идёт о системности, аккуратности и, если угодно, чистом коде.

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

И самое главное (постскриптум для начинающих разработчиков) - не стоит использовать фразу «Go не Java» как оправдание отсутствия архитектуры и незнания принципов проектирования.

К сожалению, вы понимаете это неверно. Фраза «не как Java» не означает, что любое сходство с Java кодом автоматически является злом или дурным тоном. Она указывает на ряд фундаментальных различий, заложенных в сам язык, — например, в обработке ошибок или модели конкурентности.

Позвольте немного разверну ответ.
Фраза «не как Java» действительно не означает что Java это очень плохо и не нужно таким быть.
Вопрос в природе решений и их сильных и слабых сторонах. Go - это типичное асимметричное решение. Вместо того чтобы делать очередной более навороченный язык авторы принесли в жертву функциональность в обмен на минималистичность. Получился довольно топорный и не всегда удобный язык но который за счет этого был дико минималистичным а это значит что его очень быстро можно выучить и сильно ограниченным что значит что разработчики сильно ограничены в фантазиях по использованию языка. Из этого сложились сильные и слабые стороны языка: силен там где можно сделать просто и понятно и слаб там где к примеру нужно реализовывать DSL подобный код или строить сложные абстракции. И логично предположить что нужно развивать проекты в направлении сильных сторон языка.
Более того, это язык с другой философией и структурой. Если Java из времен где модно было ООП и не существует ничего вне классов, а классы связаны с файлами то Go это другая история скорее ближе к помеси C с современными языками, в нем нет никаких классов и построения логики вокруг них, есть пакеты, есть обычные функции и обычные структуры которые можно и нужно использовать не как ООП.
Когда проект пишется на Go в стиле Java то теряются многие сильные стороны языка потому как он для этого не предназначен и теряются сильные стороны, получается недо-Java 2.0 на минималках. По мне так это не очень хорошая история.

Захотелось привести хороший пример из истории. На заре Первой мировой войны, английский лорд Джон Фишер придумал идею линейных крейсеров т.е. кораблей с орудиями от линкоров но обладающих огромной скоростью, разумеется для этого пришлось пожертвовать броней. Как говаривал лорд - "скорость — лучшая броня ". Разумеется никто не подразумевал участие таких кораблей в линейном бою с линкорами противника. Это было типичное асимметричное решение - разменять броню на скорость и использовать согласно данным преимуществам. Но вот незадача, типичному адмиралу может прийти в голову мысль - "как можно не использовать корабли с такими большими пушками в линейном бою". Через определенное время так и случилось, в Ютландском сражении где адмиралы таки бросили линейные крейсера в бой и те начала гореть как спички и тонуть, Дэвидом Битти была сказана знаменитая фраза " There seems to be something wrong with our bloody ships today."

«Нельзя добавить более десяти пунктов в список дел» — небольшой вариант оптимизации

make([]TodoListItem, 0, 10)

Правда, с подобной структурой проекта это «оптимизация» ни на что не повлияет, так, просто «хороший тон».

оптимизацией чего? Если будет 10 млн списков дел и в каждом спике по 1-2 делу, а всего в нескольких по 10, то это не оптимизация.

Без паттерна использования и измерений не надо ничего оптимизировать, а то как раз "преждевременная" и получится

На Хабре в последнее время каждую неделю по DDD статье. Это отлично, но что за хайп я пропустил?

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

Большая статья, но если коротко, почему вы вначале рисуете зависимости снаружи во внутрь? Почему домену нельзя пользоваться своей же оберткой? Для чего вообще тогда слои? CQS, на сколько я понимаю относится к методам класса, а не архитектуре приложения, и вы, по всей видимости, путаете его с паттерном ООП команда. И почему у людей в гексогональной архитектуре порты только в 1 месте всегда? А слои как между собой общаются?

Спасибо за статью. Многие решения откликаются во мне.

Предлагаю обсудить интерфейсы.
Почему интерфейсы репозиториев в домене?
Их необходимость ещё стоит обосновать (https://enterprisecraftsmanship.com/posts/ocp-vs-yagni/)
Но если они все-таки нужны... Мне кажется, что подход, при котором интерфейсы находятся в слое домена, взят из Джавы и прочих ООП языков, в которых в реализации явно указывается, что она реализовывает интерфейс.
В го типизация утиная и такой необходимости нет. Так зачем размазывать знание о хранении модели в слое модели?

Я предпочитаю определять интерфейсы по месту использования. В данном случае это слой приложения.

Я обратил внимание, что у вас в доменных службах используется 2 репозитория.

Я пришел к выводу, что нужно быть аккуратным в этом вопросе, особенно в случаях, когда в рамках сервиса происходит изменение нескольких агрегатов. Если вы допускаете такое в своем домене, то такого рода изменение обязательно произойдёт (закон Мерфи).

Первая причина - это ограничивает масштабируемость приложения, происходит завязывание этих модулей друг на друга и разбить их например на микросервисы станет невозможным без изменения домена. Для вашего выдуманного сервиса это может быть актуально, т.к. using очевидно гораздо более demanding контекст, чем editing.

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

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

Для композиции query из editing и вызова command из using, я использую слой оркестрации. Это application слой, который манипулирует доступными commands & queries. Оркестраторов может быть несколько, в зависимости от сложности бизнеса (кор бизнес, аналитика, отчёты, биллинг). Оркестратор управляет машиной состояний заложенной в некий процесс. Примером оркестратора может служить AWS Step Functions, или Mass Transit. Таким образом и слой домена и слой оркестрации содержат бизнес логику. Слой оркестрации содержит логику оператора, это собственно и есть то, что мы пытаемся автоматизировать. Слой домена предлагает кирпичики и ручки, с которыми работает оператор. Другая метафора оркестратора это блоки управления в авто. Есть агрегаты - ДВС, АКПП. БУ делает условный query в ДВС, далее БУ рассчитывает следующую передачу и на основе этого отправляет command в АКПП на переключение передачи. Точно так же есть отдельный БУ для системы мультимедиа и тд.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации