Обновить
17

Пользователь

0,3
Рейтинг
7
Подписчики
Отправить сообщение

К сожалению, такие слова про простоту проекта без репозиторием, сервисов, оберток и т.д. остаются голословными утверждениями. Многие говорят, что это всё не нужно, но ни разу не видел крупный (практически production-ready) проект, написанный таким образом и который бы можно было ставить в пример вариантам с усложнениями и говорить: "Вот пример простого и крупного проекта".

Другими словами: "докажите что не нужно". Но ведь Ваше же утверждение можно применить и в обратную сторону - "докажите что нужно".

Многие говорят, что это всё нужно, но ни разу не видел крупный (практически production-ready) проект, написанный таким образом и который бы:

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

  • не имел протекающие абстракции на каждом шагу

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

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

Найти и предоставить хороший простой проект с простой архитектурой не проще чем найти такой же хороший со сложной.

Производительность часто приносится в жертву академической чистоте.

С точки зрения строгой архитектурной чистоты здесь возникает асимметрия: для записи у нас остаются доменные сущности, сервисы, репозитории и транзакции, а для чтения появляется отдельный optimized read‑path. Это уже не выглядит так «красиво», как каноническая layered‑схема, но в production такая асимметрия часто оказывается оправданной.

Огромное заблуждение считать что есть какая-то архитектурная чистота и каноничность. Все эти чистые архитектуры и DDD это не более чем завирусившиеся авторские методики отдельных скучающих писателей. Будучи объективным нужно сказать что нет никаких хотя бы околонаучных доказательств что вся эта многослойность, вся эта эмуляция реального мира, вся эта чисто-архитектурность дает пользы больше чем вреда. Да по папочкам разложено, да уложено приложено, все реализации поменять можно, вот только там где в нормальном проекте для доработки хватить пары строк, в книжных архитектурах все будет когнитивно переусложнено, раскидано по десяткам классов и будут уходить часы, дни и недели на обсуждения архитектурной частоты согласно книжке и будет написаны тысячи строк кода которые решают проблемы которая такая архитектура и создала и не имеют никакой практической ценности для практической задачи.

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

Вообще, это забавно и печально одновременно - как в одно идиотское политическое решение cделать .Net только под Windows, до сих пор не дает, технически куда более зрелому и совершенному .Net(C#) обойти Java(JVM) исключительно по политическим причинам. Один раз упустили, а потом все по привычке будут делать все только на Java.

Второй забавный момент - изначально именно Java/JVM делалась как инструмент под все платформы-задачи а сейчас все наоборот, C#/CLR - хороший инструмент для решения практически любой задачи, в то время как Java/JVM в подавляющей доле это инструмент написания корпоративного backend и практически ничего более.

Хвост начинает вилять собакой

Просто иронизирую на тему компенсации профессиональных качеств красивыми названиями

У меня в дипломе написано "инженер-программист", когда я его получал, думал "нафиг там инженер, яж программист?". Теперь понял

Кто бы тогда мог подумать что сейчас это будет +1 к цветовой дифференциации штанов

Зачем называть вещи своими именами если можно заменить слово "программист" на "инженер" и все вдруг станет загадочнее, элитарнее, понтовее и круче. И пофиг что чем дальше тем средний программист тупее в техническом плане с каждым годом, главное что теперь он гордо зовется "инженер" и занимается они не никчемным программированием а целой инженерией!

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

Логика в сущности - это бесполезная полу-мера. Никакого толку от метода is_signed в сущности нет, потому что по итогу она мне не отвечает на вопрос - подписано ли что-то или нет, мне нужно еще искать другую половинку логики. Проще все концентрировать в одном месте где таких ограничений нет.

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

Сервисный слой(use cases) в любом случае остаётся, вопрос тут скорее не "сервис или нет", а в том, где живут бизнес операции, относящиеся к конкретной сущности.Если такая логика находится в сервисах, то при росте системы мы получаем дублирование правил или вынуждены выносить их в хелперы, что увеличивает связность.

Не получаем. Все бизнес-операции сущности живут в сервисе сущности и больше нигде.

Если логика проверки "уже подписывал или нет" находится в нескольких сервисах, нам придётся менять её во всех этих местах.Если же локальные бизнес-операции сущности инкапсулированы внутри сущности (например, invoice.add_signer() или invoice.is_signed_by(user)), то изменение set -> list затрагивает только саму сущность, а внешний код остаётся стабильным

Как я уже написал - логика не будет в нескольких сервисах.

В Вашем примере данная логика работает ровно до той поры пока там нет работы ввода вывода. Если логика проверки подписи требует сходить в другую систему, или хотя бы в БД, то метод сущности превращается в тыкву и все нужно переделывать и решать вопрос с перед-использованием. Вариант с управляющим сервисом такой проблемы лишен.

Domain model в моём примере не пытается инкапсулировать вообще весь бизнес процесс системы.

Не понял почему "не самодостаточна даже для соблюдения своих собственных инвариантов", можете подробнее раскрыть, пожалуйста.

Ну смотрите, давайте от обратного - зачем нам вообще какие-либо бизнес -правила описывать в модели? Вполне разумные преимущества:
1.концентрируем логику в одном месте - из какого бы участка кода мы не вызвали сущность, нельзя привести ее в неправильное состояние
2.вместо изменения отдельных полей мы делаем осмысленные операции с валидацией состояний и прочим
Звучит здорово, но ломается в то что это полумера. Сущность неполноценна. Ну допустим, мы зашили в ней правила касаемо ее полей. А какой в этом толк?
Вот есть операция - подписать. В ней мы меняем поля signature, signer, signed_at, предварительно проверил состояние подписываемого объекта. красиво. А потом в проверку приходиться вставлять запрос и толк от этой схемы стремиться к нулю, потому что из любого места в коде можно сделать подпись и сохранить а внешние проверки застряли в юзкейсе. При этом мы разнесли логику по разным классам, ходи потом бегай и собирай по кускам как делаться подпись. Решительных преимуществ не получили а вот недостатки в полной мере.

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

Что мы хотим?

Мы хотим, чтобы собеседование было диалогом про инженерное мышление и опыт.

Что мы для этого делаем?

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

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

Немного покритикую что хорошо а что нет.

Доменная модель описывает бизнес-сущность и её правила.

Обратите внимание, домен ничего не знает про инфраструктуру, здесь нет ORM, SQL, pydantic, boto3, etc.

Раз, и между двумя предложениями рождается противотечение. Это фундаментальная проблема ООП подхода на бэке. Мы не можем оперировать всей доменной логикой без инфраструктуры. Как только появиться необходимость валидировать на существование других данных в БД или что еще хуже, менять их - этот подход сразу превратиться в тыкву.

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

Дальше:

# usecases/invoice/upload.py
class UploadInvoiceUseCase:
    def __init__(self, storage: IFileStorage, uow: IUoW):
        self.storage = storage
        self.uow = uow

    def execute(
        self,
        invoice_id: UUID,
        content: bytes,
    ) -> UploadInvoiceOutput:
        invoice = self.uow.invoice_gate.get_by_id(invoice_id)

        if invoice.is_cancelled:
            raise InvoiceCancelledError()

        key = f"invoices/{invoice_id}.pdf"

        self.storage.save(key, content)

        invoice.mark_uploaded()

        self.uow.invoice_gate.save(invoice)

        return UploadInvoiceOutput(
            invoice_id=invoice.id,
            uploaded=True,
        )

Окей. Зачем нам этот класс? Вы по сути написали очень странную и неуклюжую функцию. Просто передайте storage и uow в параметры и выкинете никому не нужный класс.

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

В-третих, что будете делать, когда появиться общая логика между usecases? Дублировать? Или выносить в еще одну костыль-абстракцию?

Сервис намного проще как решение. Ну или просто оставить это функциями.

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

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

Асинхронность

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

Мне кажеться Вы сами запутались.

Асинхронность - это свойство частей исполняемого кода работать не последовательно. Например, у нас есть задача A, для ее вычисления нам нужно еще вычислить задачи B и C. Если мы вычисляем их последовательно: A1->B->C->A2 то получаем синхронно выполненный набор задач. Если же мы ломаем последовательность вычислений, продолжая вычислять работу которая должна быть после B и C еще не имея результатов тех самых B и C , то получаем непоследовательно, ну или не синхронно ну или асинхронно выполненную последовательность.

Причем здесь IO bound задачи в определении асинхронности? Я с тем же успехом могу асинхронно считать алгоритмы в разных потоках выполняя работу параллельно или не параллельно в одном, и так и так работа будет асинхронной.

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

На самом деле проблема намного глубже и шире. Сводиться она к трем состовляющим: лени, жадности и тупости.

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

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

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

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

Через две недели и тысячи коммитов, ИИ радостно сообщит что у него лапки и нужен кожаный. Кожаный констатирует что за две недели было выполнено 100500 задач каждая их которых крутилась в цикле сансары и все ее коммиты перемерджены с тысячами коммитов других чудо задач. Оператор управления ИИ приходит к выводу что проще проект сжечь чем что-то откатывать или чинить.

Предлагаю на такие случаи выводить на экран довольную морду Бориса Черни.

Т.е. причина - она не техническая, но, скажем так, идеологическая. Это то, что я хотел сказать.

Я и не писал что они не могу это сделать технически я писал что не могут - фактически.

 про специализацию там и нет ничего

Parametric JVM, specializing and optimizing generic class and method parameterizations at run time

Сама по себе Вальхалла - по большому счету - это как раз попытка поправить “костылики” в системе типов, которые “наросли” в Java за всё это время.

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

Другой момент (в этом плане) - у нас есть перед глазами пример, например, dot net’а… где всё “это” уже “пробежали” и - внезапно - выяснилось, что “иметь нормальные, не тормозные коллекции и другие контейнеры” - это "хорошо, но мало"© :-)

Сто лет в обед у них там все хорошо сделано изначально и работает. Ничего не понял что там внезапно выяснилось?

Любопытно. Но, не могли бы вы таки “поискать”?

Если попадется - обязательно скину.

?! Вы, имхо, не понимаете о чём говорите. Никакого прямого влияния на асинхронную обработку это не имеет.

История от колбэков NodeJs, горутин Go, корутин C# до виртуальных потоков Java это история - как удобно управлять асинхронностью.
Асинхронная операция - это операция которая может быть прервана и выполнена позднее. В контексте Web это в основном сетевые запросы. Смысл виртуальных потоков - сделать эти операции неблокирующими из коробки вот и все. Какой то там философской дикой разницы нет, немного разный инструмент. Что корутины что горутины это удобный инструмент управления асинхронными задачами, не важно параллельно мы выполняем задачу на нашем процессоре или на другом сервере. Виртуальные потоки это просто оптимизация этого процесса. В старой Java для этого использовался отдельный поток и это было не эффективно а в новой доросли до возможностей не блокировать ОС поток. По сути очень близко к тому что в Go но менее удобно.

Достаточно того, что в “спецификацию Java” входит аnnotation processing (aka JSR-269) как таковой. Что, имхо, уже делает ваше утверждение насчет “что написано в коде плюс минус будет в байткоде” - немножечко некорректным.

Возможность не равно данность. Если я открываю голую Java то написанный код близок к байткоду если я открываю голый Kotlin, без всякого аnnotation processing и фреймворков это с порога не так.

Ну дык я про это и говорю. Кто-то не стал заморачиваться публикацией source jar, а стандартный javap для Kotlin и будет выдавать то самое “месиво”.

А мне как пользователю Kotlin какое дело? В Java я с таким не сталкиваюсь а в Kotlin сталкиваюсь - это единственное что важно.

Ну вот у F# с C# ровно такая же “проблема”, как у Kotlin с Java. Так понятней?

Какое мне дело до F#? Я пишу про Kotlin и Java. Если будем говорить про F# и C# то буду критиковать F#.

Вы - судя по всему - не понимаете как связан конкретный Kotlin с конкретным JDK. Эта связь настолько эфемерна, что можно утверждать, что её и нет никакой.

Т.е. никто вам не запретит писать на старом Kotlin под последний JDK.

И - ровно точно так же - никто не запретит использовать последний Kotlin (со всеми его фичами) на древней JDK, типа 1.8

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

Большинство пользователей заинтересовано работать на последней версии JVM и разработчики Kotlin вынуждены с этим считаться. Разработчики вынуждены считаться с всеми фичами новых JVM для того чтобы Kotlin работал оптимально. Можно сколько угодно рассказывать про теоретическую слабую связь с Java экосистемой но на практике эта связь сильная и вынуждает Kotlin следовать в этом мейнстриме как бы Вам не хотелось убедить себя в обратном.

И гораздо лучшей системой типов. Про это, почему-то всё время забывают.

Не нужно перехваливать. С Гораздо лучшей: Haskell, Scala, TypeScript, Rust. А Kotlin с чуть лучшей.

Вообще-то может. И более того, на старте это даже, имнип, рассматривалось. Но оно действительно ломает совместимость со стороны Java кода

Звучит как "Так то я тебе наваляю но просто не хочу". Если не сделали, значит в силу разных обстоятельств не могут. Никого не волнует что бы они в теории могли или не могли, волнует только то что есть и чего нет в языке.

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

Львиная доля потребности в специализации - это иметь нормальные, не тормозные коллекции и другие контейнеры. Именно этого все хотят и именно поэтому в Java их делают уже 12 лет. Если бы reified type parameters закрывали львиную долю потребностей в Java бы их ввели давно но на деле как раз менее важные потребности.

?! Если вы про ML-подобный (с deep deconstruction и т.п.) - так от него тоже отказались осознано, а не потому, что “невозможно”. На практике, PM в Kotlin (в гораздо более продвинутой форме) был (и есть) с самых первых версий. Он постоянно развивается, и - насколько я в курсе - даже в самой своей продвинутой форме (на данный момент) не требует никаких фич от JVM… т.е. вполне себе компилируется в “java 8”.

Был хороший доклад от Андрея Бреслав, лень искать, где он ответил на вопрос почему не сделали pattern matching. Ответ был такой: это очень муторная и сложная фича пользы от которой не так много, тем более в Java уже делают, мы дождемся пока они сделают, проанализируем и может быть сделаем.

А можно чуть подробнее про “приседания и костыли”? Реально интересно, чего вы там “такого” находили, что оно было нужно.

Spring и Hibernate требуют пустого публичного конструктора,что несовместимо с Kotlin без костылей в виде плагинов.
Mockito из коробки нормально работать не будет.
Jackson / Gson из коробки тоже нормально не работают.
Могу ошибаться, но вроде бы с MyBatis были какие то проблемы в свое время.
Таких либ достаточно которые без оберток-костылей не будет работать.

Вы зря сравниваете корутиты Kotlin и виртуальные потоки Java. Они - сильно “про разное”…

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

Даже в Kotlin JVM есть вещи, которые, скажем так, не дружат с “миром JVM”. Но они там, есть, т.к. с точки зрения развития языка “это нам нада”(с).

Компилятор Kotlin генерирует байткод который не поддерживается JVM но она его по старой дружбе всеравно выполняет?

О да. Особенно - на практике :-) Вы не используете annotation processing? Ну там Spring и/или Lombok? Реально? А что вы такое пишете на Java?

Spring и Lombok входят в спецификацию Java?

?! А можно по подробнее, об чём речь? Емнип, правила аллокации в Kotlin полностью аналогичны таким же в Java. Зачем в бейткод-то смотреть?

Вот я напишу Вам Int а вы скажите, это примитивный тип или ссылочный?

Тут вообще не понятно “про что”. Если вы про то, что “стандартные” java decompiler’ы сильно плохо дружат с Kotlin - то есть такое. Но есть же специализированные инструменты для. 

Я захожу в исходники через инструмент который написала компания который этот язык и сделала и часто вижу или месиво нечитаемое или вообще отсутствие реализации как будто бы я в C. Можно ли запариваться, использовать специальные инструменты? Можно. Но вот почему то в Java у меня таких проблем нет и в C# тоже а в Kotlin есть.

:-) Рискую открыть вам “страшную тайну”, но даже kotlin 2.3 (самая новая, на данный момент версия языка) компилируется в JDK 1.8 без особых ограничений по, собственно, фичам Kotlin’а. Ну да… какие-то платформозависимые (типа @JvmRecords) аннотации становятся не доступны - но и только.

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

Это я к тому, что самому Kotlin JVM от самой JVM “много не нужно”.

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

Да, все так. Чудовищной ошибкой Microsoft было делать C# только под Windows. Жадность покоя не давала. Только в 2016 одумались и сделали кроссплатформу. Хоть доля C# на рынке большая до Java естественно не дотягивает. Но как язык и платформа C#/.net это прям пример как нужно делать.

ps. Занимательный забавный факт.

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

В теории то оно понятно, там и Scala в JS умеет. На практике в реальной жизни это либо андроид либо backend т.е. один к одному с джавой.

Ну и в целом на Kotlin писать гораздо приятнее и проще чем на Java. Говорю как человек, который 18 лет писал на Java и 5 лет на Kotlin.

Тогда лучше не пробовать C#. Можно словить разочарование от осознания - насколько на самом деле все нормально и удобно можно было сделать.

1
23 ...

Информация

В рейтинге
2 984-й
Откуда
Санкт-Петербург и область, Россия
Зарегистрирован
Активность

Специализация

Бэкенд разработчик
Ведущий
C#
Java
Rust
Golang
Многопоточность
C
Системное программирование
Разработка игр
Unity3d
Алгоритмы и структуры данных