Предположим, есть сущность User. Для работы с БД используется слой доступа к данным. Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД. Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению. Скорей всего часть этих полей будет совпадать с полями для БД, но вряд ли на 100%. Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают. При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность. А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?
1. Использовать первый класс везде. Возникает опасность, что юзер сможет изменить поле, которое ему менять не положено. С этим можно пытаться бороться...
2. Писать новый класс для каждого юз-кейса. Как их называть? Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто. Например в Java, где импорты переименовывать нельзя, а полностью специфицированные имена слишком длинны. Делать префиксы? Ну их не хватит на всё. Как данные копировать из одного такого класса в другой? Рефлексией? Руками скучно и долго.
3. Не писать классы, а использовать Map<String, Object>. Ну это уже динамическая типизация получается со всеми её минусами.
Как с таким умные люди борются?
Re: Как называть одинаковые сущности в разных слоях?
Здравствуйте, vsb, Вы писали:
vsb>Предположим, есть сущность User. Для работы с БД используется слой доступа к данным. Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД. Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению. Скорей всего часть этих полей будет совпадать с полями для БД, но вряд ли на 100%. Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают. При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность. А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?
vsb>1. Использовать первый класс везде. Возникает опасность, что юзер сможет изменить поле, которое ему менять не положено. С этим можно пытаться бороться...
Очевидно, что этот вариант. А пользовательский интерейс должен быть спроектирован таким образом, чтобы пользователь не смог ничего лишнего сделать.
В любом случае смотрите на DTO и в зависимости от ситуации, убирайте или добавляйте соотв. поля.
Т.е. сущность должна быть одна, максимально общая, но для отображения должна настраиваться соотв. образом (сервисом).
Кодом людям нужно помогать!
Re: Как называть одинаковые сущности в разных слоях?
Здравствуйте, vsb, Вы писали:
vsb>2. Писать новый класс для каждого юз-кейса. Как их называть?
Называть User и класть в разные namespaces / packages. Делать using / import там, где надо использовать.
> Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто.
Если вам часто требуется одновременно использовать User из разных неймспейсов в одном и том же куске кода, вы что-то другое делаете и описание вашей задачи другое. Обычно эти куски кода разделены, а куски где один User превращается в другой, изолированы и полная спецификация имени класса там проблем не вызывает
> Например в Java, где импорты переименовывать нельзя
ну вот в Java нельзя, в Java писать больше придется. В других языках можно, там меньше писать.
Re: Как называть одинаковые сущности в разных слоях?
Здравствуйте, vsb, Вы писали:
vsb>Предположим, есть сущность User. Для работы с БД используется слой доступа к данным. Для представления этого User-а логично создать класс User с полями, соответствующими столбцам в таблице БД. Для обмена данными с мобильным приложением используется JSON. Логично создать класс User с полями, которые нужны этому мобильному приложению. Скорей всего часть этих полей будет совпадать с полями для БД, но вряд ли на 100%. Для отрисовки HTML мы во View передаём опять же класс User (предположим, мы не пользуемся новомодными SPA, а пишем по старинке, с отрисовкой на сервере). Опять же эти поля вряд ли на 100% совпадают. При изменении пользователя нам приходит форма. В этой форме опять же поля от класса User. Т.е. это уже 4-я сущность. А ещё разные REST-методы и формы могут требовать разный набор полей. Как со всем этим поступать?
User — объект доменной модели (ну или данных, если дата-драйвен-дизайн), UserViewData для отображения данных в представлении.
Данные для мобильного интерфейса, веб интерфейса и т.д. не должны пересекаться в рамках одного класса, а то и сборки, так что нормально что разные интерфейсы имеют одинаково названный UserViewData
В одном интерфейсе, однако, может быть несколько использований данных по пользователе. Например списки с краткой информацией или страница подробных деталей. Тут или детальнее именовать (UserListItemViewData) либо использовать вложенные классы (UserListViewData внутри имеет класс RowViewData поскольку используется только в рамках этого списка)
Re: Как называть одинаковые сущности в разных слоях?
Здравствуйте, vsb, Вы писали:
vsb>1. Использовать первый класс везде. Возникает опасность, что юзер сможет изменить поле, которое ему менять не положено. С этим можно пытаться бороться...
vsb>2. Писать новый класс для каждого юз-кейса. Как их называть? Ведь очевидно, что в части классов придётся работать с несколькими классами User сразу и можно просто запутаться, кто есть кто. Например в Java, где импорты переименовывать нельзя, а полностью специфицированные имена слишком длинны. Делать префиксы? Ну их не хватит на всё. Как данные копировать из одного такого класса в другой? Рефлексией? Руками скучно и долго.
Пункт 1 в принципе рабочий вариант, пока возможен. проблемы начнутся например если объекты на фронтенде сильно отличаются от объектов в базе. Или если есть разница, кто что (какие поля) должен видеть.
Тут ещё есть такая штука, что классы для базы могут быть автогенереные, без поддержки каких-либо настроек полей. или например какой-то "слой" требует чтобы все дата-классы от чего-то наследовались..
В общем когда этот вариант становится слишком громоздким и неподдерживаемым, переходим ко второму пункту..
Для пункта 2 придумали всякие мапперы, в том числе и не через рефлекшен (см automapper например), неймспейсы тоже вполне себе, если сделать префиксы. Больше двух версий одной сущностив одном файле встречаться вроде бы не должно..
Re: Как называть одинаковые сущности в разных слоях?
Здравствуйте, 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: Как называть одинаковые сущности в разных слоях?
Здравствуйте, 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: Как называть одинаковые сущности в разных слоях?
Здравствуйте, 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: Как называть одинаковые сущности в разных слоях?
Здравствуйте, vsb, Вы писали:
vsb>Как с таким умные люди борются?
эволюционно (т.е. по мере развития абстракций) это почти всегда путь вида один класс для всех -> один класс + адаптеры -> разные классы + конвертеры на границах слоёв абстракций. И собственно я бы так и начинал, т.е. не городил бы сразу городуши охретекторные, а сделал бы один общий класс/структуру данных, если в каких-то местах сильно критично не сломать структуру — сделал бы в этих местах класс-адаптер, через который шёл бы доступ к структуре, но при этом можно было бы напрямую обратиться к структуре в случае форс-мажора (и эти места соответственно явно были бы видны в коде), ну а когда дизайн уже устоялся (ну или вы его заранее чётко себе представляли), можно ещё более явно провести границы абстракций и выделить уже отдельные структуры для каждого слоя.
Здравствуйте, 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, это больше похоже на энтерпрайз, обнять и плакать..
Просто интересно, каким образом вот это вот может быть лучше нормально (декларативно) объявленных классов?
bnk>Это нормальный api? Лично у меня чуть кровь из глаз не потекла, imho, это больше похоже на энтерпрайз, обнять и плакать.. bnk>Просто интересно, каким образом вот это вот может быть лучше нормально (декларативно) объявленных классов?
Ок, замечание к документации учел. Там показано слишком много различных стилей использования конверсии типов. Типичный код содержит гораздо меньше конвертаций и указаний типов. В целом он похож на пример с json1. Например, с типами и DTO:
Это эквивалент кода из предыдущего примера. Да, с коллекциями есть небольшие сложности, но они известные. Коллекций в среднем меньше 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 в классы.