Как правильно делать DTO?
От: vsb Казахстан  
Дата: 07.04.18 15:31
Оценка:
Под DTO я понимаю классы (структуры) с данными, без какой-либо существенной логики. Например такие классы часто принимает/возвращает слой доступа к БД или с ними оперируют REST-контролеры, веб-сервисы и тд.

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

Какие вижу минусы: во-первых часто есть несколько запросов, которые возвращают разное число данных. Скажем одно дело это вытащить сущность по ID, тут можно и поджойнить другие сущности, другое дело это делать сложный селект, возвращающий тысячу сущностей, любой джойн тут неприятен, да и просто лишнее поле тянуть не хочется, если оно не нужно. Т.е. запросы по сути возвращают разные типы, но логически у них куча общих полей и делать разные классы на каждый запрос это странно, одних имён не напасёшься на такие классы, к тому же нередко код работает со всеми этими классами и в типизированном языке придётся выдумывать какой-то ад с интерфейсами. Проще просто сделать один мегакласс со всеми полями ну и каждый запрос заполняет некоторое их подмножество. В итоге каждое поле может принимать значение undefined, которое в той же Java напрямую не выражается. Если сильно не мудрствуя использовать null, то легко допустить баг, когда код думает, что это поле извлечено из базы, а оно не извлечено, система типов не помогает и хорошо, есть будет NPE, а то и просто ошибка в логике. JavaScript теоретически тут лучше, каждое значение может быть undefined, хотя на практике с такими значениями может быть непросто работать правильно.

Второй вариант это сделать для каждого поля значения undefined, причём такой undefined, что при любой попытке его использовать вылетит ошибка. Либо сделать класс-холдер, либо специальным образом генерировать DTO-классы. Мне такой подход нравится больше всего, но его тяжело использовать: наивный подход ведёт к большому перерасходу памяти, излишней нагрузке на GC и дополнительным переходам по ссылкам, по крайней мере в Java, а грамотный подход требует специальной библиотеки, которую не так тривиально написать.

Попутно есть ещё одна проблема. Часто при работе с БД прикручивают кеширование. С другой стороны если у нас есть класс с "сеттерами", то можно получить закешированное значение, вызвать на него сеттер (нечаянно) и забыть про это. В следующий раз у нас это же закешированное значение уже будет с изменённым полем. То бишь хочется часто отдавать иммутабельные объекты. С другой стороны объект того же класса рядом должен быть мутабельным, получили значение, поменяли, сохранили в БД. Делать все объекты иммутабельными, на мой взгляд, это перебор. Поэтому хочется помимо неинициализованности хранить ещё для каждого поля его иммутабельность. Ну и тоже эффективно, конечно же, а не плодить врапперы.

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

В итоге получается какой-то не совсем простой DTO-объект.

Как эти проблемы решаются? Особенно буду благодарен информации про Java, но и для других языков интересно. В той же Java пока нигде не видел нормальных решений, обычно никак не решаются эти проблемы, посему чешутся руки написать свой "фреймворк" с мегаоптимальной генерацией байткода.
Отредактировано 07.04.2018 15:35 vsb . Предыдущая версия . Еще …
Отредактировано 07.04.2018 15:35 vsb . Предыдущая версия .
Отредактировано 07.04.2018 15:33 vsb . Предыдущая версия .
Отредактировано 07.04.2018 15:31 vsb . Предыдущая версия .
Re: Как правильно делать DTO?
От: Qulac Россия  
Дата: 07.04.18 16:34
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Под DTO я понимаю классы (структуры) с данными, без какой-либо существенной логики. Например такие классы часто принимает/возвращает слой доступа к БД или с ними оперируют REST-контролеры, веб-сервисы и тд.


vsb>Первый вариант это тупо структура с полями. В мире Java к ней обычно автогенерируют тривиальные геттеры-сеттеры, в других мирах делают проперти или просто высовывают поля наружу, суть одна.


vsb>Какие вижу минусы: во-первых часто есть несколько запросов, которые возвращают разное число данных. Скажем одно дело это вытащить сущность по ID, тут можно и поджойнить другие сущности, другое дело это делать сложный селект, возвращающий тысячу сущностей, любой джойн тут неприятен, да и просто лишнее поле тянуть не хочется, если оно не нужно. Т.е. запросы по сути возвращают разные типы, но логически у них куча общих полей и делать разные классы на каждый запрос это странно, одних имён не напасёшься на такие классы, к тому же нередко код работает со всеми этими классами и в типизированном языке придётся выдумывать какой-то ад с интерфейсами. Проще просто сделать один мегакласс со всеми полями ну и каждый запрос заполняет некоторое их подмножество. В итоге каждое поле может принимать значение undefined, которое в той же Java напрямую не выражается. Если сильно не мудрствуя использовать null, то легко допустить баг, когда код думает, что это поле извлечено из базы, а оно не извлечено, система типов не помогает и хорошо, есть будет NPE, а то и просто ошибка в логике. JavaScript теоретически тут лучше, каждое значение может быть undefined, хотя на практике с такими значениями может быть непросто работать правильно.


DTO этой клей,мудрить тут не надо, если даже что-то повторяется это не так страшно. Если не хватает имён, то использовать пространства имён, например: Service1.Dto, Service2.Dto и т.д. Ну не много можно использовать наследование что бы повторно использовать такие поля как например Id. Если всё это не подходит, то можно передавать какой ни будь dictionary с key-value, только это уже кривой способ.

vsb>Второй вариант это сделать для каждого поля значения undefined, причём такой undefined, что при любой попытке его использовать вылетит ошибка. Либо сделать класс-холдер, либо специальным образом генерировать DTO-классы. Мне такой подход нравится больше всего, но его тяжело использовать: наивный подход ведёт к большому перерасходу памяти, излишней нагрузке на GC и дополнительным переходам по ссылкам, по крайней мере в Java, а грамотный подход требует специальной библиотеки, которую не так тривиально написать.


vsb>Попутно есть ещё одна проблема. Часто при работе с БД прикручивают кеширование. С другой стороны если у нас есть класс с "сеттерами", то можно получить закешированное значение, вызвать на него сеттер (нечаянно) и забыть про это. В следующий раз у нас это же закешированное значение уже будет с изменённым полем. То бишь хочется часто отдавать иммутабельные объекты. С другой стороны объект того же класса рядом должен быть мутабельным, получили значение, поменяли, сохранили в БД. Делать все объекты иммутабельными, на мой взгляд, это перебор. Поэтому хочется помимо неинициализованности хранить ещё для каждого поля его иммутабельность. Ну и тоже эффективно, конечно же, а не плодить врапперы.


vsb>Ну и для ORM-подобного функционала хочется отслеживания изменений, то бишь чтобы update сохранил только изменённые поля.


vsb>В итоге получается какой-то не совсем простой DTO-объект.


Ну да, если еще добавить отложенную загрузку данных то будет совсем не DTO, а DАО. Есть паттерны для этого, например активная запись.

vsb>Как эти проблемы решаются? Особенно буду благодарен информации про Java, но и для других языков интересно. В той же Java пока нигде не видел нормальных решений, обычно никак не решаются эти проблемы, посему чешутся руки написать свой "фреймворк" с мегаоптимальной генерацией байткода.
Программа – это мысли спрессованные в код
Re[2]: Как правильно делать DTO?
От: vsb Казахстан  
Дата: 07.04.18 16:40
Оценка:
Здравствуйте, Qulac, Вы писали:

Q>DTO этой клей,мудрить тут не надо, если даже что-то повторяется это не так страшно. Если не хватает имён, то использовать пространства имён, например: Service1.Dto, Service2.Dto и т.д.


class GetPersonById.Record { string lastName, string firstName, string patronymic, Address address }, GetPersonListByCountry.Record { string lastName, string firstName, string patronymic }?

Q>Ну не много можно использовать наследование что бы повторно использовать такие поля как например Id. Если всё это не подходит, то можно передавать какой ни будь dictionary с key-value, только это уже кривой способ.


Ну вот пример выше. У меня метод, который возвращает ФИО для персоны. getPersonFullName(??? person) { return person.firstName + " " + person.lastName + " " + person.patronymic }. И какой тут тип передавать?

vsb>>Ну и для ORM-подобного функционала хочется отслеживания изменений, то бишь чтобы update сохранил только изменённые поля.


vsb>>В итоге получается какой-то не совсем простой DTO-объект.


Q>Ну да, если еще добавить отложенную загрузку данных то будет совсем не DTO, а DАО. Есть паттерны для этого, например активная запись.


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