Что если можно сделать по другому. Что если можно сделать функцию-объект-сервис "ВыкопатьЗемлюмыЭкскаватором(ЭксковаторИД, ЗемляИД)" и это функция гарантированно изменит состояние всех агрегатов валидно.
Я бы даже продолжил мысль сразу дальше, что если есть веб-серсив, у которого есть http endpoint который принимает ЭксковаторИД и ЗемляИД и гарантирует что действие будет выполнено правильно.
В заметке я как раз и пытаюсь сказать, что, похоже, пора пересмотреть то как мы применяем ООП. Обратите внимание на года стандартов, что вы привели в качестве аргументов, одному из них больше 25 лет, а второму 13.
Если выражаться вашими словами, то я действительно говорю об антииООП.
Вы правы, отсюда огромная путаница между DDD и ООП, как будто одно должно быть выражено в другом. Я делаю скидку автору, книга была выпущенна в 2007 года, уже почти 20 лет прошло, а писалась наверное еще раньше. В те года "клиенты" были толстые, БД медленные(из за дисков), о stateless подходе мало кто говорил, а парадигма ООП казалась ответом на все вопросы, поэтому автор так много уделил ей внимание.
Но сейчас, мы можем взять лучшее из книги - это методы проектирования и используя новые языки, функциональные принципы, stateless подход и микросервисы наконец то реализовать DDD как оно может работать!
Наверное уже пораньше, 15 лет назад, был очередной виток споров между функциональщиками и инперативщиками. Процедурщики и ООПки, наверное, спорили 30 лет назад, в 80х ) когда C++ вынужден был делать zero cost of abstraction.
А что плохого что у класса 1 экземпляр, его задача это сокрытие сложности, если надо скрыть ее один раз, ну и хорошо.
Наследование вообще спорная вещь, все меньше вижу ему применение(речь не про интерфейсы как контракты). Тем более, когда мы все ближе к миру с discriminated union и exhaustive pattern matching, где у наследования еще меньше причин.
Мы эту папочку с функциями-классами упаковываем в сервис, и только этот сервис знает пароль от БД где лежит состояние агрегатов этого домена. Считаем что все методы этого сервиса(api) обязаны менять состояние агрегата валидно и должны быть затестированы - ~100% покрытие тестами.
Ссылка на метод, в случае если сущность и методы изменения состояния разделены, концептуально, почти идентичное решение с тем, что я предлагаю, у него даже есть плюс в том, что все методы в сервисе созданы в рамках вызова одного конструктора -- гарантия что все методы имеют одну ссылку на БД или что-то подобное.
Плюсы которые я могу выделить в моем подходе:
1) Для тестирование отдельного метода, не требуется создавать сложный объект со всеми зависимостями;
2) Небольшие удобные файлы с "функциями", которые могут быть отредактированы отдельно друг от друга. Это кажется не значимо, но на деле, очень удобно.
Мне кажется, что в тезисе "Одним из посылов[ООП(я добавил)] было соблюдение инвариантов класса." и кроется наше с вами различие взглядов, мне кажется, это ошибочный тезис, тем более если речь идет об сущностях из DDD. ООП, похоже, было про алгебраические типы, вроде int, double, которые между собой независимы и состояние у них крайне ограниченно, если это вообще моно назвать состоянием.
Попытка описать инвариант агрегата в рамках одного класса приводит к очень громоздким конструкциям.
"Строить систему на триггерах и хранимках" -- мне казалось, подход, который я предлагаю, прямо противоположный. Все методы изменения состояния явно описаны в коде, независимы друг от друга, да еще и отделены от слоя доступа к данным(data layer). Требуется явный и прямой вызов "функции" изменения состояния, который не удасться скрыть.
Мы не проверяем инвариантность после вызова каждого метода, у нас метод гарантирует что он не нарушит инвариант, а фиксируем мы это в тестах, тестируя каждый метод изменения состояния.
Статьей я хотел намекнуть, что у DogsService просматриваются проблемы с SRP т.к. сервис имеет все зависимости, которые требуются для поддержания инварианта по всем методам изменения состояния собаки. Везде где разработчики взаимодействуют с "собакой", даже по самым простым методам вроде получения года рождения, будет необходимо знать о всей сложности такого сервиса -- о всех зависимостях. Явно это проблема заметна в тестах, где для проверки простого кейса, вроде вычисления возраста собаки, придется инициализировать объект-сервис непропорциональной кейсу сложности.
Ниже в комментариях так же описали еще две проблемы таких сервисов как DogService:
1) со временем сервисы склонным превращаться в god-object 2) если есть задача в одной транзакции и собаку в будку прописать и кол-во жильцов в будке увеличить, то в каком сервисе(сервис собаки или будки) должен лежать такой метод прописки собаки?
В статье я предлагаю распилить сервис на отдельные функции, сложить их в папку с именем DogsService и там где требуется использовать конкретную "функцию-класс". А те функции, которые меняют состояние двух агрегатов вынести отдельно в папку "Services".
Я могу прислать скан страницы из книги, если вы не верите мне на слово. Нам удается в большом проекте успешно применять DDD, на большом кол-ве сущностей.
Думаю что проблема не в книге Эванса, а в том что "мы"(программисты) пишем "Земля.Копайся()" в книге он не настаивает(не призывает) на к такому подходу, а явно говорит, что его книга не про тактику реализации на конкретной парадигме, а про подход проектирования.
Мне было бы интересно, если бы вы сказали решает ли подход, описанный в коментарии выше, проблемы которые вы упомянули.
Я не слышал, что есть такой тренд, подскажите, что они перевели.
Действительно есть сходство с MediatR и CQRS, но, на мой взгляд, есть и ряд значимых отличия.
Заметка, скорее для тех, кто делает классы бизнес-сущности с состояниями и поведением. Надеюсь для них она будет полезной.
Что если можно сделать по другому. Что если можно сделать функцию-объект-сервис "ВыкопатьЗемлюмыЭкскаватором(ЭксковаторИД, ЗемляИД)" и это функция гарантированно изменит состояние всех агрегатов валидно.
Я бы даже продолжил мысль сразу дальше, что если есть веб-серсив, у которого есть http endpoint который принимает ЭксковаторИД и ЗемляИД и гарантирует что действие будет выполнено правильно.
Зачем нам вообще нужно это ООП для этой задачи?
В заметке я как раз и пытаюсь сказать, что, похоже, пора пересмотреть то как мы применяем ООП. Обратите внимание на года стандартов, что вы привели в качестве аргументов, одному из них больше 25 лет, а второму 13.
Если выражаться вашими словами, то я действительно говорю об антииООП.
Вы считаете что инстанцирование классов как-то заметно сказывается на потребление ресурсов?
Расскажите пожалуйста, когда синглтон это отличное решение.
Вы правы, отсюда огромная путаница между DDD и ООП, как будто одно должно быть выражено в другом. Я делаю скидку автору, книга была выпущенна в 2007 года, уже почти 20 лет прошло, а писалась наверное еще раньше. В те года "клиенты" были толстые, БД медленные(из за дисков), о stateless подходе мало кто говорил, а парадигма ООП казалась ответом на все вопросы, поэтому автор так много уделил ей внимание.
Но сейчас, мы можем взять лучшее из книги - это методы проектирования и используя новые языки, функциональные принципы, stateless подход и микросервисы наконец то реализовать DDD как оно может работать!
Наверное уже пораньше, 15 лет назад, был очередной виток споров между функциональщиками и инперативщиками. Процедурщики и ООПки, наверное, спорили 30 лет назад, в 80х ) когда C++ вынужден был делать zero cost of abstraction.
А что плохого что у класса 1 экземпляр, его задача это сокрытие сложности, если надо скрыть ее один раз, ну и хорошо.
Наследование вообще спорная вещь, все меньше вижу ему применение(речь не про интерфейсы как контракты). Тем более, когда мы все ближе к миру с discriminated union и exhaustive pattern matching, где у наследования еще меньше причин.
Что такое настоящий динамический объект?
Мы эту папочку с функциями-классами упаковываем в сервис, и только этот сервис знает пароль от БД где лежит состояние агрегатов этого домена. Считаем что все методы этого сервиса(api) обязаны менять состояние агрегата валидно и должны быть затестированы - ~100% покрытие тестами.
Пока только такие способы нашил.
Кстати! Невозможность передавать в MVC контроллер зависимости явно - это боль. Навязанный DI, какая-то жуткая ошибка. Я вот тут поразмышлял об этом https://habr.com/ru/company/retailrocket/blog/512222/
Ссылка на метод, в случае если сущность и методы изменения состояния разделены, концептуально, почти идентичное решение с тем, что я предлагаю, у него даже есть плюс в том, что все методы в сервисе созданы в рамках вызова одного конструктора -- гарантия что все методы имеют одну ссылку на БД или что-то подобное.
Плюсы которые я могу выделить в моем подходе:
1) Для тестирование отдельного метода, не требуется создавать сложный объект со всеми зависимостями;
2) Небольшие удобные файлы с "функциями", которые могут быть отредактированы отдельно друг от друга. Это кажется не значимо, но на деле, очень удобно.
Я вот на этот тезис из книги опираюсь.
Мне кажется, что в тезисе "Одним из посылов[ООП(я добавил)] было соблюдение инвариантов класса." и кроется наше с вами различие взглядов, мне кажется, это ошибочный тезис, тем более если речь идет об сущностях из DDD. ООП, похоже, было про алгебраические типы, вроде int, double, которые между собой независимы и состояние у них крайне ограниченно, если это вообще моно назвать состоянием.
Попытка описать инвариант агрегата в рамках одного класса приводит к очень громоздким конструкциям.
"Строить систему на триггерах и хранимках" -- мне казалось, подход, который я предлагаю, прямо противоположный. Все методы изменения состояния явно описаны в коде, независимы друг от друга, да еще и отделены от слоя доступа к данным(data layer). Требуется явный и прямой вызов "функции" изменения состояния, который не удасться скрыть.
Мы не проверяем инвариантность после вызова каждого метода, у нас метод гарантирует что он не нарушит инвариант, а фиксируем мы это в тестах, тестируя каждый метод изменения состояния.
Подскажите, а как у вас проверяется инвариант.
Статьей я хотел намекнуть, что у DogsService просматриваются проблемы с SRP т.к. сервис имеет все зависимости, которые требуются для поддержания инварианта по всем методам изменения состояния собаки. Везде где разработчики взаимодействуют с "собакой", даже по самым простым методам вроде получения года рождения, будет необходимо знать о всей сложности такого сервиса -- о всех зависимостях. Явно это проблема заметна в тестах, где для проверки простого кейса, вроде вычисления возраста собаки, придется инициализировать объект-сервис непропорциональной кейсу сложности.
Ниже в комментариях так же описали еще две проблемы таких сервисов как DogService:
1) со временем сервисы склонным превращаться в god-object
2) если есть задача в одной транзакции и собаку в будку прописать и кол-во жильцов в будке увеличить, то в каком сервисе(сервис собаки или будки) должен лежать такой метод прописки собаки?
В статье я предлагаю распилить сервис на отдельные функции, сложить их в папку с именем DogsService и там где требуется использовать конкретную "функцию-класс". А те функции, которые меняют состояние двух агрегатов вынести отдельно в папку "Services".
Подскажите, где вам кажется нарушается принцип DRY?
Я могу прислать скан страницы из книги, если вы не верите мне на слово. Нам удается в большом проекте успешно применять DDD, на большом кол-ве сущностей.
Думаю что проблема не в книге Эванса, а в том что "мы"(программисты) пишем "Земля.Копайся()" в книге он не настаивает(не призывает) на к такому подходу, а явно говорит, что его книга не про тактику реализации на конкретной парадигме, а про подход проектирования.
Мне было бы интересно, если бы вы сказали решает ли подход, описанный в коментарии выше, проблемы которые вы упомянули.