Как называть одинаковые сущности в разных слоях?
От: vsb Казахстан  
Дата: 11.02.20 18:08
Оценка:
Предположим, есть сущность User. Для работы с БД используется слой доступа к данным. Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД. Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению. Скорей всего часть этих полей будет совпадать с полями для БД, но вряд ли на 100%. Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают. При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность. А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?

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

2. Писать новый класс для каждого юз-кейса. Как их называть? Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто. Например в Java, где импорты переименовывать нельзя, а полностью специфицированные имена слишком длинны. Делать префиксы? Ну их не хватит на всё. Как данные копировать из одного такого класса в другой? Рефлексией? Руками скучно и долго.

3. Не писать классы, а использовать Map<String, Object>. Ну это уже динамическая типизация получается со всеми её минусами.

Как с таким умные люди борются?
Re: Как называть одинаковые сущности в разных слоях?
От: Sharov Россия  
Дата: 11.02.20 18:33
Оценка: 10 (1)
Здравствуйте, vsb, Вы писали:

vsb>Предположим, есть сущность User. Для работы с БД используется слой доступа к данным. Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД. Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению. Скорей всего часть этих полей будет совпадать с полями для БД, но вряд ли на 100%. Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают. При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность. А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?


vsb>1. Использовать первый класс везде. Возникает опасность, что юзер сможет изменить поле, которое ему менять не положено. С этим можно пытаться бороться...


Очевидно, что этот вариант. А пользовательский интерейс должен быть спроектирован таким образом, чтобы пользователь не смог ничего лишнего сделать.
В любом случае смотрите на DTO и в зависимости от ситуации, убирайте или добавляйте соотв. поля.
Т.е. сущность должна быть одна, максимально общая, но для отображения должна настраиваться соотв. образом (сервисом).
Кодом людям нужно помогать!
Re: Как называть одинаковые сущности в разных слоях?
От: blp  
Дата: 11.02.20 19:02
Оценка: 10 (1) +3
Здравствуйте, vsb, Вы писали:

vsb>2. Писать новый класс для каждого юз-кейса. Как их называть?

Называть User и класть в разные namespaces / packages. Делать using / import там, где надо использовать.


> Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто.

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

> Например в Java, где импорты переименовывать нельзя

ну вот в Java нельзя, в Java писать больше придется. В других языках можно, там меньше писать.
Re: Как называть одинаковые сущности в разных слоях?
От: fmiracle  
Дата: 11.02.20 19:51
Оценка: 10 (1)
Здравствуйте, vsb, Вы писали:

vsb>Предположим, есть сущность User. Для работы с БД используется слой доступа к данным. Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД. Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению. Скорей всего часть этих полей будет совпадать с полями для БД, но вряд ли на 100%. Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают. При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность. А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?


User — объект доменной модели (ну или данных, если дата-драйвен-дизайн), UserViewData для отображения данных в представлении.

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

В одном интерфейсе, однако, может быть несколько использований данных по пользователе. Например списки с краткой информацией или страница подробных деталей. Тут или детальнее именовать (UserListItemViewData) либо использовать вложенные классы (UserListViewData внутри имеет класс RowViewData поскольку используется только в рамках этого списка)
Re: Как называть одинаковые сущности в разных слоях?
От: bnk СССР http://unmanagedvisio.com/
Дата: 11.02.20 22:36
Оценка: 10 (1)
Здравствуйте, vsb, Вы писали:

vsb>1. Использовать первый класс везде. Возникает опасность, что юзер сможет изменить поле, которое ему менять не положено. С этим можно пытаться бороться...


vsb>2. Писать новый класс для каждого юз-кейса. Как их называть? Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто. Например в Java, где импорты переименовывать нельзя, а полностью специфицированные имена слишком длинны. Делать префиксы? Ну их не хватит на всё. Как данные копировать из одного такого класса в другой? Рефлексией? Руками скучно и долго.


Пункт 1 в принципе рабочий вариант, пока возможен. проблемы начнутся например если объекты на фронтенде сильно отличаются от объектов в базе. Или если есть разница, кто что (какие поля) должен видеть.

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

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

Для пункта 2 придумали всякие мапперы, в том числе и не через рефлекшен (см automapper например), неймспейсы тоже вполне себе, если сделать префиксы. Больше двух версий одной сущностив одном файле встречаться вроде бы не должно..
Re: Как называть одинаковые сущности в разных слоях?
От: akasoft Россия  
Дата: 12.02.20 07:25
Оценка:
Здравствуйте, vsb, Вы писали:


vsb>Как с таким умные люди борются?


Тоже интересно.

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

Но обычно хочется минимизировать размер данных, поэтому DbUser, MobileUser, ViewUser, ChangeUser не такая уж и глупость.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re: Как называть одинаковые сущности в разных слоях?
От: scf  
Дата: 12.02.20 07:46
Оценка: 16 (2) +1
Здравствуйте, vsb, Вы писали:

vsb>Предположим, есть сущность User. Для работы с БД используется слой доступа к данным. Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД. Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению. Скорей всего часть этих полей будет совпадать с полями для БД, но вряд ли на 100%. Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают. При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность. А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?


vsb>1. Использовать первый класс везде. Возникает опасность, что юзер сможет изменить поле, которое ему менять не положено. С этим можно пытаться бороться...


vsb>2. Писать новый класс для каждого юз-кейса. Как их называть? Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто. Например в Java, где импорты переименовывать нельзя, а полностью специфицированные имена слишком длинны. Делать префиксы? Ну их не хватит на всё. Как данные копировать из одного такого класса в другой? Рефлексией? Руками скучно и долго.


vsb>3. Не писать классы, а использовать Map<String, Object>. Ну это уже динамическая типизация получается со всеми её минусами.


vsb>Как с таким умные люди борются?


Разделение приложения на слои (dao/repository, бизнес-логика, апи) и максимальная изоляция этих слоев, в том числе через использование разных моделей. Т.е. в приложении будут три класса User. Чтобы не путать их и упростить контроль за границей слоев, обычно используют суффиксы:
UserDO (Data Object) — с этой моделью работает слой DAO. User — это модель для бизнес-логики. UserTO (transfer object) — это модель для API.
Re: Как называть одинаковые сущности в разных слоях?
От: Vladek Россия Github
Дата: 12.02.20 08:24
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Предположим, есть сущность User. Для работы с БД используется слой доступа к данным. Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД. Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению. Скорей всего часть этих полей будет совпадать с полями для БД, но вряд ли на 100%. Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают. При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность. А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?


vsb>1. Использовать первый класс везде. Возникает опасность, что юзер сможет изменить поле, которое ему менять не положено. С этим можно пытаться бороться...


У этой сущности есть идентичность (ID) — вот это и использовать везде. Написать тип UserId (не использовать примитивный тип), который и использовать для запроса дополнительных данных в нужном виде, когда они действительно нужны.

vsb>2. Писать новый класс для каждого юз-кейса. Как их называть? Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто. Например в Java, где импорты переименовывать нельзя, а полностью специфицированные имена слишком длинны. Делать префиксы? Ну их не хватит на всё. Как данные копировать из одного такого класса в другой? Рефлексией? Руками скучно и долго.


Стараться не копировать данные туда-сюда, а просто иметь возможность их запросить в самый последний момент по идентификатору, а ещё лучше просто передать идентификатор туда, где делается реальная работа.
Re: Как называть одинаковые сущности в разных слоях?
От: Sharov Россия  
Дата: 14.02.20 12:29
Оценка:
Здравствуйте, vsb, Вы писали:

https://aspnetboilerplate.com/Pages/Documents/Data-Transfer-Objects
Кодом людям нужно помогать!
Re: Как называть одинаковые сущности в разных слоях?
От: maxkar  
Дата: 17.02.20 19:08
Оценка: 10 (1)
Здравствуйте, vsb, Вы писали:

vsb>Предположим, есть сущность User. Для работы с БД используется слой доступа к данным.

Допустим.

vsb>Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД.

Почему логично? Давайте создадим User так, чтобы он нормально отображал предметную область и сервисам было удобно с ним работать. А БД и отображением на столбцы пусть занимается слой доступа к данным, ему за это деньги платят (и вообще не обязательно один класс на одну таблицу отображается).

vsb>Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению.

Т.е. пользователю нужен JSON и для этого вы создаете класс? А в чем логика? Если вам нужен JSON, создавайте JSON. При нормальном API (пример) его создавать проще, чем перекладывать из объекта в объект. Можно включать не все поля сущности. Можно включать поля нескольких сущностей. Можно перелопатить формат.

vsb>Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают.

А в чем проблема отрисовать только из нужных полей? Или из нескольких объектов? Зачем заводить новый класс? Можно сборку вообще вызывать из кода, в котором собрались все нужные данные. Например, scalatags.

vsb>При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность.

А нельзя ее по существующим сущностям + несколько "примитивных" полей разобрать? Зачем классы то на каждый случай плодить?

vsb>А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?

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

vsb>1. Использовать первый класс везде. Возникает опасность, что юзер сможет изменить поле, которое ему менять не положено. С этим можно пытаться бороться...


Зависит от того, кто отвечает за безопасность изменения. Это может быть веб-слой. Может быть слой доступа к данным. Может — общая ответственность (слой доступа предоставляет несколько методов и веб-слой выбирает, какой именно использовать). В случае ответственности веб-слоя я люблю грустно смотреть на свою команду и начинать писать тайпклассы (ага, для DTO ).

case class User[F[_]](name: F[String], surname: F[String], login: F[String], password: F[String], age: F[Int])
type Identity[T] = T

trait UserStore {
  def createUser(u: User[Identity]): Int
  def getUser(id: Int): User[Idenity]
  def storeUser(id: Int, u: User[Identity]): Unit
  def updateUser(id: Int, u: User[Option]): Unit
}


В этом случае create/get/store работают со всеми полями. А вот updateUser обновляет только опциональные поля, установленные во втором параметре. Веб-слой при этом знает, какие параметры нужно парсить из запроса, а какие — нет.

vsb>2. Писать новый класс для каждого юз-кейса. Как их называть?

По use-case и называть. UserDelta, UserUpdateRequest, LoginUpdateRequest, и т.д.

vsb>Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто. Например в Java, где импорты переименовывать нельзя, а полностью специфицированные имена слишком длинны. Делать префиксы? Ну их не хватит на всё.

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

vsb> Как данные копировать из одного такого класса в другой? Рефлексией? Руками скучно и долго.

Зато руками надежно и прозрачно. А с рефлексией вы где-то поле отрефакторите и потом долго будете вычищать "параллельные" классы, где поле переименовалось. Если же не плодить классы а сразу писать/читать из нужного формата, проблем еще меньше.

vsb>3. Не писать классы, а использовать Map<String, Object>. Ну это уже динамическая типизация получается со всеми её минусами.

Вот если вы замените базу на mongo или другое хранилище без схемы, по вышеуказанной логике и придется поступать (у вас User же от схемы базы зависит).

vsb>Как с таким умные люди борются?

Мы боремся примерно так, как описано выше. Сущности — те, которые удобны для передачи между сервисами и вообще отображают некоторые понятия предметной области. При необходимости разбиваются на более мелкие сущности. Для специфичных сценариев можно заводить отдельные классы. Вся сериализация/десериализация (как в веб, так и в базу) — прямо из/в нужные форматы (json/hmtl/sql). Обычно — ручками. Скучно, зато относительно просто и можно менять внутреннее представление как душа пожелает, не заботясь о побочных эффектах. С правами доступа — сложнее, зависит от конкретных приложений. Можно попрбовать разбить на разные DTO (сгруппировав поля по ролям), можно по-разному заполнять наборы полей в веб и т.п. Результат в конце концов будет зависеть от того, что в слое доступа к данным предоставляется.
Re: Как называть одинаковые сущности в разных слоях?
От: Мирный герцог Ниоткуда  
Дата: 17.02.20 20:06
Оценка: 10 (1)
Здравствуйте, vsb, Вы писали:

vsb>Как с таким умные люди борются?


эволюционно (т.е. по мере развития абстракций) это почти всегда путь вида один класс для всех -> один класс + адаптеры -> разные классы + конвертеры на границах слоёв абстракций. И собственно я бы так и начинал, т.е. не городил бы сразу городуши охретекторные, а сделал бы один общий класс/структуру данных, если в каких-то местах сильно критично не сломать структуру — сделал бы в этих местах класс-адаптер, через который шёл бы доступ к структуре, но при этом можно было бы напрямую обратиться к структуре в случае форс-мажора (и эти места соответственно явно были бы видны в коде), ну а когда дизайн уже устоялся (ну или вы его заранее чётко себе представляли), можно ещё более явно провести границы абстракций и выделить уже отдельные структуры для каждого слоя.
нормально делай — нормально будет
Отредактировано 17.02.2020 20:06 Умака Кумакаки . Предыдущая версия .
Re[2]: Как называть одинаковые сущности в разных слоях?
От: bnk СССР http://unmanagedvisio.com/
Дата: 17.02.20 20:57
Оценка: 11 (2)
Здравствуйте, maxkar, Вы писали:

M> Если вам нужен JSON, создавайте JSON. При нормальном API (пример) его создавать проще, чем перекладывать из объекта в объект.


val myJson = Json.parse("""{"a" : 3, "arr" : ["test"], "nested" : {"obj" : 3}})
val a1 = myJson.a.as[Int] // specify the expected type
val a2 : Int = myJson.a // and there is an implicit conversion
val nested = myJson.nested.obj.as[Int] // traverse an object
val v1 : Option[Int] = myJson.nonExistent // reading optional values
val v2 : Option[Int] = myJson.a // also reading optionals
val items = json.arr.as[Seq[JsonValue]].map(_.as[String]) // handling collections
val jsonMap : Map[String, JsonValue] = myJson.nested // object could be treated as maps

val json1 : JsonValue = Json.make(
  "a" -> a1, // put a value
  "b" -> v1, // optionally put a value
  "c" -> items.map(i => i : JsonValue) // put a collection of values
)


Это нормальный api? Лично у меня чуть кровь из глаз не потекла, imho, это больше похоже на энтерпрайз, обнять и плакать..
Просто интересно, каким образом вот это вот может быть лучше нормально (декларативно) объявленных классов?
Отредактировано 17.02.2020 21:05 bnk . Предыдущая версия . Еще …
Отредактировано 17.02.2020 21:04 bnk . Предыдущая версия .
Re[3]: Как называть одинаковые сущности в разных слоях?
От: maxkar  
Дата: 21.02.20 08:15
Оценка:
Здравствуйте, bnk, Вы писали:


bnk>Это нормальный api? Лично у меня чуть кровь из глаз не потекла, imho, это больше похоже на энтерпрайз, обнять и плакать..

bnk>Просто интересно, каким образом вот это вот может быть лучше нормально (декларативно) объявленных классов?

Ок, замечание к документации учел. Там показано слишком много различных стилей использования конверсии типов. Типичный код содержит гораздо меньше конвертаций и указаний типов. В целом он похож на пример с json1. Например, с типами и DTO:

def reconstruct(a1: Int, v1: Option[Int], items: Seq[String]): JsonValue =
  Json.make(
    "a" -> a1,
    "b" -> v1, 
    "c" -> items.map(i => i : JsonValue) 
  )

val myJson = Json.parse("""{"a" : 3, "arr" : ["test"], "nested" : {"obj" : 3}})
val json1 = reconstruct(
  a1 = myJson.a,
  v1 = myJson.nonExistent,
  items = json.arr.as[Seq[JsonValue]].map(_.as[String])
)


Это эквивалент кода из предыдущего примера. Да, с коллекциями есть небольшие сложности, но они известные. Коллекций в среднем меньше 10% полей от всего json, так что особых проблем работа с ними не доставляет (плюс можно добавить конверсий для типичных коллекций).

По сравнению с классами. Одна из больших задач — развязать доменные классы от формата сериализации. Я при разборе могу перегруппировать поля так, как надо. Могу переименовать (бывает, появилось лучшее название для домена а протокол менять сложнее — там клиенты). Могу разобрать несколько разных форматов в один и тот же внутренний объект. Можно, конечно, заводить по классу на каждый протокол и один для внутреннего использования. Но какой при этом будет код перекладывания между объектами? Примерно такой же, как я имею сейчас с json. А использование всякой непрозрачной хрупкой магии для перекладывания — это будет типичный энтерпрайз.

Еще одно преимущество. Я с использованием этого API могу легко и просто парсить discriminated unions (не помню русский термин):

abstract sealed class Figure
final case class Rectangle(width: Int, height: Int) extends Figure
final case class Circle(radius: Int) extends Figure

def toJson(f: Figure): JsonValue =
  f match {
    case Rectangle(w, h) => Json.make(
      "type" -> "rectangle", "width" -> w, "height" -> h)
    case Circle(r) => Json.make(
      "type" -> "circle", "radius" -> r)
  }

def fromJson(v: JsonValue): Figure = 
  v("type").as[String] match {
    case "rectangle" => Rectangle(v.width, v.height)
    case "circle" => Circle(v.radius)
    case other => throw new IOException(s"Unknown figure type ${other}")
  }


Как это будет выглядеть на классах? И давайте сразу предположим, что мы фигурами обмениваемся со внешней системой и поменять формат не можем (т.е. плоский json, значения дискриминатора rectangle или circle а не полные имена классов с namespace).

И есть еще тизер. В экспериментальных версиях библиотеки (на монадах и тайпклассах) я могу без изменения парсера управлять обработкой ошибок. Т.е. можно остановиться на первой ошибке формата (неожиданный тип, отсутствует обязательное поле), а можно — продолжать и собирать максимум для ответа. Клиент парсера определяет, какая информация об ошибках собирается и как представляется (это набор текстовых сообщений, классы для возможности формально описать ошибки, работу с путями/позициями и т.п.). Я очень затрудняюсь делать подобное с типичными энетпрайзными библиотеками, конвертирующими json в классы.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.