Навіщо використовують DTO. Приклади в Java-застосунках

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Я Сергій Моренець, Java-девелопер, викладач і тренер. На своїх тренінгах регулярно розповідаю про такий популярний патерн, як DTO. Згодом матеріалу з цієї теми набралося досить багато для окремої статті, де я хочу впорядкувати свої знання і поділитися прикладами з практики.

Якщо розробника запитати, з чого складається його проєкт, то він впевнено відповість, що з функцій, класів, компонентів і сервісів. Це можна назвати початковим рівнем розуміння структури проєкту. Але якщо абстрагуватися і піднятися на рівень вище, то виявиться, що в проєктi залучені десятки і сотні випадків застосування патернів, принципів дизайну і конвенцій. Вони впливають на те, наскільки просто зрозуміти і змінити наш проєкт, і на його якість.

Так історично склалося, що першими «офіційно визнаними» патернами в ІТ були патерни проєктування, і придумали їх четверо молодих девелоперів, яких згодом назвали «Банда чотирьох». Їхня книга «Прийоми об’єктно-орієнтованого проєктування. Патерни проєктування» досі вважається бестселером, хоча була випущена ще в 1994 році. Після цього розробники охоче взяли патерни на озброєння. 21 століття принесло нам еру Web 2.0 і розквіт enterprise-застосунків, де широко використовували вебсервіси, ORM-технології, багатопотокову обробку даних і розподілені системи. Відповідно автори почали випускати нові книги, де розповідали про свої дослідження і напрацювання. Так, відомий британський програміст Мартін Фаулер в 2003 році випустив ще один бестселер Patterns of Enterprise Application Architecture, а Кент Бек 2007-го написав книгу Implementation patterns.

У своїй книзі Фаулер розділив 51 патерн на 10 категорій, включаючи Distribution patterns, куди зарахував патерн Data-transfer object (DTO). У цій статті ми розглянемо його призначення і розберемо два найбільш цікаві приклади використання для Java-застосунків. Фаулер давав таке визначення DTO: «Об’єкт, який переносить дані між процесами для зменшення кількості викликів», і згадував альтернативну назву патерну — Value Object, хоча і не схвалював його. Розберімо невеликий приклад для того, щоб зрозуміти його задум.

Отже, уявімо проєкт найпростішого інтернет-магазину. У його доменний моделі є і товари (клас Product), і замовлення (клас Order):

@Getter
@Setter
public class Product {
        private String id;
        private String name;
        private List<Order> orders;
}

Товари повинні відображатися на сайті (і в мобільному застосунку), тому існує REST API, який їх відправляє клієнту:

@RestController
@RequestMapping("products")
@RequiredArgsConstructor
public class ProductController {
       private final ProductService productService;
      
       @GetMapping
       public List<Product> findAll() {
              return productService.findProducts();
       }
 }

І все б добре, але виявляється, що:

  • замовлень для товару може бути непристойно багато, і їх завантаження та відправка уповільнює роботу REST API;
  • замовлення взагалі не потрібні для відображення в розділі «Товари».

Оскільки Spring MVC використовує Jackson для серіалізації даних, то вихід з цієї ситуації досить простий — додати анотацію @JsonIgnore, яка видаляє поле з отриманого JSON:

       @JsonIgnore
       private List<Order> orders;

І все б добре, але виявляється, що є ще один REST-сервiс, в якому замовлення вже повинні повертатися:

       @GetMapping("{id}")
       public Product findById(@PathVariable String id) {
              return productService.findById(id);
       }

Але й тут є залізобетонний вихід — патерн Json View.Необхідно створити новий маркер-клас (або інтерфейс) ProductView і додати в нього вкладені типи для кожного подання нашого товару:

public interface ProductView {
       interface Detailed {}
       interface Summary {}
}

Тепер залишилося змінити @JsonIgnore на @JsonView з того ж Jackson:

@JsonView(ProductView.Detailed.class)
private List<Order> orders;

І відповідно наш REST API:

@GetMapping
@JsonView(ProductView.Summary.class)
public List<Product> findAll() {
       return productService.findProducts();
}
      
@GetMapping("{id}")
@JsonView(ProductView.Detailed.class)
public Product findById(@PathVariable String id) {
       return productService.findById(id);
}

Тепер Jackson буде серіалізувати поле orders тільки для REST-сервісу, на якому стоїть анотація @JsonView(ProductView.Detailed.class). І все б добре, але виявляється, що:

  • Клієнтові потрібно разом з товарами відправити нові дані (наприклад, статистичні), які зберігаються в іншому місці (або навіть іншій базі). Тому клієнт повинен зробити кілька запитів до сервера, щоб отримати всю необхідну інформацію.
  • Наш API має негативну звичку еволюціонувати, і потрібно відправляти клієнтам різні види товару, залежно від версії API.
  • Ми не можемо видаляти і змінювати поля в доменній моделі, оскільки це зламає роботу клієнтського коду.

А найгірше — те, що наша доменна модель (частина middle-tier) залежить від рівня представлення (presentation-tier) і постійно під неї підлаштовується. А це порушує один з наріжних каменів layered architecture — кожен layer повинен залежати (і знати) тільки від нижчих layers. Тому єдиний вихід — відправляти клієнтам тільки ті дані, які вони запитують, а для цього потрібні нові класи, до яких пред’являється жорстка вимога. У них повинні бути тільки дані і жодної бізнес-логіки.

Ці класи і будуть нашими DTO. Для прикладу створимо новий клас DTO для відображення ключових полів товару:

@Getter
@Setter
public class ProductSummaryDTO {
        private String id;
        private String name;
}

Тепер наш REST API не залежить від доменної моделі, але нові класи ускладнюють процес обробки даних, тому що необхідно кожен раз копіювати дані з DTO в доменну модель і навпаки. Це можна робити і вручну, але є цілий набір бібліотек, які дозволяють спростити цей процес, наприклад Dozer, ModelMapper або MapStruct. Якщо ви використовуєте Dozer, то копіювати дані дуже просто:

private final Mapper mapper = DozerBeanMapperBuilder.buildDefault();
@GetMapping
public List<ProductSummaryDTO> findAll() {
       return productService.findProducts().stream().map(product -> mapper.map(product, ProductSummaryDTO.class))
              .collect(Collectors.toList());
}

Dozer дуже гнучкий в налаштуванні, але використовує Reflection API, тому працює повільно. Якщо вам критична швидкодія, то бібліотека MapStruct використовує annotation processor. Він під час компіляції генерує код, який буде копіювати дані object-to-object, тобто максимально швидко.

Зараз DTO — це один з необхідних патернів розподіленої архітектури, який дозволяє розділити доменну модель і модель представлення (ViewModel). У Domain-driven-design (DDD) є принцип anti-corruption layer, який говорить про те, що ми не повинні розкривати (відправляти) споживачам наших сервісів доменну модель, а замість цього створювати проміжний layer (фасад) і віддавати клієнтам. По суті, наші DTO якраз і виконують роль такого anti-corruption layer.

Чи повинен DTO бути immutable? В ідеалі так, тому що після свого створення він серіалізується і відправляється клієнту. І не дуже бажано, щоб якийсь інший код мав можливість його змінювати. Ми можемо оголосити клас immutable, зробивши всi поля final і додавши анотацію @Value з Lombok. Lombok при компіляції згенерує getters і конструктор для ініціалізації всіх полів:

@Value
public class ProductSummaryDTO {
        private final String id;
        private final String name;
}

І якщо додати новий REST-сервіс для створення товарів:

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void save(@RequestBody ProductSummaryDTO dto) {

то наш об’єкт ProductSummaryDTO буде коректно десеріалізований, використовуючи згенерований all-args конструктор. Тут доречно згадати, що в Java 16 з’явилися immutable-типи — записи (records). Раз так, чому б їх не використовувати як DTO, тим більше, що Jackscon, починаючи з версії 2.12, їх підтримує.

public record ProductSummaryDTO(String id, String name) {
}

Є і ще один цікавий приклад використання DTO, пов’язаний не з web, а з ORM-технологіями. Уявімо, що у вас в проєкті використовується JPA (Hibernate) разом з Spring Data і є метод, який запитує і повертає назовні товари з бази:

@RequiredArgsConstructor
@Service
@Transactional
public class ProductService {
        private final ProductRepository productRepository;
      
       public Product findById(int id) {
              return productRepository.findWithId(id);
       }

Оскільки ми позначили наш сервіс як @Transactional, це означає, що після завершення методу findById Spring автоматично завершить транзакцію (і закриє сесію). Але що, як у нас в класі Product є lazy-loading поля?

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = «product»)

private Set<Order> orders;

При спробі до них звернутися поза сесією (наприклад, в контролері або при серіалізації) ми відразу отримаємо сумнозвісну LazyInitializationException. І це буде неприємним сюрпризом, особливо якщо у нас немає інтеграційних тестів. З цією проблемою можна боротися:

  • Змінити fetch type на EAGER.
  • Застосувати JPQL-запит з join fetch.
  • Використовувати OpenSessionInView filter.

Але переважно це workarounds і навіть антипатерни. Крім того, щоб розпізнати цю потенційну проблему, потрібно переглянути весь код, який пов’язаний з тими ж товарами. Є спосіб більш безпечний, хоча і більш витратний — повертати з сервісу тільки DTO. Це гарантує, що всі запити до бази будуть зроблені при активній транзакції. Крім того, ми скопіюємо в DTO тільки ті дані, які нам потрібні. І якщо нам, наприклад, замовлення не потрібні для поточного запиту, то вони і не будуть завантажені з бази.

Отже, ми розглянули історію появи такого патерну, як DTO, і кілька прикладів його використання в Java-застосунках. Загалом застосування DTO дозволяє вирішити цілий пласт проблем:

  • Розділити доменну модель від REST API і позбавити її від необхідності підлаштовуватися під потреби споживачів.
  • Реалізувати версіонування вашого API.
  • Реалізувати принципи Hypermedia (Spring HATEOAS).
  • Створити клієнтську бібліотеку для вашого сервісу і помістити туди DTO. Ця бібліотека буде використовуватися тими сервісами, які викликають ваш REST API.
👍НравитсяПонравилось8
В избранноеВ избранном5
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Давайте рассмотрим конченность подхода с ДТО и вообще конечнность Джавы в частности.
Что такое ДТО ? Это дата транфер модел. Что происходит в реальном приложении, когда у вас допустим 100 таблиц ?
Со стороны приложения:
1. У вас появляется 100-200 классов описывающих дто.
2. На каждый дто появляется маппер, с довольно таки глупой функциональностью.
3. У вас появляется эдак 100-300 веб методов и контроллеров со всей обвязкой которые апдейтят сущности в базе
Со стороны перформанца.
1. Теперь у вас есть 200 классов дто и 300 методов. Если вы хотите сохранить доменную модель, теперь вы судорожно начинаете дергать один за одним эдак 30 веб методов на каждое сохранение или чтение доменной модели в системе.
2. Трафик у вас примерно как на порнохабе. Часто возникает ситуация что нужно только часть данных с тех что присылает метод. Постоянная дилема, запилить новый узкоспециализированный метод чтобы уменьшить нагрузку на систему или вытянуть все скопом а потом отфильтровать.
3. Вы наплодили асинхронной лапши, которая при первой возможности вас наградит увлекательной отладкой, не менее увлекательной чем ваши дедушки писали write only (не путать с read) код которые фанатели от goto.
Со стороны транспортных протоколов.
1. Свято наближається. 200 классов дто с их маперами и контроллерами и еще столькоже веб методов таких же умных, скоро начинают жить своей жизнью. Появляются отдельные версии интерфейсов.
2. Если в системе появляется несколько поставщиков и несколько потребителей и все нужны друг другу, общая диаграмма связей напоминает когда в детстве твоя бабушка тебе худенькому насыпала лапши в тарелку, да побольше, щедро это все поливая сметанкой.

Что предлагается.
1. Уволить несколько сотен джавистов на проекте с ДТО и маппингом головного мозга.
2. Уволить всех девопсов.
3. Засунуть рест сервисы, а потом можно и сервис бас местному архитектору, да поглубже.

PS. Все совпадения случайны. Автор уважительно относится к всем носителям белковой жизни на планете. Пост можно определять (почти) шуткой.

Со стороны перформанца.

ДТО можуть покращити перформанс. В одній з систем яку ми робили, введення ДТО зменшило час життя об’єктів, тим самим зменшило максимальний час на ГЦ і поращило лейтенсі.

Суть проста:
Будь-який паттерн — це трейдофф, рішення чи використовувати його має прийматись в кожному окремому випадку, на основі пріоритезованих НФРів.

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

Мне кажется в тактах процессора с 12 нулями кто-то потерял последние как минимум 10 нулей.

Ти не правий майже у кожному реченні.
Але мені впадло тобі про це розказувать.

А в чем проблема ? В споре рождается истина. Сделаем Моринетс подарок, забустим рекламный пост эдак на 2к комментариев

А в чем проблема ?

В том, что твои тезисы ошибочны :)

забустим
мені впадло

:)

Цікаво, і які альтернативи REST API і DTO ви використовуєте?
І як наприклад, вирішили б завдання версіонування і HATEOAS посилань без DTO?

Вместо Rest и DTO используется что-то вроде GraphQL. ,Тоесть вместо написания сотен контроллеров дто и маперов прибитых гвоздями, пишутся буквально пара тройка методов которые редактируют документы. Делают это батчем и также читают. Целыми доменами. Если мне нужно в контексте 12 атрибутов из 50 то только их я запрашиваю по сети и их читаю.

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

Причем я не хочу сказать что сервис бас вершина эволюции. Эволюция выглядит примерно так RPC/Rest — Service Bus — Sync документов

И RPC как и WCF как и REST и ещё куча лиц одного и того же, это самое деревяное и устаревшее.

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

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

используется что-то вроде GraphQL

*блевотина брызжет во все стороны*
Самая отвратительная и бессмыссленная вещь из виденных мной. Чистый оверинжиниринг на ровном месте. Выкинуть на свалку истории и забыть.

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

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

Самая отвратительная и бессмыссленная вещь из виденных мной. Чистый оверинжиниринг на ровном месте.

Пруф ?

У реста нет проблем. Это просто передача данных, почти самым простым и прямолинейным способом из доступных. Проблема в мозгах.

А ты знаешь какой самый простой и прямолинейный способ в программировании ? Это ассемблер. Работа с регистрами памяти. А еще более прямолинейней ассемблера ? Это бреинфак или машина Тьюринга. Именно из этого ростут проблемы. Придумали Remote Procedure Call чуть ли не во времена меинфреймов и юзают до сих пор. А теперь, не спугнем, и наведем наводящий пример. Самый простой.

У меня есть класс из 50 атрибутов. Назовем их Attr1, Attr2, Attr5 ... Attr50
В первом контексте мне нужны атрибуты 1, 4, 5, 6, 7, 8, 10, 12
Во втором контексте мне нужны атрибуты 11,14, 15, 16, 17, 18
В третьем 5, 8, 10, 20, 50

1. Сколько Контроллеров, Рест методов, Дто, Маперов, Репозиториев у тебя будет чтобы обеспечить бизнесс логику данными из этих 3х контекстов ?
2. Сколько трафика ты будешь генерировать ?
3. Какой оверхед будет на мапинге перемапинге, сериализации десериализации ?

У меня есть класс из 50 атрибутов. Назовем их Attr1, Attr2, Attr5 ... Attr50
В первом контексте мне нужны атрибуты 1, 4, 5, 6, 7, 8, 10, 12
Во втором контексте мне нужны атрибуты 11,14, 15, 16, 17, 18
В третьем 5, 8, 10, 20, 50

Принято

1. Сколько Контроллеров, Рест методов, Дто, Маперов, Репозиториев у тебя будет чтобы обеспечить бизнесс логику данными из этих 3х контекстов ?

1 контроллер, 3 рест-метода, 1 модель, 3 дто.
/my-model/contexts/
..../1/{id}
..../2/{id}
..../3/{id}
Заметь, я просто формально описал твою задачу — и уже получил готовую объектную модель для работы. А ты еще только мудохаешься с написанием схемы и втягиваешь графкл в проект.

Не нравится рест? Я знаю. Потому что ты на каком-то этапе не сумел в дизайн, и утонул в ошибках. А мне — нравится. И я готов отстаивать этот подход. Потому что передо мной почти готовая документация, описывающая CQRS логику работы с данными. Дто-классы четко специфицируют что я отдаю и в каком формате. Рест-методы просты и понятны, как угол дома, их может поддерживать любой джун не приходя в сознание, над каждым методом висят сваггер-аннотации. Рест-доку сгенерированную сваггером поймет даже не-программист, который хотябы отдаленно работал с программистами и знает что такое апи и ендпойнт.

Твой вариант — один ендпойнт для всего-говна-на-свете который делает адову тучу парсинга всякого говна внутри спагетти кода из графкл-резолверов. Для поддержки логики сложнее пет-проекта нужно 2-3 синьора. В нагрузку нихера нечитабельная спека из графкл файлов, написанных на неведомом языке от которого кровь из глаз. Вердикт — НАХЙ.

2. Сколько трафика ты будешь генерировать ?

Ровно столько же, сколько ты. А вот эффективность моего приложения внутри будет гораздо выше, потому что каждая операция может быть легко оптимизирована. У тебя же внутренности будут делать N+100500 селектов.

3. Какой оверхед будет на мапинге перемапинге, сериализации десериализации ?

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

1 контроллер, 3 рест-метода, 1 модель, 3 дто.

Погоди погоди. Правильно ли я понял, что если количество возможных контекстов (помним что атрибутов 50 в таблице) выростит в сто раз (мы же не верим что у нас сайт будет состоять из всего 3х страничек ?) то количество методов будет 300 и 300 дто 300 мапперов ? И это на одну таблицу.

А кто это все будет писать и поддерживать ? Отлаживать ? Кто это все будет комитить ? За все это отвечать ? Кто будет ревьювить ? На каком сервере это все будет работать ?

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

У тебя уже есть эти 300 апи методов. Появились новые требования. Поменялась сущность, теперь везде нужно пробрасывать новое поле.

1. Меняем 300 методов ? Как обновить всех консюмеров которые уже присосались к твоим 300 методам ?

2. Добавилась локализация. Что произойдет с твоими 300 рест апи методами ?

3. Добавилось логирование 300 методов. Что делаем ?

4. Добавилось секурити, в 300 методов. Что делаем ?

5. Добавилась историчность. Теперь можно запросить данные на какуюто дату в прошлом.

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

Поздравляю, по-моему это успех.

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

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

Мне кажется

Да, тебе кажется.

самые глупые подходы

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

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

ничего там убедительного нет

начать с того что не будет никаких 300 методов. 5, 10 — может. 300 — нет. Все проблемы, упомянутые оратором, тупо высосаны из пальца. А если у автора действительно 300 вьюх 1 сущности то это значит что он рукожоп и не сумел в дизайн данных или бизнес анализ.

А комментировать, насколько сложно прологгировать 10 методов имеет смысл?
Но даже деюре ответить можно.
Логгирование — АОП.
Локализация — проблема дизайна данных а не доступа к ним
Секюрити — вообще идиотский аргумент. Везде есть глобальные настройки секьюрити конфиги, хоть для миллиона ендпойнтов
Историчность/версионность — еще более идиотский аргумент. Версионирование всегда является проблемой, и проблема эта лежит в изменениях структур хранимых данных и изменениях операций и процедур работы с ними, а не в апи к ним.

Итого из 5 «проблем» 2 порождены незнанием оратором инструментов, а 3 — непониманием причин.

5, 10 — может.

Так 10 методов на одну таблицу. Если таблиц 100, то сколько получается рест методов ? Сколько в твоем прод приложение рест методов ?
Неужели 5-10 штук ?

Но даже деюре ответить можно.
Логгирование — АОП.
Локализация — проблема дизайна данных а не доступа к ним
Секюрити — вообще идиотский аргумент. Везде есть глобальные настройки секьюрити конфиги, хоть для миллиона ендпойнтов
Историчность/версионность — еще более идиотский аргумент. Версионирование всегда является проблемой, и проблема эта лежит в изменениях структур хранимых данных и изменениях операций и процедур работы с ними, а не в апи к ним.

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

Так 10 методов на одну таблицу. Если таблиц 100, то сколько получается рест методов ?

Математика от аквариумной рыбки.

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

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

Все это в сумме изобличает в тебе формошлема-крудописца «с 20-летним опытом». И это позволяет не воспринимать всерьез твои надутые щеки и пузыри из носа про гранулярность, аттрибуты и графкл.

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

Ты не ответил на вопрос

И не отвечу, потому что твой вопрос идиотский. Задавать правильные вопросы ты еще не научился.

Рест метод — это бизнес-значимая операция с логикой, а не доступ к таблице. Проводить количественную корреляцию между ними может только человек, не понимающий различия между данными и интерфейсом доступа к ним. Бинго, это ж ты!
Если я тебе скажу что рест-метод может вообще не читать и не писать таблицы, ты наверное, с ума сойдешь.

Приходи, когда умственно созреешь для осознания, поговорим.

Рест метод — это бизнес-значимая операция с логикой, а не доступ к таблице.

Понимаешь, я тебя пытаюсь вытянуть на уровень абстракций выше, а ты застрял на уровне Remote Procedure Call.

Если бы мы затеяли эту дискуссию в 80х, ты бы мне втирал что ассемблерная комманда CALL это просто вызов метода, который ты наполняешь как хочешь и типа хорошие разработчики сами все напишут на ассемблере. А я бы тебе втирал про ООП.

Но мы не в 80х, а в 2021 году. И ты втираешь мне про Рест и ДТО, а я тебе втираю про АОП на уровне субд, которая оперирует доменами.

АОП на уровне субд

*фейспалм.жпг*

Я нашел твое фото. На мониторе можно разглядеть Рест апи метод под номером 17824 и маппинг дто.
www.meme-arsenal.com/...​ef9ff9e5e60004e5a068d.jpg

Вообще странный вы человек Юрий Винидиктович.
Буквально сегодня вот такой здравый пост
dou.ua/...​rums/topic/34172/#2202671

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

Если мне нужно в контексте 12 атрибутов из 50 то только их я запрашиваю по сети и их читаю.

А ты понимаешь, что в этом случае берёшь на себя всю ответственность за поддержание адекватности состояния объекта? Вот что, если из этих 12 какие-то 2 будут требовать инварианта с другими 2, которые ты вообще не упоминал и про них не думал?

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

Или вот: в запросе 1 ты у объекта X1 выбрал атрибуты P и Q, в запросе 2 — Q и R. Теперь ты пишешь изменённое состояние, в обоих ответах модицифируя Q. Чья модификация останется и почему? В случае работы с объектом целиком (в стиле DTO) у тебя есть хоть какое-то понимание, что на клиентской стороне надо держать только один экземпляр. А в случае такого расщеплённого представления?

Ну и, сама машина фильтрации в GraphQL для одного объекта — это просто потеря ресурсов. Ладно бы ещё сложная выборка по группе объектов, особенно с хитрыми связями...

А ты понимаешь, что в этом случае берёшь на себя всю ответственность за поддержание адекватности состояния объекта?

Адекватность состояния обьекта должна контролировать база данных. Она последний бастион консистентности. Бизнесс логика никогда не умела правильно и красиво валидировать данные.
Для этого и придуманы транзакции.

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

Почему ? Элементарный SELECT позволяет перечислить колонки которые нужны. Что плохого чтобы вернуть из таблицы не все колонки, а те что нужны ?

Или вот: в запросе 1 ты у объекта X1 выбрал атрибуты P и Q, в запросе 2 — Q и R. Теперь ты пишешь изменённое состояние, в обоих ответах модицифируя Q. Чья модификация останется и почему? В случае работы с объектом целиком (в стиле DTO) у тебя есть хоть какое-то понимание, что на клиентской стороне надо держать только один экземпляр. А в случае такого расщеплённого представления?

ДТО здесь абсолютно ничего не меняет. Я тебе сейчас расскажу недавний практический пример. Есть 10 микросервисов, которые вычитывают доменную модель целиком. Тоесть это ДТО которое содержит агрегированные данные допустим с 20 таблиц. Каждый сервис пытается сохранится. Но как пытается, конечно же по опмитистической блокировке. Тоесть Сервис 1 получил версию 20 таблиц и Сервис 2 получил версию этих 20 таблиц вместе с ДТО. Теперь Сервис 1 пытается сохранится, но обнаруживается что одна из таблиц ыла изменена Сервисом 2 и происходит полный ресет данных, эксепшин и все по новой. Понимаешь что происходит ? Чем больше обьект тем сложнее его сохранить. Если бы эти сервисы запрашивали и сохраняли то что им нужно, этой проблемы бы не было. А консистентность данных должна контролировать _база данных_ и больше никто. Никакие костыли с дто, глупыми валидациями и проверками на нулл и прочьим хламом, только субд, единственный механизм данные держать в консистентном и непротиворечивом состоянии существует толкьо на уровне субд. Все.

Тоесть Сервис 1 получил версию 20 таблиц и Сервис 2 получил версию этих 20 таблиц вместе с ДТО. Теперь Сервис 1 пытается сохранится, но обнаруживается что одна из таблиц ыла изменена Сервисом 2 и происходит полный ресет данных, эксепшин и все по новой. Понимаешь что происходит ? Чем больше обьект тем сложнее его сохранить.

И все 20 таблиц (не строка в 20 таблицах, а 20 целых таблиц) являются одним объектом, который целиком меняют?

В том то и дело, что меняют его конечно не целиком. Но ДТО и агрегейт рут подразумевает то что у него есть версия. Предположим наш агрегейт рут это Компания. Данные о Компании находятся в 20 разных дочерних таблицах. С точки зрения бизнесс логики, конечно удобно вычитать ДТО Компания и работать с ним, как с консистентным обьектом. Это то, о чем говорит Валентин. Но проблема в том что когда это ДТО вычитывает Сервис 1 он меняет только адресс этой компании. Сервис 2 меняет только название компании. Сервис 3 добавляет допустим связь с дочерней компанией. Все три сервиса меняют версию рута, версию ДТО. И все попадают на конфликты, на оптимистической блокировке.

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

Ну если у тебя несколько человек начнут менять одну компанию — то будет конфликт. Но вероятность такого события крайне низкая. И такие конфликты лучше разрулить самим пользователям. А то будут перетирать изменения друг друга.

Но вероятность такого события крайне низкая.

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

Кстате красивое решение это гранулярные версии. Тоесть не одна версия на весь рут, а версия на каждый атрибут нашей доменной модели. Но такое реализовать на бизнесс логике с ДТО крайне тяжело и работать будет криво. А вот на базе данных, да еще с аспектной логикой, запросто.

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

Вася исправлял ошибку в адресе, пошел в туалет, в это время заказчик позвонил Пете и сказал, что надо еще раз исправить. Петя исправил, Вася пришел из туалета и перетер изменения Пети, не увидев, что запись уже другая стала.
Можно даже не гранулярные версии аттрибутов — достаточно вместе с новым значением аттрибута отсылать старое тем же сообщением. Если старое не равно тому, что в базе — отлуп с апдейтом на экран того, что сейчас в базе.

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

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

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

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

Ага, а бекенд с фронтендом-то все равно менять надо)

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

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

Не совсем так.
1. Фронт имеет свою подножную базу данных. Он туда пишет как хочет. Он пишет туда быстро и стремительно, также от туда и читает. Можно сказать что это его локальный кеш. Аналог ДТО.
2. Все самое интересное начинается, когда он пытается запушить свои изменения в центрульную базу данных.
3. Он идет и говорит, версия моей локальной базы данных, которую я последний раз прочитал, версия номер 5.
Поверх этой версии у мен есть допустим 20 новых изменившихся атрибутов.
4. Когда к серверу приходят с версией 5, сервер обнаруживает, что вообщето последняя версия данных на сервере это версия 8. Он пытается извлечь по своей истории все изменения между 5й и 8ой версией и определить, а не менял ли кто эти 20 атрибутов между 5й и 8й версией, которые ему пытаются сейчас запушить.
5. Если не менял, он говорит добро фронту и позволяет запушить изменения. Сам апгрейдится до 9й версии. И фронт пропагейтится до 9й версии..
6. Если менял, тогда он говорит. Нет, погоди, вот эти 2 атрибута уже изменили пока тебя не было. Решай что с ними делать, потом пушь.

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

Потому что считается что локи это плохо. Ведь Петя может залочить обьект и уйти пить кофе. Оптимистическая блокировка позволяет всем работать паралельно. Это аналог лок фри алгоритмов из С++. Проблемы начинаются только когда начинают сохранятся, тут то и выясняется что пока ты пил кофе, твою версию доменной модели уже ктото поменял и умудрился сохранить. И тебе придется идти на второй круг, прочитал, поменял, пытаешся записать. До победного конца.

Локи с таймстампами (типа записал когда начал редактировать, за минуту он протух) вполне ок, если на вебе раздельные формы под чтение и под редактирование.

Система не работает нормально

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

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

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

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

Боже, какой же ты смешной и тугой.

Это равноценно как открыть транзакцию на базе данных уровня снапшот на 20 таблицах

Я наверное открою для тебя Америку, но пессимистические локи != SELECT ... FOR UPDATE.

Любой разработчик (кроме тебя, конечно) может управлять логикой локов. Преимущество пессимистик локов как раз в том, что именно они подходят для высоко-конкурентного приложения, не отваливая 99 запросов из 100 с OptimisticLockException. Но тебе это конечно не понятно, как и то, как можно управлять локами иначе как
SELECT * WHERE root.id FOR UPDATE
Но ты не погружайся, ты не поймешь все равно.

SELECT * WHERE root.id FOR UPDATE

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

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

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

А оно и не должно.

То, что ты это не понимаешь, уже ясно. Можешь не повторяться.

читать домен частично

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

делая рид коммитед транзакцию

О боже ужас.

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

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

Это равноценно как открыть транзакцию на базе данных уровня снапшот на 20 таблицах и уйти пить чай.

Snapshot по определению не ставит блокировок, или у вас какие-то СУБД с собственными названиями для всего?
(как именно вводится блокировка для записи, оптимистично или пессимистично, от этого уровня не зависит)

После этого переходит к второй фазе. А именно важности гранулярности блокировок и принципа шаринг насинг.

Не вижу, чем особенно sharing nothing тут важен в этом контексте.

Тебя куда-то совсем не туда понесло.

Snapshot по определению не ставит блокировок, или у вас какие-то СУБД

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

Не вижу, чем особенно sharing nothing тут важен в этом контексте.

Это вторая сторона гранулярности. Чем меньше шаред поинтов в системе, тем лучше. Тем система лучше справляется с параллельной обработкой.

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

Может, это в одном конкретном блокировочнике? И явно не в новых версиях, иначе бы такой бред давно ликвидировали.

Это вторая сторона гранулярности. Чем меньше шаред поинтов в системе, тем лучше. Тем система лучше справляется с параллельной обработкой.

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

В общем, не понимаю смысла этой дописки в текущем контексте.

Может, это в одном конкретном блокировочнике? И явно не в новых версиях, иначе бы такой бред давно ликвидировали.

Что значит в конкретном ? Да в принципе везде. Из контекста понятно что не так просто СУБД этот уровень обеспечить, поэтому такое поведение.

Другой вопрос что бизнесс логике такой топ уровень изоляции нужен редко. В основном по дефолту это рид коммитед. В более сложном случае это репитбл рид. С такой изоляцией например можно делать декремент счётчиков товаров на складе и гарантировать что один и тот же товар не продадут в двух параллельных транзакциях.

Угу, и тем сложнее (а значит и больше багов)

Шаред насинг логика позволяет избежать багов в многопоточности впринципе.

Что значит в конкретном ? Да в принципе везде. Из контекста понятно что не так просто СУБД этот уровень обеспечить, поэтому такое поведение.

Что-то тут совсем не то.

Snapshot — это про то, что _читается_ содержимое базы во всех деталях на момент старта транзакции. Как именно будет _писаться_ база — уровни изоляции транзакций регулируют уже косвенно. Может, транзакция вообще ничего писать не предполагает, но ей нужно консистентное неизменное состояние.
Дальше, если действительно тупая реализация, она будет всех блокировать. Если умнее — она будет для снапшота хранить старые версии всего изменяемого.
А если запрошена хоть одна запись — тут зависит от определения уровня snapshot в конкретном движке. Может быть, да, они будут драться с любым другим изменением. Но опять же, может быть политика «кто первый сказал commit, того и тапки» (обычно приоритет у раньше начавшейся транзакции, но может быть какая-то более сложная логика).

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

В данном случае нет «тупых» и «умных» реализаций.
Есть блокировочники и есть версионники. И у первых и у других есть свои плюсы и минусы. Например Оракл реализовал версионник, а MS SQL реализовал блокировочник.

Может быть, да, они будут драться с любым другим изменением.

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

Но опять же, может быть политика «кто первый сказал commit, того и тапки» (обычно приоритет у раньше начавшейся транзакции, но может быть какая-то более сложная логика).

Такой подход не работает и снимает пользу с уровня изоляции снапшот. Пример. 10 товаров в стоке. Начинается первая транзакция, пишет осталось 9 товаров. В этом же время, почти одновременно , начинается вторая транзакция, пишет осталось 9 товаров. В итоге из 10 товаров продано 2 товара, а в стоке осталось 9 товаров вместо 8.

Есть блокировочники и есть версионники. И у первых и у других есть свои плюсы и минусы. Например Оракл реализовал версионник, а MS SQL реализовал блокировочник.

Что-то у тебя странная терминология. Чистый версионник — это PG, там пишется новая версия отдельно для каждого изменения (кажется, даже откаченного). В то же время, вариант, когда старая версия сохраняется для того, кому нужна в рамках транзакции — реализуется же и в MS SQL и в Oracle? Они сохраняют новую версию строки на том же месте, а точный момент, когда вписывается новая — уже не принципиален. А чтобы нельзя было получить старую версию, пока сохраняется транзакция — такой тупизны они не делают.

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

Мержить что? От строки сохраняется та версия, которая прошла коммит без конфликтов или победив в них. (включая новые и удалённые)
Остальные просто теряются и затем подбираются в GC.

Такой подход не работает и снимает пользу с уровня изоляции снапшот. Пример. 10 товаров в стоке. Начинается первая транзакция, пишет осталось 9 товаров. В этом же время, почти одновременно , начинается вторая транзакция, пишет осталось 9 товаров. В итоге из 10 товаров продано 2 товара, а в стоке осталось 9 товаров вместо 8.

Эээ... а почему у тебя второй снапшот увидит изменения из первого, которые ещё не закоммичены?

И почему, если это изменение количества в одной строке, они не законфликтовали, а если в разных, то не объединились?

Что-то я уже запутался. Попробуем распутать этот клубок.
Весь тред начался с этого поста.

Это равноценно как открыть транзакцию на базе данных уровня снапшот на 20 таблицах и уйти пить чай.

1. Блокировочник именно так и работает. Пример блокировочника MS SQL и при открытии снапшот транзакции он много чего может заблокировать. (да я знаю что в новых версиях МС СКЛ добавили элементы версионности, но о них ниже).

2. С версионниками чуда не происходит. Если двум транзакциям понадобится снапшот одних и техже данных, читать они будут каждый свой срез базы данных на опредлеенное время, без вопросов. Проблема начнется когда они начнут записывать и комитить одни и теже данные параллельно (пример со стоком) У каждой такой транзакции будет своя версия данных. Для оракла (любой версии), например, правила доступа к одним и тем же данным таковы: «читатель не блокирует читателя», «читатель не блокирует писателя», «писатель не блокирует читателя», «писатель блокирует писателя». Неблокируемость читателя обеспечивается созданием нужной для него версии данных, писатели же работают с последней версией данных.

3. Возвращаясь к изначальному посту, создавать снапшот транзакции которые не только читают данные это плохо. И для блокировчников и для версионников.

Все

Поздравляю, накрутили говнокода потому что не разобрались что такое CQRS.

Как раз это и есть CQRS шаблон. Самый что нинаесть православный, с коммандами, оптимистическими блокировками, агрегейт рутами (они же домены приложения) и вот этим вот всем. Как ты его не узнал ?

В твоем посте, который я откоментил, нет ничего про CQRS, есть про 20 табличек и 1 рут, а также о том, что вы вытягиваете весь граф, что само по себе п****ц, хотя для тебя это не удивительно.

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

The Command and Query Responsibility Segregation (CQRS) — принцип или парадигма CQRS разделяет назначение запросов(напр. при чтении данных) и команд на обработку данных

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

1 рут, а также о том, что вы вытягиваете весь граф, что само по себе п****ц,

Для этого тебе нужно еще чуток подрости и понять, например, о чем говорил Валентин. Всетаки даже если ты просто крутишь баранку авто и нажимаешь на 2 педали, есть разница между таксистом с опытом в 3 года и 15 лет. Ты уж поверь.

Агрегейт рут существует для того, чтобы сохранить согласованность и непротиворечивость всего домена при его обработке бизнесс логикой. А с твоими рест методами, которых ты наплодил как кхм блох, ты можешь вычитать одним рест методом одно состояние домена, другим рест методом другое состояние домена. А вместе что ты вычитал со своим любимым дто не согласуется. И получаем тот самый
ru.wikipedia.org/wiki/Гейзенбаг

The Command and Query Responsibility Segregation (CQRS) — принцип или парадигма CQRS разделяет назначение запросов(напр. при чтении данных) и команд на обработку данных

Пожалуй, тут ты достиг вершины своего развития, копипастить из Вики тоже надо уметь. Но я доволен хоть тем, что в результате ты прочитал что такое CQRS. Правда, я уверен, что ты ничего не понял, но может годков через 10-20 «опыта» поймешь. Ты главное википедию не бросай.

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

Агрегейт рут существует потому, что ты его создал, думая, что он нужен, а не потому, что он нужен. Не уверен правда что ты поймешь столь сложное предложение.

А вместе что ты вычитал со своим любимым дто не согласуется.

А оно и не должно.

Походу ты даже не понимаешь разницы между данными и апи к ним.

Агрегейт рут существует потому, что ты его создал, думая, что он нужен, а не потому, что он нужен. Не уверен правда что ты поймешь столь сложное предложение.

Я понял. Больше вопросов не имею. Продолжай писать сайты визитки с дто и рест апи. Пока это твой уровень понимания проблематики.

Что такое ДТО ? Это дата транфер модел.

DTO is a data transfer OBJECT
ну если ты аббревиатуру из 3х букв расшифровать не сумел, то дальше о чем речь :))))))

Окей. Чем концептуально обджект отличается от модели ?

концептуально проблема в том, что ты не в состоянии правильно дефинировать классическую аббревиатуру. Но чсв имеешь овер 9000. И это смешно.

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

иди учи букварь :)

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

Я вижу ты себе сенсея по дто нашел )
Вместе построите амазон на основе сайта визитки )
dou.ua/...​rums/topic/34411/#2203675
Только не плачь потом в соседнем топике, ок ?
dou.ua/...​rums/topic/34172/#2202476

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

Чем дальше ты пишешь, тем больше понятно, что ты не понимаешь ни терминологию ни проблематику дизайна данных, но зато большой чсв на ровном месте :)

Ну да, я имея почти 20 лет опыта в коммерческой разработке, насмотревшись десятки разных видов архитектур, от спартанских сишных, с еав до «современных с дто», сервисбасами ажур, авс, докерами кафками ничего не понимаю ) А ты, молодой хомячок, который был выдрессирован писать с оверхедом уже на взлете, все понимаешь )
Не хотите смотреть на проблему под другим углом, ваше дело. Только не жалуйтесь что джава скатилась в говно и джава девелоперы горят как спички на проектах ) это ваши проблемы если не умеете правильно писать код без оверинжениринга )

Я-то умею, а вот вы, несмотря на

20 лет опыта в коммерческой разработке

нет, раз так и не разобрались в ресте, виня его в своем неумении :)

писать код без оверинжениринга )

...сказал человек топящий за графкл, который является чистым оверинжинирингом на ровном месте

десятки разных видов архитектур, от спартанских сишных, с еав до «современных с дто», сервисбасами ажур, авс, докерами кафками

Ну, то что насмотрелся, не значит поумнел, соглсись.

нет, раз так и не разобрались в ресте, виня его в своем неумении :)

Дело скорей не в ресте, а дто. А именно в таких кадрах как ты, которые на три селекта с одной таблицы уже сгенерили три дто с нулевой пользой. И с перспективой сгенерить ещё сотню.

...сказал человек топящий за графкл, который является чистым оверинжинирингом на ровном месте

Только для джунов которые не понимают идеи и как зомби плодят пустые по наполнению слои приложения.

Ну да, я имея почти 20 лет опыта

Як людина, яка має 15+ років досвіду срачів в інтернетах, можу вас запевнити, що людині, яка апелює до розміру (досить посереднього розміру) свого стажу роботи, довести нічого не можливо :)

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

Опыт прежде все даёт навык содержательно держать разговор, по существу. А у вас же как в том анекдоте, мама сказала лямочки на ушанке завязывать, как же без ушанки теперь то жить.

Ну смешно же. Причем тут стаж.

Не знаю, спитайте у чувака, який почав заливати про 20 років досвіду :)

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

Тому що вони не однакові або немає впевненості, що будуть однаковими в найближчому майбутньому. Для того щоб зрозуміту картинку з книжки Фаулера не треба 20 років martinfowler.com/...​g/dataTransferObject.html

Ще один сценарій я навів вам вище, але ви замість того щоб його уточнити у разі якщо не зрозуміли, що там була «числодробилка» (і прирост був за рахунок того, що в сучасних ДжВМ для нашого сценарія алокація і ГЦ «молодих» об’єктів було швидшим ніж ГЦ старих об’єктів), придумали, що йшлось про «простий веб круд»

Тому що вони не однакові або немає впевненості, що будуть однаковими в найближчому майбутньому. Для того щоб зрозуміту картинку з книжки Фаулера не треба 20 років martinfowler.com/...​g/dataTransferObject.html

Это хорошо иллюстрирует пример с ушанкой. Ушанку Фаулер придумал когда на дворе метель и мороз. Но носят ее теперь даже на южных континентах. И в 95% на проектах эти классы именно _одинаковые_.

немає впевненості, що будуть однаковими в найближчому майбутньому

Да, нужно именно 20 лет опыта и не годом меньше, чтобы наконец дошел основпологаяющий принцип программирования.
Преждевременная оптимизация корень всех зол ©

Да, нужно именно 20 лет опыта и не годом меньше, чтобы наконец дошел основпологаяющий принцип программирования.
Преждевременная оптимизация корень всех зол ©

печально. Будучи несколько моложе Кнута, и имея доступ к его трудам, потратить 20 лет чтобы осознать один из классических тезисов CS вообще.... я почему то не удивлен :)

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

Ви вважаєте, що людина, яка переживає через перепаковку в ДТО, усвідомила те що казав Кнут (хоча я чомусь думав, що то Хоар, але я в персоналіях не дуже шарю)?

я вот тоже думал что Хоар автор, но гугл говорит что как минимум, популяризировал идею Кнут. Специально перепроверил перед постом. Даже в статье вики про Кнута есть эта цитата
en.wikiquote.org/wiki/Donald_Knuth
Поймет ли элементарные термины записной клоун — скорее нет чем да.

чтобы осознать один из классических
Специально перепроверил
перед постом.

Видишь ли Прокофьев, от моих постов есть польза. Ты начал гуглить термины и учится на Википедии.

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

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

Ты можешь сказать, о каких азах речь ? Проблема молодых разработчиков, что они не видели просто какие были системы еще лет 15 назад.
В том же MS SQL/Oracle хот фикс выкатывался на девелоп, а иногда и прод в течении одной минуты, без редеплоя. Про девопсов даже не слышали. А сейчас сколько проект деплоится? Минут 30, может час ?
Когда выкатывали фикс, сервер не останавливали.
Сам SQL запрос, учитывая декларативность языка, писали без ошибок с первого раза, если конечно это не простыня на 2 экрана. Но в круде такое редкость.
Все запросы легко ловились профайлером. Юнит и интеграционные тесты в такой системе по определению не нужны. Там нечего тестировать, нет той бизнесс логики которую нужно тестировать. Все слои схлопнуты до уровня базы данных, а база данных простая как пять копеек, если ее не загаживать дто и мапингом перемапингом.

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

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

о тех самых азах СS

СS

 за CS апай тему с Индианом. Там весь Индиан один сплошной CS

т.е. тебя там учат cs :)

Про девопсов даже не слышали.

Про девопов (development operator — в единственном числе). DevOps (development operations) это подход в целом.
(Ну если на самом деле не имелось в виду «про девопсо́в» у которых фурри-сьюты.)

Но вариант с выкатом напрямую на прод или даже на тестовый стенд... когда последний раз сталкивался с тем, что там ещё надо выполнить 105 команд по подточке конфигов, чтобы это заработало? И что последний человек, который умел в эти команды, ушёл 2 месяца назад через дорогу на +500? А человек, который умел всё делать за один раз без проблем и ошибок, вообще был только один за всю историю, и то устал от такой жизни?
Извини за кэпство.

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

Хорошо тому, у кого простая база. Завидую. Хотя нет, задачи явно слишком простые, мало заплатят.

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

Не согласен. Раньше был один монолит. Этот монолит было реально запустить на своей машине. Конекшин стринг к базе, например, там прописывался всего в одном месте. Дебажился монолит с пол пинка.

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

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

Проблема не в перепаковке, проблема намного глубже. Я бы написал большую статью, как все нужно проектировать, но сейчас пока нет времени.

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

Не забудьте розказати в ній, як підтримувати контракт між різними деплоймент елементами (хоча б фротенд та бекенд), та як забезпечити простоту внесення змін у бізнес логіку та структуру моделі одного з елементів без змін та подальшого регресійного тестування інших :)

Поддерживать контракт или обратную совместимость научились еще лет 30 назад, через метерилизированные вью и инстид оф тригеры. Всякие Apex как-то работают. Но эта идея слегка устарела, хотя и она на голову выше чем безудержное засорение бизнесс логики дто и рест сервисами.
С дто и рест я видел слишком много натуральных живых трупов. Тормозящих, не поддерживаемых, не гибких, раздутых до невероятных размером на пустом месте.
Любая бизнесс логика должна быть ближе к данным. Это плюс и по трафику, плюс к валидации, плюс к секурити.
А вьюхи данных это просто вьюхи, их собирает база данных, они тривиальны. В 95% случаев данные ложатся на СУБД один в один

через метерилизированные вью и инстид оф тригеры.

www.youtube.com/watch?v=KLYlIQ2oqJE

Вот до чего дтошки доводят. Украиноязычный джавист начал бросать ссылки на российские сериалы.

Сам подход называется data transfer objects.
Хотя почему-то его часто называют по одному объекту.

Деталі мають значення. По суті, не зважаючи на мінорність у назвах, ви вводи людей в оману і допускаєте серйозну помилку.

martinfowler.com/eaaCatalog

куди зарахував патерн Domain-transfer object (DTO)

Патерн називається Data Transfer Object. Це не про домен (предметну область), а про сутність для передачі даних між процесами. Іноді, цей патерн використовують для передачі даних між слоями монолітної системи.

Фаулер давав таке визначення DTO: «Об’єкт, який переносить дані між процесами для зменшення кількості викликів», і згадував альтернативну назву патерну — Value Object

Знову ж термінологія.
У Фуалера є окрмо патерн Value Object:
Many people in the Sun community use the term «Value Object» for this pattern. I use it to mean something else. See the discussion on page 487.

Value Object не є альтернативною назвою для DTO. Просто в часи до P of EAA люди не думали виділяди DTO, а частіше оперували поняттями by reference, by value.

Так, все вірно, виправив. Звичайно, він називається Data-Transfer-Object.
Дякую за уточнення.

Ничего страшного. Согласно аксиоме Эскобара там не было ошибки.

ви вводи людей в оману

Ніколи в своїх статтях у мене не було мети вводити людей в оману. Це була просто описка.

Давайте следующую статью про что-то по-настоящему сложное.
Про pojo, конструкторы, гет-сет.

Давайте следующую статью про что-то по-настоящему сложное.

Тема, доречі, дуже складна і обширна, але вона не про технології, а про підходи парадигми. Цього в статті не видно.

Про pojo, конструкторы, гет-сет.

Тут теж є багато про що поговорити. Інше питання, що якісні статті на ці теми буде дуже важко і довго писати.
Те ж саме pojo:
— історія та передумови появи
— анемічна та багата моделі
— джава бін та мапінги, автоматизовані мапінги
— слої, класи-сервіси
— те ж саме ДТО

Тут лише основні матеріали опрацьовувати буде десь 8-16 годин (це при умові, що той хто пише шарить і йому треба просто перечитати/освіжити).

Так, згоден.
Але сенс поста був трохи в іншому )

А так я згоден.
Одна тільки тема про ломбок і геттери проти public final у дто може на статтю затягнути.

Хорошая вводная статья.
Но на практике при большой вложенности объекта Entity и если не следить за сгенерированным Mapstruct кодом можно легко как раз и нарваться на N+1 проблему. Например mapstruct зайдет сам в каждый вложенный объект и сам попробует его конвертировать. Как правило Марино находиться в сервисе = в транзакции и мы получим огромное количество запросов если до этого не сделали join fetch.
При использовании «удобных» библиотек можно откопать те же проблемы только под другим углом.

А не надо маппинги пхать в транзакционный слой.

Как правило Марино находиться в сервисе = в транзакции

Потому что «как правило» никто не думает о дизайне.

Ну т.е. в Spring не писать @Transactional а юзать TransactionTemplate и самому оборачивать. Думаю такое не очень удобно. Где тогда должен быть маппинг, в отдельном классе выше *Service ?

Где тогда должен быть маппинг, в отдельном классе выше *Service ?

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

Ну тогда контроллер будет получать Entity объекты про которые он вообще не должен знать. Я более склонялся к еще 1 прослойке между ними, но нигде этого не видел и возможно это уже over engineering

Ну тогда контроллер будет получать Entity объекты про которые он вообще не должен знать.

Якщо для вас так критичний «пуризм», то зробіть шар маперів :)
Власне в чому проблема того що контролер буде формувати об’єкт БЛ (з ДТО запиту) для передачі в сервіси і формувати ДТО для відповіді? До яких негативних сценарій це може привести?

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

Скоро психотерапевты будут исследовать следующие диагнозы.
Поражение головного мозга ООП, мания создания «шарiв маперiв», обсессивно-компульсивное расстройство на почве дто.

Та тут просто хотел получить структурированный ответ на практическую проблему.
А «хуяк-хуяк и в продакшн» всегда можно сделать.

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

Ну можно ещё про ООП поговорить.
Какие основные принципы ООП ты знаешь и какие из них хоть както относятся к маппинг и дто ?

Какие основные принципы ООП

Сладкий, на роль экзаменатора ты точно не подходишь

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

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

И в этом же посте

app.module1.api.logic
app.module1.api.model
app.module1.impl.logic
app.module1.impl.model
app.module2.*
module1 только через
app.module1.api.*
app.moduleX.api.*
app.moduleX.view.*, только к app.moduleX.api.model.* ограничен пакет app.moduleX.api.logic.*

Сначала я хотел ставить биполярочку, но там между сменами личностей должно проходить хотябы 48 часов.
Коллеги нид хелп. Я в замешательстве.

ставить биполярочку

Ты слишком часто об этом упоминаешь, дедушка Фрейд смотрит на тебя с подозрением.

Ты выдохся, давай иди передохни уже, развейся немножко.

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

Нужно где то попробовать будет такой подход

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

везде маперы суют в сервисы.

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

Я тебе больше скажу, внутри приложения я давно стараюсь по возможности выделять слой внутреннего апи, типа
app.module1.api.logic
app.module1.api.model
которые реализуются в
app.module1.impl.logic
app.module1.impl.model
и таким образом, например
app.module2.*
должен взаимодействовать с module1 только через
app.module1.api.*
и в таком случае контроллеры взаимодействую только с
app.moduleX.api.*
т.е. они обязаны заниматься маппингом на
app.moduleX.view.*, т.к. имеют доступ только к app.moduleX.api.model.* классам, потому что ими ограничен пакет app.moduleX.api.logic.*
С одной стороны, это вроде как должно быть всем очевидно из dependency inversion, а с другой стороны, я приходя на проект вижу обычно адову смесь слоев почти без какого-либо структурирования.

Да интересный подход, и так мы можем оперировать на уровне реализации «полным» entity а наружу будет виден только нужный интерфейс модели.
Конечно хочу попробовать сначала, потому что с наследованием нужно быть осторожным, а тут еще наследование моделей, с которым легко упороться.

У вас не росла сложность и количество боли при менеджменте наследования modelApi->modelImpl после такого подхода ? потому что не приходилось работать с командой над системой в который большой слой именно контроллеров.

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

сложность и количество боли при менеджменте наследования modelApi->modelImpl

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

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

Так в этом и суть того, что тут обсуждают.

А чому MapStruct зайде в кожен вкладений об’єкт? Він скопіює тільки ті поля, які є в DTO. А те, що є в DTO, контролюєте тільки ви самі.

Ну это понятно. Просто если у нас DTO класс и должен быть с внутренними объектами, мапстракт сам полезет и начнет их мапить (если ему явно не указать чтобы он их не трогал). И мы получим N SQL запросов если забыли join fetch

Хороший матеріал. Коротко, але дуже по суті.

Насправді паттернами є все. Взагалі все, що ти можеш думати та висловлювати — є патернами. Навіть крапка в цьому реченні. Тому вважати щось єдино-вірними патернами є ні що інше як бюрократизм. Патерни не є константою, їх змінять щойно це знадобиться. Проблема лише в тому, що бюрократи, які ним сліпо поклоняються, змінити не можуть нічого — бо не розуміють що і навіщо вони роблять.

А от коли якийсь визнаний гуру бюрократії патерни змінить — та аж бігом, бо виніпанімаєтє етодругоє, то ж іновація, а у вас гріх смертний.

Насправді нема нічого поганого у зв′язках там, де за теорією має бути абстракція. Бо єдина справжня мета абстракцій — спростити розуміння. Якщо абстракція розуміння не спрощує — вона зайва, це генератор помилок. Чому так: бо патернами є все, що ви можете мислити. Абстракція має бути патерном конкретної сутності. Або ще краще сказати, що для людини є патерном і сама сутність, і тому між абстракцією і повним об′єктом нема різниці. Чи краще сказати, не має бути — бо говнокодери таки роблять незрозумілі абстракти, яким навіть імені не даси. А ще й додадуть слово «Abstract» до імені, щоб було зрозуміло ще менше.

Саме тому при побудові класів описання даних, зовсім не зайвим буде цитувати ТЗ прямо ж таки в коментах до класу. Лише так, через надлишкові дані, ви можете досягти абстракцій у розумінні проекту, значно знизивши у читача потребу читати зайвий код. Чи можуть подібні надлишкові дані бути зв’язками, всупереч теорії? Звичайно ж можуть. Бо гарний код — це керований код. Легкість внесення змін, легкість перевірки — це фінансова вигода, перш за все вигода передбачуваності наступних завдань по проекту.

Саме написання коду — це проста задача, яку оптимізувати не варто. Оптимізувати треба асоціативну пам’ять людини, що цей код читатиме. Жодна теорія вам не допоможе, якщо ви програмуєте, виходячи з презумпції безлімітної пам’яті читача та повного розуміння читачем всього коду одразу. Саме це зветься говнокодом. Чи може бути говнокодом той проект, де вся бюрократична частина виконана? Та майже гарантовано. Бо що бюрократія робить найкраще — так це будує паралельний всесвіт, з якого всі проблеми можна просто викинути, роблячи вигляд що їх не існує.

Бо єдина справжня мета абстракцій — спростити розуміння

То, чего не понимает почти никто по моим наблюдениям.
И чего очень тяжело достичь.

Якщо люди цього не розуміють, навчіть їх.

Есть несколько проблем.

1. Это на самом деле никому не надо, кроме 5-10%. Будем честны — большинство девелоперов выросли и сформировались из джунов в архитекты, исповедуя подход «нахуачить как-нибудь чтобы работало на демке», закрыть таски, закрыть спринт, и идти к новым таскам. И это понятно, потому что за это платит бизнес, за качество кода никто не платит. Ему не учит ни один туториал, оно не нужно в жизни, потому что на самом деле 90% людей пишут примитивный круд-код и они никогда не сталкивались с трудностями своего дизайна. Качественный дизайн никому не нужен.

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

3. по причине 1 и 2 в процессе обучения возникнет много споров на которых можно просадить очень много энергии.

4. какой с этого реальный профит, учитывая еще и пп.1-3?

5. в догонку есть еще одна проблема — достаточно сложно придумать искусственные примеры доменной логики чтобы дать учащимся и продемонстрировать проблемы. Это уже полноценная преподавательская работа.

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

class Event {
    String content
}
class Command {
   String content 
}
class Domain {
   String content
}
Event e = getEventFromSomeTransport()
...
Command c = mapper.map(e);
...
Domain d = mapper.map(c);
Бессмысленный и беспощадный маппинг одной и той же вещи сквозь слои. При этом объяснять что это не CQRS бесполезно, люди не понимают. Класс Command есть? Есть! Он разделен с остальными объектами? Да! CQRS!!1
Объяснять, что это бессмысленный код ради кода на ровном месте — тоже бесполезно, люди не понимают, цепляясь за формальное определение Dependency Inversion, рассказывая байки про разделение слоев. Проект уже написан так, всем ок, никто не видит проблемы.

А ви ніколи не думали, чому в звичайній людській мові 5-50 тисяч слів, а не 5 мільйонів?
Патерни — це ж теж як мова. Сказати Adapter або Decorator простіше, ніж 5 хвилин пояснювати. Але ви не зможете створити 10 тисяч патернів, тому що ніхто не зможе їх запам’ятати, а тим більше застосувати.
А тепер уявімо, що у кожного розробника свій набір патернів зі своїми проблемами/рішеннями. Наскільки легко було б програмувати і розуміти чужий код?

Так, простіше — лише доки зберігається їхній сенс. Щойно вимагається трактовка — шаблон мертвий. Інакше кажучи, патерни — то добре, якщо знати їхнє призначення, та припиняти використовувати застарілі щойно себе віджили, або не придатні для конкретного проекту.

Приклад: патерн Visitor. А ну, спробуй хоча б сам собі пояснити, що воно таке і навіщо, так, як би ти пояснював іншим. Писати тут не треба, я знаю, що «як правильно» не важко скопіпастити, краще зрозуміти спробуй саму суть патернів на цьому прикладі.

В минулому Visitor як патерн проектування— досить гарна ідея, власне кажучи, вона може ще хто зна скільки раз актуалізуватися. Але патерн мертвий. Тупо через небезпечність невірної трактовки. Патерн, який треба щоразу пояснювати, не може бути загальноприйнятим. Простіше таки створити локальний новий, та пояснити в конкретному проекті, що мається на увазі.

Подписаться на комментарии