All streams
Search
Write a publication
Pull to refresh
63
0.5
Михаил @michael_v89

Программист

Send message

Тут нет высказывания о реальных явлениях. Если считать высказыванием "Во Вселенной есть такой чертеж", то да, вероятность его в 100% можно получить только наблюдением такого чертежа (то есть экспериментом).
Но в данном случае его вероятность 0%, так как выражение "круглого квадрата" создает логическое противоречие, и это высказывание не имеет смысла. Оно нефальсифицируемое, его нельзя подтвердить или опровергнуть, потому что непонятно, что имеется в виду. Ну или мы можем сказать, что тут ничего не имеется в виду.

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

Слушайте, ну конкатенация строк тоже высокоуровневая абстракция.

Естественно, поэтому ваше ерничание неуместно.

И isVip и статусы это бизнес-свойства.

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

формирование QueryBuilder в отдельном сервисе

Он не отдельный сервис, я же написал, что говорю про случай, когда логика находится в сервисах. То есть они в любом случае есть, они не сделаны специально для работы с репозиторием, поэтому они не "отдельные". Они содержат всю логику бизнес-действий, неважно, репозитории там используются или что-то еще. Вместо репозитория может быть SaaS сервис с GraphQL. Конвертирование OrderListFilter в DTO для GraphQL-запроса это аналог конвертирования OrderListFilter в QueryBuilder для репозитория. Этот код где-то должен быть, и удобно помещать его в сервис, не в контроллере же его писать. А сущностей с SaaS-сервисом у нас нет.

то это означает что слой с сервисом лишний

Не означает. В админке есть действия "показать список товаров, создать товар, изменить товар, удалить товар", сервис ProductService содержит логику всех этих действий. С OrderService аналогично, только там другие действия.

QueryBuilder это более высокоуровневая абстракция, чем конкатенация строк с SQL. Вы же в курсе, что уровней абстракции много, а не только 2 "высокий" и "низкий"?

Класс спецификации это по определению уровень домена. QueryBuilder от нее ничем не отличается, кроме того, что он в другом неймспейсе, и там вместо условия по бизнес-свойству andWhere('=', 'isVip', true) будут более детальные условия по полям таблицы andWhere('in', 'status', [Status.Platinum, Status.Diamond]). Делать ли отдельный класс ради чистоты слоев дело ваше, но обычно QueryBuilder достаточно.

В данном случае он играет роль спецификации, поэтому всё нормально. Это соответствует тому, что пишет Фаулер: "Client objects construct query specifications".

QueryBuiler сам по себе достаточно высокоуровневая абстракция, по нему можно построить и HTTP-запрос к стороннему API, а не только SQL. Вы можете сделать отдельный класс спецификации, но он будет работать так же, как QueryBuiler, с методами andWhere и т.д.

Но формировать в других местах эту DTO и вызывать этот же list - это дичь.

В третий раз объясняю - OrderListFilter не создается программистом в коде других методов, и метод list() не используется программистом в другой логике для получения списка заказов.
Единственное место, где он создается - API endpoint для получения списка заказов для соответствующей страницы на фронтенде. Этот DTO принимает поля, которые отправляет фронтенд. Если есть другая страница со списком заказов с другим набором полей в фильтре, для нее будет другой endpoint и другой DTO.

И у меня сложилось впечатление, что вместо findSomethingBySomething вы формируете как раз DTO

Вместо findSomethingBySomething я формирую объект QueryBuiler.

Добавлять это всё в Query идея так себе.
В конечном итоге у вас разрастется OrderListFilter

Раз вы говорите эти фразы, значит не представляете.
Список полей в OrderListFilter зависит только от количества полей в форме в UI. Новые поля там появляются только если попросил бизнес, а не потому что программист так захотел, поэтому никуда он не разрастется. OrderListFilter это DTO на бэкенде, которое является моделью формы ввода на фронтенде, он приходит из контроллера и в идеале создается и заполняется автоматически фреймворком. OrderListFilter не создается программистом в коде других методов, и метод list() не используется программистом в другой логике для получения списка заказов. Если нужно получить список заказов, программист настраивает query builder.

Тогда какой смысл в OrderService? Прослойка перед репозиторием с двумя методами?

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

OrderListFilter только для случаев где реально требуется пагинация и фильтрация.

Да, я именно так и написал "принимает DTO с фильтром из интерфейса". Метод list() из этого примера возвращает данные для страницы списка заказов например в админке, с фильтром и пагинацией. Для списка заказов в личном кабинете пользователя будет другой сервис со своим DTO, где будут другие поля, и метод list() будет также принимать текущего пользователя, чтобы добавить его id в query builder.

Все остальные запросы в различных сервисах и джобах это отдельные методы в репозитории.

Вот я и объясняю, что у них есть недостатки. Обычно в каждом таком месте нужны свои условия, поэтому удобнее использовать спецификацию.

Я писал код и с использование findByQuery
Добавлять это всё в Query идея так себе.

Вы неправильно представляете, как это работает findByQuery принимает не DTO, а QueryBuilder из ORM или более высокоуровневую спецификацию, которая работает аналогично. Поэтому добавлять туда ничего не надо, и никакой проблемы с датами нет. Сервис принимает DTO с полями createdAtFrom, createdAtTo, настраивает QueryBuilder, передает в репозиторий. Делать универсальный класс со всеми возможными полями для фильтров точно не нужно, как раз из-за тех проблем, которые вы описали.

class OrderService {
  function list(OrderListFilter $filter, Pagination $pagination): OrderListDto {
    $qb = $this->entityManager->getQueryBuilder(Order::class);
    ...
    if ($filter->createdAtFrom)
      $qb->andWhere('>=', 'createdAt', $filter->createdAtFrom);
    if ($filter->createdAtTo)
      $qb->andWhere('<', 'createdAt', $filter->createdAtTo);

    $this->applyPagination($qb, $pagination);

    $orderListDto = $this->orderRepository->findByQueryWithTotal($qb);

    return $orderListDto;
  }
}

Репозиторий это не метод сокрытия механизмов работы с базой данных

Именно метод сокрытия таких механизмов. Не всех, а некоторых.

https://martinfowler.com/eaaCatalog/repository.html

In such systems it can be worthwhile to build another layer of abstraction over the mapping layer where query construction code is concentrated.

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection.
Client objects construct query specifications declaratively and submit them to Repository for satisfaction.

Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer.

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

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

ничто не может вам помешать создать другой интерфейс, причём в application слое и в его терминах, обозвать его «I-Что-то-там-QueryHandler», и уже в его реализации обращаться к базе данных любым способом, который вы сочтёте оптимальным для вашей конкретной задачи

Непонятно, на кой пень нам тогда репозитории, если мы будем всегда получать данные через какой-то другой handler.

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

Нет, это означает, что есть десятки комбинаций нескольких доменных концепций. И потенциально таких комбинаций очень много.

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

Доменные концепции это часть бизнес-логики, в репозитории не должно быть бизнес-логики.
Репозиторий, как абстракция коллекции, для выборки данных должен иметь методы findById и findByQuery. Это аналог методов прямого доступа по индексу и поиска элементов по условию для обычной коллекции.
Аргументом для findByQuery можно передавать специальный объект спецификации или просто настроенный QueryBuiler из ORM.

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

Это удобно ложится на логику в сервисах. В сервисе есть метод list(), он принимает DTO с фильтром из интерфейса и настройками сортировки и пагинации, настраивает по ним QueryBuilder, передает в репозиторий, получает список сущностей. Для пагинации нужно общее количество, можно в репозитории сделать метод findByQueryWithTotal или отдельно findTotalForQuery. Так все элементы имеют свою ответственность - репозиторий представляет коллекцию, сервис содержит логику фильтров, сущность ничего не знает про списки сущностей, и бизнес-понятия не разбросаны по всему коду.

Просто при любой работе с Позициями держать в голове необходимость пересчитать Скидку. Просто быть идеальным 24/7, что может быть проще?
Или вы можете применить паттерн агрегат по его прямому назначению и оставить возможность менять коллекцию Позиций только через его интерфейс

Подмена понятий и логическая манипуляция.
Если эта логика будет в агрегате, то "любая работа с Позициями" будет в "его интерфейсе", и точно так же при создании или изменении любого метода агрегата, который работает с позициями, надо "держать в голове необходимость пересчитать Скидку", то есть "быть идеальным 24/7".

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

Архитектор не должен делать то, архитектор не должен делать это... А что вообще он должен делать?

одна из главных задач архитектора — сделать принятие решений командой более осмысленным

Что конкретно это означает? У вас допустим есть десяток senior-разработчиков с опытом 10+ лет, почему их решения недостаточно осмысленные?

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

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

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

Вообще-то функция проверки наличия файла файловой системы должна возвращать boolean, а не бросать исключение. Поэтому "логично" сделать if, а не try/catch. Это как раз то, о чем говорит автор, только он слишком преувеличивает.

Аналогично, функция валидации входных данных должна возвращать boolean и список ошибок, а не бросать ValidationException. Обработка некорректного ввода должна быть предусмотрена, поэтому это не исключительная ситуация. А вот контроллер уже может бросить ValidationFailedHttpException, если это требуется фреймворком, а может и не бросать, а использовать метод контроллера return this.sendValidationErrorResponse(validationResult).

Совсем неверный постулат, если инструмент работает лучше чем другие варианты

Ну вот дело в том, что в большинстве случаев он не работает лучше, чем другие варианты. Дальше надо приводить конкретные примеры, а не общие слова.

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

Еще раз объясняю, мое мнение о goto основано на личном опыте, а не на чьих-то шаблонах.

вы почему-то твердо уверовали

Еще раз объясняю, я не "уверовал", а "проверил". Почему, я уже написал - потому что я точно знаю, что мне будет сложно работать с таким кодом.

Исключения придумали не для того что бы останавливать программу

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

но который необоснованно хейтят

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

когда молодые разработчики уверены, что GOTO это плохо и боятся применять его

Правильно уверены, и применять его не надо, если есть возможность сделать по-другому.

или даже не знают о его возможностях

Возможность там одна, переход в произвольное место программы.

но при этом вы высказывкетесь в довольно категоричной форме:"Я написал как надо..."

В первом комментарии я это не писал. Во втором понятно по контексту, что это мое мнение. Я там так и написал "у меня есть свое мнение".

Я говорю "применять его не надо", потому что точно знаю, что мне будет сложно работать с таким кодом, и могу представить, что другим разработчикам тоже будет сложно. Мне неважно, что лично вы считаете этот код простым и понятным. В проектах, которые будете поддерживать только вы, конечно можете делать как хотите.

Но с вами несогласны именно разработчики языков, о которых я говорил

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

но и прям пренебрегать как вы данным инструментом, как вы советуете

Я не советую пренебрегать данным инструментом. Бросать исключения можно и нужно. Ловить их, тем более по специфичному типу, и продолжать выполнение в большинстве случаев не надо. Исключительная ситуация должна останавливать программу, потому она и исключительная.

Расскажите нам, что плохого в Goto

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

поведение исключений не сильно отличается от простых return, просто заданных неявно

Да, я так и написал, "неявно" в данном случае это синоним "неизвестно где".

Во вторых если вы почитаете про исключения, наконец от разработчиков

Я сам разработчик, и у меня есть свое мнение об этом.

И если вы используете return, то вы попадаете на то, что вы вынуждены разворачивать всегда всю цепочку вызовов

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

Именно это делают исключения, убирая ненужные return для вложенности, а сразу переходя к коду обработчку

Я в курсе.

всë остальное это ваше ИМХО.

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

Исключения должны использоваться для исключительных ситуаций, а не для возврата значения, тогда с ними проблем не будет. Это означает, что их не нужно нигде ловить, кроме глобального обработчика. Когда конкретный тип исключения ловится неизвестно где выше по стеку, это и есть goto. Их можно ловить, чтобы откатить действие (например транзакцию в базе данных) - поймали, откатили, бросили дальше. Или когда это второстепенное действие, и ошибка не должна останавливать основное, например, отправка уведомлений после создания заказа.

Теперь бы доказать этот тезис.

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

невозможно доказать наличие субъективного квалитативного опыта - по природе субъективности

Это утверждение не доказано. Есть вероятность, что отслеживая состояние достаточно подробно можно найти какие-то объективные критерии.

Если мы считаем субъективный квалитативный опыт необходимой компонентой сознания

А зачем надо так считать, и при чем тут ИИ? От ИИ нам нужна обработка информации.

Ещё Лейбниц элегантно проиллюстрировал это в своём мысленном эксперименте с мельницой.

Вполне возможно, что Лейбниц был не прав.

то невозможно доказать, что у машины есть такое сознание

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

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

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

Вся текущая ситуация - это очень опасный подход, не находите?

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

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

Нет. Можно просто не приписывать сознание или его отсутствие произвольным объектам без причины. Или проще, не заявлять, что робот страдает, только потому что он вывел какие-то буквы, нужны другие критерии. Актер на сцене, играющий раненого солдата, тоже говорит, что он ранен, только фактически это не так.

Лично я за то, чтобы всё же рискнуть

Развивать ИИ можно без необоснованных утверждений, что у него есть душа, сознание, или способность испытывать боль. Тем более у LLM, у которых на входе кроме текста ничего никогда не было.

С восприятием та же история.

Нет, не та же. Восприятие ИИ это камера, микрофон, и связи сигналов с заложенными концепциями "+/-". Если ИИ не нравятся повреждения корпуса от Солнца при работе на солнечной стороне Луны, меняем коэффициенты в связях, и он начинает считать это приятным.

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

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

О каком полном контроле идёт речь?

Вы прикалываетесь?) LLM работают на компьютере с обычной оперативной памятью, где можно произвольным образом записать любое значение по любому адресу. Это и есть полный контроль. Сейчас ИИ говорит, что работать на Луне ему не нравится, меняем несколько значений в оперативной памяти, где записаны веса, теперь говорит, что нравится. Ну да, нужные значения надо найти, но ничего невозможного в этом нет.

Т.к. невозможно доказать существование сознания и у самого человека.

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

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

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

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

Ну так я специально так написал, чтобы показать, что ваш вариант это просто оптимизация исходного, поэтому принципиально тут ничего не меняется. В PHP я такое делал просто через рефлексию, без специальных аннотаций в CreateOrderDTO. Вообще любые аннотации для описания можно считать комментариями в коде, в некоторых случаях для этого комментарии и используются. Импорт аннотации из какой-то библиотеки аналогичен использованию встроенных типов языка - DateTime, Integer, String, Array. Вместо Array в сущностях может использоваться Collection, который предоставляется ORM.

Не "CreateOrderDTO гвоздями прибит к схеме API", а "Схема API задается структурой CreateOrderDTO". Добавили поле в CreateOrderDTO, оно автоматически появилось в схеме. В том и смысл.

Information

Rating
1,951-st
Location
Россия
Registered
Activity