Microservices Restful API - DTO или нет?
REST API - DTO или нет?
Я хотел бы задать этот вопрос в контексте Microservices. Вот цитата из оригинального вопроса.
В настоящее время я создаю REST-API для проекта и читаю статью о передовой практике. Многие, кажется, против DTO и просто просто выставить модель домена, в то время как другие, похоже, думаю, DTO (или пользовательские модели или что-то, что вы хотите назвать) плохие практика. Лично я думал, что эта статья имеет большой смысл.
Однако я также понимаю недостатки DTO со всеми дополнительными код отображения, модели домена, которые могут быть на 100% идентичны их DTO-аналог и т.д.
Теперь, Мой вопрос
Я больше ориентирован на использование одного объекта через все слои моего приложения (другими словами, просто выведите объект домена, а не создавайте DTO и вручную копируйте по каждому полю). И различия в моем контракте и коде могут быть решены с помощью аннотаций Джексона, таких как @JsonIgnore
или @JsonProperty(access = Access.WRITE_ONLY)
или @JsonView
и т.д.). Или, если есть одно или два поля, которым требуется преобразование, которое невозможно сделать с помощью аннотации Джексона, тогда я напишу пользовательскую логику, чтобы справиться именно с этим (поверьте мне, я не сталкивался с этим сценарием даже не один раз в свои 5+ лет долгое путешествие в службах отдыха)
Я хотел бы знать, не хватает ли я каких-либо реальных плохих последствий для того, чтобы не копировать домен в DTO
Ответы
Ответ 1
Плюсы разоблачения объектов домена
- Чем меньше кода вы пишете, тем меньше ошибок вы производите.
- несмотря на наличие обширных (спорных) тестовых примеров в нашей базе кода, я столкнулся с ошибками из-за пропущенного/неправильного копирования полей из домена в DTO или наоборот.
- Поддержание работоспособности - меньше кода котловой плиты.
- Если мне нужно добавить новый атрибут, мне, конечно, не нужно добавлять в Domain, DTO, Mapper и testcases. Не говорите мне, что это может быть достигнуто с помощью отражения beanCopy utils, оно побеждает целую цель.
- Ломбок, Groovy, Котлин, я знаю, но это спасет меня только головной болью геттера.
- DRY
- Performance
- Я знаю, что это подпадает под категорию "преждевременная оптимизация производительности - это корень всего зла". Но все же это сэкономит некоторые циклы процессоров, чтобы не создавать (и позже собирать мусор) еще один объект (по крайней мере) за запрос
Против
- DTO предоставят вам большую гибкость в долгосрочной перспективе
- Если мне нужна только такая гибкость. По крайней мере, все, что я до сих пор встречал, это CRUD-операции над http, которыми я могу управлять, используя пару @JsonIgnores. Или, если есть одно или два поля, которым требуется преобразование, которое невозможно сделать с помощью аннотации Джексона, как я уже говорил ранее, я могу написать собственную логику, чтобы справиться именно с этим.
- Объекты домена, раздутые с помощью аннотаций.
- Это актуальная проблема. Если я использую JPA или MyBatis в качестве моей постоянной структуры, объект домена может иметь эти аннотации, тогда также будут аннотации Джексона. В моем случае это не очень подходит, но я использую Spring boot, и я могу уйти, используя свойства всей приложения, такие как
mybatis.configuration.map-underscore-to-camel-case: true
, spring.jackson.property-naming-strategy: SNAKE_CASE
Краткая история, по крайней мере, в моем случае, минусы не перевешивают плюсы, поэтому нет смысла повторять себя, имея новый POJO как DTO. Меньше кода, меньше шансов на ошибки. Итак, продолжайте раскрывать объект Domain и не иметь отдельный объект "view".
Отказ от ответственности. Это может быть или не быть применимым в вашем случае использования. Это наблюдение за мой usecase (в основном CRUD api с 15-ю конечными точками)
Ответ 2
Я бы проголосовал за использование DTO, и вот почему:
- Различные запросы (события) и сущности БД. Часто бывает так, что ваши запросы/ответы отличаются от того, что у вас есть в модели домена. Особенно это имеет смысл в архитектуре микросервиса, где у вас много событий, поступающих из других микросервисов. Например, у вас есть объект Order, но событие, которое вы получаете из другого микросервиса, - OrderItemAdded. Даже если половина событий (или запросов) совпадает с сущностями, все же имеет смысл иметь DTO для всех из них, чтобы избежать беспорядка.
- Связь между схемой DB и API, которую вы показываете. При использовании объектов вы в основном раскрываете, как вы моделируете свою БД в конкретном микросервисе. В MySQL вы, вероятно, захотите, чтобы ваши сущности имели отношения, они будут довольно массивными с точки зрения композиции. В других типах БД у вас будут плоские объекты без большого количества внутренних объектов. Это означает, что если вы используете объекты для раскрытия вашего API и хотите изменить свою БД, чтобы сказать MySQL в Cassandra, вам также нужно будет изменить свой API, что, очевидно, плохо.
- Контракты с потребителями. Вероятно, это связано с предыдущей пулей, но DTO упрощает обеспечение того, чтобы связь между микросервисами не прерывалась во время их эволюции. Поскольку контракты и БД не связаны, это проще проверить.
- Агрегация. Иногда вам нужно вернуть больше, чем у вас в одном объекте СУБД. В этом случае ваш DTO будет всего лишь агрегатором.
- Производительность. Микросервис предполагает много передачи данных по сети, что может стоить вам проблем с производительностью. Если клиентам вашего микросервиса требуется меньше данных, чем вы храните в БД, вы должны предоставить им меньше данных. Опять же - просто сделайте DTO, и ваша сетевая нагрузка будет уменьшена.
- Забудьте об исключении LazyInitializationException. DTO не имеет ленивой загрузки и проксирования, а не объектов домена, управляемых вашим ORM.
- Уровень DTO не так сложно поддерживать с помощью правильных инструментов. Обычно возникает проблема при сопоставлении объектов с DTO и назад - вам нужно вручную устанавливать правильные поля каждый раз, когда вы хотите сделать преобразование. Легко забыть о настройке отображения при добавлении новых полей в объект и в DTO, но, к счастью, есть много инструментов, которые могут выполнить эту задачу для вас. Например, мы использовали MapStruct в нашем проекте - он может автоматически преобразовывать вас в и во время компиляции.
Ответ 3
Решение намного проще, если вы используете CQRS, потому что:
- для стороны записи вы используете
Commands
, которые уже являются DTO; Aggregates
- объекты с богатым поведением в вашем доменном слое - не подвергаются/не запрашиваются, поэтому проблем нет.
- для чтения, потому что вы используете тонкий слой, объекты, извлеченные из персистентности, должны быть уже DTO. Не должно быть проблем с отображением, потому что вы можете иметь
readmodel
для каждого варианта использования. В худшем случае вы можете использовать что-то вроде GraphQL, чтобы выбрать только нужные вам поля.
Если вы не разделите чтение с записи, тогда решение будет сложнее, потому что в обоих решениях есть компромиссы.