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, чтобы выбрать только нужные вам поля.

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