Alex Efros @powerman
Software Architect, Team Lead, Lead Go Developer
Information
- Rating
- 2,512-th
- Location
- Харьков, Харьковская обл., Украина
- Date of birth
- Registered
- Activity
Specialization
Backend Developer, Software Architect
Lead
From 10,000 $
Designing application architecture
Golang
Linux
Docker
Network security
Modular testing
Mentoring
Development of tech specifications
Software development
High-loaded systems
Для линуха существуют тайловые оконные менеджеры.
Я последнюю версию не смотрел, но пару лет назад была та же проблема - оба компа за NAT, поднял self-hosted ретранслятор на третьем компе (без NAT), но работало всё это довольно нестабильно. Пришлось вернуться к AnyDesk (постоянно добавляя вручную
/np
к адресу при подключении).А причём тут обмен опытом? Обмен-то я применяю активно - и мастер-классы провожу, и на ревью кода/документов активно обучаю… только вот никакое обучение не сделает из условного миддла с 5 годами опыта того, на кого архитект с 35 годами опыта может скинуть с себя какие-то значимые обязательства.
Да, это всё рабочие техники, люблю-умею-практикую. Это помогает поддерживать интерес к работе. Но "могу=должен" это не решает.
Везут на том, кто тянет - а я обычно тяну. Обязательства я на себя набираю достаточно осознанно и контролируемо, но со временем (обычно - года за 2-3) они имеют тенденцию всё-равно накапливаться… как и общая усталость. И, в какой-то момент, всё-равно оказывается так, что я на себя набрал больше, чем готов продолжать тянуть (хотя ещё недавно эти обязательства были норм)… А поскольку набирал я их осознанно и обычно те, которые другие тянуть реально не смогут, то и скидывать образовавшийся "перебор" как правило не на кого (либо к этому моменту уже всё, что было реально скинуть на других - уже скинуто). Начальство в таких ситуациях обычно предпочитает кормить завтраками вместо того чтобы оперативно что-то изменить (например, начинают процесс найма дополнительных сотрудников, который может тянуться месяцами без результата по никому не известным причинам).
Погрешность, да, но такие тоже встречаются. Мифы про "10x разработчиков" и "переписать весь проект одному за выходные" не совсем мифы. Если бы таких на весь мир было всего человек 10 то мифы бы даже не возникли.
Эффективность меняется не в течение дня, в течение дня она вполне стабильна (так лично у меня, за всех не скажу). А вот между разными днями - меняется, и довольно сильно. Я поэтому и упомянул несколько раз в комменте "в среднем". Потому что в один день у меня может настроения работать вообще не быть, и я не буду себя насиловать - если формально это рабочий день то максимум буду мониторить слак чтобы отвечать (механически, не включая голову) на вопросы команды/начальника если таковые будут, но реально работу делать даже пытаться не буду. А в другой день настроение рабочее и я буду писать код часов 12 подряд забыв даже поесть. Из-за этого "выходные" у меня обычно не суббота/воскресенье, а любой день когда не работается, а когда работается - значит этот день рабочий, как бы он не назывался и что бы не показывал календарь. :) Удалёнка решает кучу проблем.
Вообще, для меня ключевые моменты это чтобы было интересно и не насиловать себя. Пока это соблюдается то эффективность работы очень высокая. К сожалению, со временем иногда вылезает "могу = должен", незаметно ведущее к насилию над собой, и справляться с этим довольно сложно.
Такое тоже есть, безусловно (и такого много, спорить не стану, но при желании действительно хороший менеджер такие совещания минимизирует до 2±1 часов в неделю). Но совещание совещанию рознь. На совещаниях, где обсуждают архитектуру или планируют какие-то стратегические шаги, ошибка в которых обойдётся очень дорого - думать приходится на полную. В идеале, конечно, думать надо заранее, во время подготовки к совещанию, а на совещании просто согласовать результаты обдумывания, но на практике так редко бывает - в основном потому, что многие к совещанию не готовятся и вместо этого начинают думать только непосредственно на самом совещании, и на их "гениальные, но не очень продуманные из-за недостатка времени" идеи нужно оперативно и адекватно отвечать.
Вы по-прежнему излишне категоричны. Лично у меня из стимуляторов только кофе, и то, более чем умеренно - в упомянутые периоды такой работы пил чашки по две в день. Мне легче 8 часов работать головой чем 1 час делать что-то механически (и не обязательно физически тяжёлое, около 30 лет назад была у меня подработка тупо "карточки в комп вбивать" - меня на ней реально укачивало и тошнило вполне физически).
Во-первых, люди всё-таки разные. Во-вторых, мозг тоже поддаётся тренировкам. Поэтому я соглашусь с "4 часа в среднем", но не соглашусь с "никто не в состоянии".
Лично я вполне в состоянии продуктивно работать головой намного дольше - вплоть до "10 часов в день в среднем без выходных на протяжении месяца-полутора". Потом, конечно, приходится давать себе отдохнуть не менее серьёзно (на уровне "месяц не подходить к компу вообще"). А в обычном режиме с выходными я спокойно часов 7-8 в день в среднем продуктивно (речь об активном написании кода или работе над архитектурой, а не просиживании на совещаниях) нарабатываю. От подчинённых я, разумеется, такого режима работы не требую, потому что понимаю, что мои возможности далеки от средней нормы. (И да, чтобы так работать обязательно нужно сделать так, чтобы работа была искренне интересна, без этого мне из себя и 4 продуктивных часа выжать крайне тяжело.)
Ключевая проблема саг - неизбежные баги в логике компенсаций. Если даже их не будет в первой версии то они появятся по мере внесения изменений в бизнес-логику. Люди просто не справляются с тем, чтобы учитывать все возможные нюансы (всё, что могло измениться во всей системе между действием и его компенсацией, и как это повлияло на то, как именно нужно корректно компенсировать) и корректно поддерживать (просчитать какие изменения в логике компенсации других шагов может потребовать изменение бизнес-логики в ином месте проекта) такую сложную логику.
Это примерно как с ручным управлением памятью: в теории ничего сложного сделать malloc а потом free нет, но на практике писать такой код без ошибок не получается почти ни у кого. И если в случае ручного управления памятью заметить такие ошибки ещё как-то возможно (по утечкам или крешам при повторном освобождении), то в случае саг даже заметить баг в логике компенсаций крайне сложно, что создаёт иллюзию "простоты" саг и способствует широкому внедрению саг там, где без них можно было бы обойтись.
Ну, оно там изначально появилось из соображений "если будет нужно, то из этого монолита мы за час любой модуль выносим в отдельный микросервис". Но в целом, да, для настоящего монолита это определённо перебор.
Плюс/минус. На практике в моих реальных проектах всё-равно требовались микросервисы (из соображения распределения нагрузки между серверами), так что в конечном итоге всё свелось к тому, что код всех микросервисов на Go находится в одном репо, и хотя формально/технически возможно скомпилировать их в одном монолите но практически нужды в этом обычно не возникает. Несколько устаревший пример такого монолита с несколькими микросервисами внутри можно увидеть в https://github.com/powerman/go-monolith-example. Можно ли (и нужно ли) в нём что-то изменить/упростить с целью получить обычный монолит (например реализовать вызовы между модулями через обычный вызов функции вместо полноценного сетевого API) пока неясно.
Не уверен, что понял где тут ценность. Начиная с того, что выявленный после мержа фичи баг может находится в месте, которое в процессе работы над этой веткой многократно менялось и рефакторилось (т.е. ценность всей истории изменения даже конкретно той точки где в финале оказался баг очень сомнительна) и заканчивая тем, что даже это может являться ценностью (пусть даже и сомнительной) исключительно для автора этой ветки (т.е. того, кому доступен reflog).
Ну, на первый взгляд данный пример напоминает скорее обычный монолит. И для монолитов такое разбухание и усложнение со временем абсолютно естественный процесс, который, разумеется, создаёт сложности. Но эта проблема возникает вовсе не из-за "жирных" интерфейсов, а из-за природы монолита.
А вот когда User, Order и Basket - это три разных микросервиса (или полноценно изолированных модуля в монолите), то данные "жирные" интерфейсы в этих микросервисах/модулях смотрятся очень хорошо и наглядно. А объединяющего их и бесконечно разбухающего OrderService обычно вообще нет - его функционал "размазывается" частично по микросервисной архитектуре (для работы Order обычно из всего domain.User достаточно поля ID, где-то можно обойтись eventual consistency и отправкой событий, etc.), частично по отдельным микросервисам, иногда даже частично по клиенту, а в исключительных случаях ещё и в сагу.
За этим следит компилятор Go, а не IDE.
Это скорее проблема, нежели повод для радости.
Это что вообще?
В целом, любых вариантов распределённых транзакций стоит избегать настолько, насколько это вообще возможно в принципе. В классическом монолите с этим проще, но уже в модульном монолите каждый модуль это аналог микросервиса, и как и у микросервисов у этих модулей должен быть собственный кусок БД (или полностью своя БД), и транзакции не должны пересекать границы этого куска БД (и границы модуля в коде). Если транзакция должна пересекать границы микросервиса/модуля монолита, то в большинстве случаев это индикатор ошибки проектирования границ ответственности (bounded context) этого микросервиса/модуля и нужно перепроектировать эти границы а не вводить транзакции пересекающие эти границы. Если перепроектировать в каком-то случае невозможно при всём желании, то да, приходится начинать использовать либо распределённые транзакции либо саги - но эта тема совершенно за рамками данной статьи/слоёной архитектуры. А попытка использовать распределённые транзакции/саги для затыкания ошибок проектирования создаст на порядок больше проблем, чем решит - потому что реализовывать и поддерживать корректную логику отката таких транзакций дико сложно и люди эту задачу просто не вытягивают (примерно как они не вытягивают задачу ручного управления памятью).
Вообще именно в "классическом DDD" именно классических ;-) геттеров и сеттеров быть не должно. Все эти методы должны быть бизнес-операциями. Внутри они могут быть похожи на геттеры/сеттеры, но называться должны не GetField/SetField а в соответствии с выполняемыми бизнес-задачами. А для предоставления доступа к полям внешнему миру (API/БД) достаточно одного-двух методов преобразующих модель в DTO. Понятно, что в некоторых случаях добавить геттер а-ля
ID()
может быть проще и удобнее альтернатив, но именно прям классической толпы геттеров/сеттеров на большинство полей модели в DDD быть не должно.Если под "разными usecase" подразумеваются разные методы в слое usecase одного микросервиса - то нет. А если разные usecase разных микросервисов у которых общая часть вынесена в единый для них всех слой domain (т.е. по сути в отдельную библиотеку общую для разных микросервисов) - то да. Но в последнем случае это уже не совсем настоящий (книжный) слой domain, это просто общая либа с бизнес-логикой которая обязана быть общей для всего проекта. Я к тому, что в эту либу попадает всякое разное важное для бизнеса, но это не обязательно только модели домена и не обязательно все модели относящиеся к домену.
Я много проектов в таком стиле написал, ни разу никаких проблем такие жирные интерфейсы в пакетах бизнес-логики и репо не создали. Можете уточнить, о каких потенциальных проблемах речь?
Что до SRP, то нет, он не нарушается. Дядя Боб под SRP имел в виду единый источник требований к конкретному коду - а-ля чтобы функциональные требования от разных "заказчиков" (напр. отдела бухгалтерии и отдела юристов) не реализовывались в одном классе/функции.
Ну, обычно видя названия методов в жирных интерфейсах бизнес-логики и репо обычно легко сообразить, какие методы репо используются из каких методов бизнес-логики (там нередко 1-к-1)… но острой потребности точно знать какие методы репо вызывает конкретный метод бизнес-логики у меня обычно не возникало. Мне, как архитекту, намного полезнее увидеть общую картину всех методов рядом, что позволяет легко оценить полноту/избыточность решения.
Там где агрегат требуется для правил бизнес-логики - там пусть будет агрегат. Но в контексте интерфейса репо и транзакций - не каждая транзакция подразумевает наличие агрегата на уровне бизнес-логики (по крайней мере пока мы не используем DDD, но у Вас в статье речь о DDD не идёт, да и не нужен DDD большинству проектов).
И всё же описанный в статье подход однозначно избыточно книжный/формальный. На практике можно и нужно срезать некоторые углы, которые хоть формально и нарушают книжные рекомендации, но при этом не создают проблем в реальных проектах и при этом заметно упрощают работу. Например:
Слои usecase и domain в большинстве проектов нет смысла разделять. Тестирование логики usecase на моках domain (и вообще отдельно от логики domain) не даёт никаких плюшек, это просто излишнее усложнение.
Учитывая автоматическую генерацию моков зачастую удобнее сделать один "жирный" интерфейс с десятком-двумя методов чем делать два десятка интерфейсов по одному методу. Это даёт возможность сделать один интерфейс для всего слоя usecase+domain и ещё один для всего слоя repository. Такие интерфейсы сильно повышают наглядность при чтении и анализе кода - сразу виден полный список всех операций как с БД так и с бизнес-логикой.
Транзакции действительно должны быть полностью внутри конкретного метода репозитория, но при этом нет острой необходимости обязательно сохранять связь "один метод репо - одна модель". Т.е. в примере из статьи не обязательно создавать модель-агрегат каждый раз когда нужно передать несколько моделей в один метод репо из-за транзакций - можно в этот метод репо передать нужные модели разными параметрами.
Что до передачи функции бизнес-логики параметром в метод репозитория - это должно быть не базовой рекомендацией, а редчайшим исключением. Потому что эта функция бизнес-логики вполне может начать делать вызовы в другие сервисы и даже обратно в репозиторий, что, очевидно, создаст кучу проблем. И даже если сейчас она ничего такого не делает, то в неё такой функционал вполне может быть добавлен в будущем. И чтобы это предотвратить такие функции нужно как-то явно отмечать, что они не являются обычными методами бизнес-логики и в них нельзя делать то, что в других методах бизнес-логики делать можно.
Вместо использования такого подхода лучше использовать специализированные методы репо. Например, если нужно в транзакции увеличить баланс юзера, то вместо того, чтобы метод репо "начал транзакцию, достал юзера из БД в модель, вызвал метод бизнес-логики модифицирующий модель юзера, сохранил модель юзера в БД, завершил транзакцию" написать метод репо `UpdateBalance(userID, amount)`, который сделает один SQL-запрос "UPDATE" в БД который изменит одно поле в БД. Да, в некоторых ситуациях логика внесения изменений зависит от текущих данных, что приводит к формальному "протеканию" бизнес-логики в такие методы репо. Иногда это можно "компенсировать" указав все нюансы бизнес-логики в названии и/или аргументах метода репо, чтобы сохранить полный контроль над этой частью бизнес-логики снаружи. Например, если изменять баланс можно только для активных юзеров, и эта проверка статуса юзера "протекла" в метод репо, то его достаточно переименовать в `UpdateActiveUserBalance(userID, amount)` либо добавить параметр `UpdateBalance(userID, isActive, amount)`.
А у них был доступ к API по HTTP?! В 2025? Мда…
Хабр просто работает на движке HOMM где-то очень глубоко под капотом. Так что как астрологи объявляют неделю чего-то там, так сразу и вылезает десяток статей по этой теме.