Здравствуйте, Cyberax, Вы писали:
Б>>С другой стороны, и persistance тоже не совсем слой (в "старом" понимании). Есть же и другие "слои" в инфраструктуре — слой поиска, слой нотификации, слой интеграции с какой-нибудь внешней системой и т.п. C>Тут тоже есть вопросы. "Слой" — это совершенно неправильное слово для много из этого.
Потому что он так и не называется. В приведенной луковой/гексагональной модели слоями назваются:
— слой доменной области
— слой приложения
— слой инфраструктуры
— слой представления
(их даже видно на той страшной картинке что привел Буравчик, если продраться сквозь всю кучу слов)
в каждом из слоев те или иные части приложения. Persistance относится к слою инфраструктуры.Доменная модель про него не должна знать вообще — она должна существаовать сама по себе. Слой приложения задает требования к Persistance через определение интерфейсов которые ему будут нужны для получения/сохранения данных. В инфраструктуре делается собственно реализация этих интерфейсов и через внедрение зависимостей становится доступным в слое приложения (та самая буква D, да).
На том же слое инфраструктуры есть и например, реализация взаимодействия с какими-нибудь вненшинми сервисами. Она не выше и не ниже persistance, она "наравне", потому один слой.
P.S.
Какие-то вы со Stalker-ом категоричные.
Я всю дорогу писал в анемичном стиле, сейчас пытаюсь все же освоить нормально DDD и надо сказать, в нем что-то есть. При этом сделать его неправильно и получить полный отстой — запросто. Но по опыту могу сказать, что превратить систему на анемичной модели в неподдерживаемую кашу — так же запросто можно. Тут они вполне себе наравне. Точнее, как всегда, все сильно зависит от людей, которые реализуют
SOLID прекрасно себя чувствует и показан к применению в обоих подходах. И так же запросто нарушается разработчиками в обоих походах, в том числе и с особым цинизмом, отчего получаются монстры.
Здравствуйте, Cyberax, Вы писали:
Б>>Т.е. нескольким запросам требуются одни и те же подзапросы. C>"const CommonExpr = ...."
А если запросы в разных частях системы?
Б>>Это когда надо написать запрос на голом SQL. Где такой код хранить, как не в внутри persistance? C>Там, где оно нужно.
Вот я и говорю. По всей системе разбросаны. Потом ищи их...
C>Ну как бы надо термины не изгибать до неузнаваемости, а так и писать: "слой доступа к данным, в котором сосредоточено управление данными". Даже термин есть: DAL (Data Access Layer).
Это мы с тобой называем — слой, по-привычке (UI-BLL-DAL). Но на картинке слои другие — Domain, Application, Infrastructure.
Persistance — один из кусочков слоя инфрастуктуры, наравне с другими, не менее важными, кусочками.
Б>>С другой стороны, и persistance тоже не совсем слой (в "старом" понимании). Есть же и другие "слои" в инфраструктуре — слой поиска, слой нотификации, слой интеграции с какой-нибудь внешней системой и т.п. C>Тут тоже есть вопросы. "Слой" — это совершенно неправильное слово для много из этого. C>В частности, "слой" подразумевает, что под ним (и над ним) что-то есть. Т.е. берём систему без оповещений, добавляем слой и получаем систему с оповещениями. При этом нижележащий слой не знает о системе оповещений.
Вот именно. Это не слои, а адаптеры И все они — часть инфраструктуры, а не дополнительная обертка вокруг системы.
P.S. Предлагаю не спорить что есть слой, а что нет. А рассматривать вопрос по существу.
Здравствуйте, Stalker., Вы писали:
S>И вообще с практической точки зрения вместо того, что-бы забивать себе голову вялотекущими холиварами 10-летней давности куда лучше направить эту энергию на что-то более полезное, в современных системах прорва мест которые надо изучать что-бы сделать хорошую систему — одна только безопасность чего стоит (токены, OAuth), а помимо нее есть микросервисы, REST, CQRS и много других интересных и реально полезных вещей
Люди регрессируют в процедурный стиль программирования, где чувствуют себя комфортно и продуктивно.
Здравствуйте, Cyberax, Вы писали:
C>>>Из собственного опыта — читаем что написано у Фаулера и делаем 100% наоборот. KP>>Этой книги Фаулера я вроде не читал, но мнение очень интересное. Не затруднит 1-2 примера привести? Очень заинтриговал C>Например, пропаганда DTO-объектов,
Здравствуйте, Cyberax, Вы писали:
C>>>Из собственного опыта — читаем что написано у Фаулера и делаем 100% наоборот. KP>>Этой книги Фаулера я вроде не читал, но мнение очень интересное. Не затруднит 1-2 примера привести? Очень заинтриговал C>Например, пропаганда DTO-объектов, богатой доменной модели и мусора типа "стратегий". Совершенно не упомянуто об асинхронной модели, для долгоиграющих операций. Мелочи типа рекомендаций отделять "веб север" от "сервера приложений" можно простить, всё-таки 2006-й год был.
C>Что нельзя простить — совершенно бездарное описание работы удалённых сервисов, оно там в худшем стиле начала 90-х. Почти ни один из его примеров не пройдёт review у меня: нет обработки сетевых ошибок и retry, нет стратегии обеспечения идемпотентности, нет анализа на поведение при ошибках downstream-сервисов и т.д.
C>Нет упоминаний о трассировке и метриках для профилирования. Нет упоминаний о мониторинге и тревожных сигналах. Нет упоминаний о стратегии обеспечения безопасности межсервисных вызовов или о механизмах передачи identity вызывающего.
C>В общем, современная сервисная архитектура — это вообще отдельная и сложная тема. Хм. А не написать ли мне книгу про это?
А какой должна быть современная архитектура? Обязательны ли микросервисы или допустим монолит? Если брать конкретно на примере Java с РСУБД, как должно работать всё от начала до конца? Вот юзер тыкнул в браузере что-то. JavaScript приложение отослало REST запрос. На бэкэнде его кто принимает, Spring MVC? Ты против DTO, т.е. мы не пишем объект, на который маппится JSON, а работаем с ним как с Map<String, ?>? Каким образом работа с базой устроена, тоже просто с Map работаем, без доменной модели? Архитектура это абстрактно, хотелось бы на более конкретных схематических примерах понять, как положено делать. Я, лично, так и не нашёл подхода, который бы меня устраивал, а свой фреймворк писать как-то странно в 2019 году.
Здравствуйте, Vladek, Вы писали:
V>Люди регрессируют в процедурный стиль программирования, где чувствуют себя комфортно и продуктивно.
Люди прогрессируют в процедурный стиль. FTFY.
Здравствуйте, Ikemefula, Вы писали:
KP>>>Этой книги Фаулера я вроде не читал, но мнение очень интересное. Не затруднит 1-2 примера привести? Очень заинтриговал C>>Например, пропаганда DTO-объектов, I>Когда и чем плохи DTO ?
Часто это просто лишняя сущность. Хотя иногда их избежать не получается,.
Здравствуйте, vsb, Вы писали:
C>>В общем, современная сервисная архитектура — это вообще отдельная и сложная тема. Хм. А не написать ли мне книгу про это? vsb>А какой должна быть современная архитектура? Обязательны ли микросервисы или допустим монолит?
Монолит однозначно возможен и желателен. Для большинства приложений это оптимальный выбор.
Я бы сказал, что именно микросервисов не существует. Так как любой микросервис при правильной реализации перестаёт быть "микро", а становится вполне себе "макро".
В целом, сервисная архитектура имеет смысл уже для достаточно больших приложений, над которыми работают десятки человек. По моему опыту, основная польза сервисов в том, что они дают удобную демаркационную линию между разными командами.
vsb>Если брать конкретно на примере Java с РСУБД, как должно работать всё от начала до конца? Вот юзер тыкнул в браузере что-то. JavaScript приложение отослало REST запрос. На бэкэнде его кто принимает, Spring MVC? Ты против DTO, т.е. мы не пишем объект, на который маппится JSON, а работаем с ним как с Map<String, ?>?
Оптимально — сразу отображаем в целевой объект, который мы потом можем записать в базу (через ORM).
Ещё у себя использую такой паттерн:
type SubtaskInfo struct {
Id String
CommandLine []string
...
}
// Структура, которая хранится в БД посредством ORM
type StoredSubtask struct {
SubtaskInfo // "Наследуемся" от SubtaskInfo
RowId string
Version int64
...
}
SubtaskInfo сгенерирован из Swagger-схемы для почти-REST-интерфейса.
Работа с базой — как обычно. В начале запроса открываем транзакцию, пишем/читаем данные через ORM или ручные запросы, в конце запроса фиксируем/откатываем. Без транзакций с NoSQL всё тоже похоже.
Здравствуйте, Cyberax, Вы писали:
C>Оптимально — сразу отображаем в целевой объект, который мы потом можем записать в базу (через ORM).
в серьезных системах так делать ни в коем случае нельзя хотя-бы по причине mass assignment attacks — когда хакер просто дописывает в json скрытое в UI поле, а система автоматически маппит его в базу, подобный подход просто не пройдет аудит безопасности. Ну про очевидные вещи про завязанность REST интерфейса к структуре базы я уж молчу...
Здравствуйте, Stalker., Вы писали:
C>>Оптимально — сразу отображаем в целевой объект, который мы потом можем записать в базу (через ORM). S>в серьезных системах так делать ни в коем случае нельзя хотя-бы по причине mass assignment attacks — когда хакер просто дописывает в json скрытое в UI поле, а система автоматически маппит его в базу, подобный подход просто не пройдет аудит безопасности.
Ну так и пусть дописывает. Надо просто не допускать в объекте "ненужных" полей, или разделять объекты на публичную/непубличную часть. Можно так же использовать рефлексию и допускать изменение только специально аннотированных полей.
Более того, я бы вообще отделял редактируемые пользователем объекты от чувствительных для безопасности объектов. Если у объекта половина полей редактируема, а половина вызывает взрывы в безопасности — это очень плохо.
S>Ну про очевидные вещи про завязанность REST интерфейса к структуре базы я уж молчу...
А оно обычно и так привязано.
Ну и если таки требуется серьёзная развязка, то придётся использовать DTO. Никуда не деться, увы. Но я оставляю это всегда на крайний случай.
Здравствуйте, vsb, Вы писали:
vsb>А какой должна быть современная архитектура? Обязательны ли микросервисы или допустим монолит? Если брать конкретно на примере Java с РСУБД, как должно работать всё от начала до конца? Вот юзер тыкнул в браузере что-то. JavaScript приложение отослало REST запрос. На бэкэнде его кто принимает, Spring MVC? Ты против DTO, т.е. мы не пишем объект, на который маппится JSON, а работаем с ним как с Map<String, ?>? Каким образом работа с базой устроена, тоже просто с Map работаем, без доменной модели? Архитектура это абстрактно, хотелось бы на более конкретных схематических примерах понять, как положено делать. Я, лично, так и не нашёл подхода, который бы меня устраивал, а свой фреймворк писать как-то странно в 2019 году.
Расскажу про свой текущий проект на Go и на Java.
Если говорить про Go, то обычно имеется выделенный контролер (это просто удобно) и бизнес модель/логика, без каких-либо четко выделенных M и V. Поэтому, пришедший через REST запрос ты так или иначе десериализуешь в свою структуру и передаешь дальше в бизнес уровни/компоненты. Сам полученных через REST объект сильно не факт что попадет в итоге базу, скорей всего он будет обработан в каком-то из бизнес компонентов и трансформирован в другую структуру, которую можно записать/считать в/из базы, а сама база k/v с объектами доменной модели. Бизнес представление содержит внутреннее состояние системы и слабо пересекающется с тем, что прилетело через REST.
Интерфейсы в купе с компонентной моделью Go позволяют получить очень чистое разделение по бизнес-компонентам и как следствие иметь легко поддерживаемый код. Скажем так, я подобный подход с Java не фак что решился бы использовать, а тут это нечто по-умолчанию и отлично работает. Ну и, конечно, фокусируешься на принципах SOLID, очень хорошо помогает в итоге и Go, в отличие от подавляющего большинства других языков, подталкивает к следованию этим практикам. Вот к примеру довольно хорошее описание де-факто стандартного подхода в Go (первый комментарий).
При этом у меня есть наглядное подтверждение правильности этого подхода. В команде довольно большая текучка, приходит и уходит как очень высококвалифицированный народ так и посредственный. При этом, проект до сих пор в великолепном, легко поддерживаемом состоянии и малейшие косяки в новых пул-реквестах просто как на ладони. В то же время в параллельной команде есть классический Spring MVC проект сделанный по Фаулеру. Я туда пару раз заглядывал починить по мелочи, и это просто АД и содомия. Нагромождение контроллеров, фабрик и прочего Java-специфического барахла делает бизнес логику совершенно не понятной и сильно размазанной. Кто-куда и зачем сказал – огромная загадка, приходится перелопачивать кучу кода что-бы понять.
Здравствуйте, Cyberax, Вы писали:
C>Ну так и пусть дописывает. Надо просто не допускать в объекте "ненужных" полей, или разделять объекты на публичную/непубличную часть. Можно так же использовать рефлексию и допускать изменение только специально аннотированных полей. C>Более того, я бы вообще отделял редактируемые пользователем объекты от чувствительных для безопасности объектов. Если у объекта половина полей редактируема, а половина вызывает взрывы в безопасности — это очень плохо.
публичная/непубличная часть зависит от контекста, у администратора может быть право править определенное поле, а у юзера нет. Плюс DTO поля зачастую требуется чистить и приводить в нормализованный вид для последующей обработки в бизнес логике, пропускать напрямую пришедшие извне данные в любом случае нельзя, да и миллион других причин есть их использовать. Юзать рефлексию для таких вещей куда сильнее запутывает код и усложняет сопровождение
Здравствуйте, Stalker., Вы писали:
C>>Более того, я бы вообще отделял редактируемые пользователем объекты от чувствительных для безопасности объектов. Если у объекта половина полей редактируема, а половина вызывает взрывы в безопасности — это очень плохо. S>публичная/непубличная часть зависит от контекста, у администратора может быть право править определенное поле, а у юзера нет.
И вот не надо так делать, по возможности. Очень это хрупко.
S>Плюс DTO поля зачастую требуется чистить и приводить в нормализованный вид для последующей обработки в бизнес логике,
пропускать напрямую пришедшие извне данные в любом случае нельзя, да и миллион других причин есть их использовать.
Вот как раз единая валидация для бизнес-логики и REST-интерфейса — это залог умственного здоровья при сопровождении. Если у объекта есть какой-то инвариант, то что мешает его проверять одинаково везде?
Насчёт только частей объектов — нынче цветёт и пахнет GraphQL, который этим занимается. Мне он не особо нравится по идеологическим причинам, но вопросы передачи только кусков объектов он решает.
Здравствуйте, Stalker., Вы писали:
S>Здравствуйте, Cyberax, Вы писали:
C>>Оптимально — сразу отображаем в целевой объект, который мы потом можем записать в базу (через ORM).
S>в серьезных системах так делать ни в коем случае нельзя хотя-бы по причине mass assignment attacks — когда хакер просто дописывает в json скрытое в UI поле, а система автоматически маппит его в базу, подобный подход просто не пройдет аудит безопасности.
А что мешает ручками прописать что и когда можно мапить, что нет? Зачем для этого DTO создавать?
S>Ну про очевидные вещи про завязанность REST интерфейса к структуре базы я уж молчу...
Все ORM давно умеют внутри делать свой мапинг.
Здравствуйте, Cyberax, Вы писали:
C>И вот не надо так делать, по возможности. Очень это хрупко.
везде так делается, блокировка пользователя/карт/счетов, пароли и масса всего другого. Нет в этом ничего хрупкого, все как раз наоборот, по другому собственно и не получится, уровни доступа и сами данные это разные вещи
C>Вот как раз единая валидация для бизнес-логики и REST-интерфейса — это залог умственного здоровья при сопровождении. Если у объекта есть какой-то инвариант, то что мешает его проверять одинаково везде?
то, что фильтрация мусора и нормализация ввода не имеет отношения к бизнес логике, номер телефона +7 (343) 456 987, так-же как и 7343 45 69 87 допустимы на входе в сервис, но в бизнес-логику и базу пойдет 7343456787 с ограничением на длину и допустимые символы. То-же самое с авторизацией, ну не может пользователь сам себя разблокировать или сбросить пароль, к формату хранения авторизация не может иметь отношения
Здравствуйте, Stalker., Вы писали:
S>Здравствуйте, gandjustas, Вы писали:
G>>А что мешает ручками прописать что и когда можно мапить, что нет? Зачем для этого DTO создавать?
S>затем, что проще и удобнее создать DTO, чем "прописать ручками", прилепить "специальные" аннотации и рефлексию что-бы получилось "прямо как с DTO"
Это наверное зависит от инструмента. В ASP.NET можно одним атрибутом сказать что можно мапить, а что нельзя. Это гораздо проще, чем создавать DTO, которое потом еще и валидировать надо отдельно.
В целом настройка мапинга форм на объекты должна быть отдельным концептом, а не выражаться через дополнительные сущности — dto.
Здравствуйте, Stalker., Вы писали:
C>>И вот не надо так делать, по возможности. Очень это хрупко. S>везде так делается, блокировка пользователя/карт/счетов, пароли и масса всего другого. Нет в этом ничего хрупкого, все как раз наоборот, по другому собственно и не получится, уровни доступа и сами данные это разные вещи
Ну вот пусть эти блокировки будут в отдельной таблице, хотя бы. Которая просто недоступна через обычный REST, аналогично с паролями. Ну а номера карт вообще должны быть в отдельной БД по правилам PCIDSS.
Если их располагать всё в одном объекте, то при одной ошибке будем иметь очередной пресс-релиз: "Компания Рога&Копыта утекла данные 100500 пользователей из-за хакерской атаки".
Ну и цепочка размышлений такая:
1. Если делать интерфейс типа "setUserData(UserDTO userData)", то надо проверять, что только администратор может редактировать определённые поля.
2. Но это очень плохо, так как слишком хрупко и неудобно.
3. Почему бы тогда не выделить отдельный метод "setSensitiveUserData(SensitiveUserDTO userData)", который будет доступен только администраторам?
4. Хм. А почему бы тогда и не выделить SensitiveUserData в отдельный объект?
C>>Вот как раз единая валидация для бизнес-логики и REST-интерфейса — это залог умственного здоровья при сопровождении. Если у объекта есть какой-то инвариант, то что мешает его проверять одинаково везде? S>то, что фильтрация мусора и нормализация ввода не имеет отношения к бизнес логике, номер телефона +7 (343) 456 987, так-же как и 7343 45 69 87 допустимы на входе в сервис
Зачем? Пусть REST-сервис сразу проверяет на нужный формат (7343456787). Frontend'у никто не мешает телефон отформатировать заранее — он и так это обязан будет делать хотя бы для отображения, так как перевод "7343456787" в "+7 (343) 456 987" кто-то должен делать.
Если нормализацию делать ещё и в REST-слое, то будем иметь мёртвый код, который не будет использоваться и будет бомбой замедленного действия.
S>но в бизнес-логику и базу пойдет 7343456787 с ограничением на длину и допустимые символы. То-же самое с авторизацией, ну не может пользователь сам себя разблокировать или сбросить пароль, к формату хранения авторизация не может иметь отношения
Аутентификация — это как раз один из примеров, где REST-интерфейс один фиг не подходит.
Здравствуйте, Cyberax, Вы писали:
C>Ну вот пусть эти блокировки будут в отдельной таблице, хотя бы. Которая просто недоступна через обычный REST, аналогично с паролями. Ну а номера карт вообще должны быть в отдельной БД по правилам PCIDSS. C>Если их располагать всё в одном объекте, то при одной ошибке будем иметь очередной пресс-релиз: "Компания Рога&Копыта утекла данные 100500 пользователей из-за хакерской атаки".
номера карточек должны быть зашифрованы, а не вынесены в отдельную базу, другое дело что база самих клиентов с паролями обычно в другой базе по несвязанным с секьюрными требованиями причинам
C>Ну и цепочка размышлений такая: C>1. Если делать интерфейс типа "setUserData(UserDTO userData)", то надо проверять, что только администратор может редактировать определённые поля. C>2. Но это очень плохо, так как слишком хрупко и неудобно. C>3. Почему бы тогда не выделить отдельный метод "setSensitiveUserData(SensitiveUserDTO userData)", который будет доступен только администраторам? C>4. Хм. А почему бы тогда и не выделить SensitiveUserData в отдельный объект?
и это называется "нехрупкий" подход? Есть миллион разных вариантов того кто, что и при каких обстоятельствах может править, они еще и меняться могут по ходу работы в виде добавления новых ролей и прочего, заложить это в структуре базы просто недостижимо, не говоря уже про то, какой ненормализованный монстр получится в итоге, адрес клиента например может меняться как оператором или админом, так и самим клиентом, а вот снятие блокировки только админом, эта логика не имеет отношения к структуре обьекта Клиент
C>Зачем? Пусть REST-сервис сразу проверяет на нужный формат (7343456787). Frontend'у никто не мешает телефон отформатировать заранее — он и так это обязан будет делать хотя бы для отображения, так как перевод "7343456787" в "+7 (343) 456 987" кто-то должен делать.
тогда знание о валидации и нормализации бизнес обьектов тонким слоем расползется по всем клиентам плюс будет продублировано в бизнес логике. Даже тот-же номер телефона может представлять проблему — например в интерфейсе для резидентов код страны может не указываться, а уж строка адреса и вообще перетащит половину логики валидации на клиента.
C>Аутентификация — это как раз один из примеров, где REST-интерфейс один фиг не подходит.
авторизация, не аутентификация. И делается она именно что в REST интерфейсе, а никак не в бизнес логике
Здравствуйте, gandjustas, Вы писали:
G>Это наверное зависит от инструмента. В ASP.NET можно одним атрибутом сказать что можно мапить, а что нельзя. Это гораздо проще, чем создавать DTO, которое потом еще и валидировать надо отдельно.
маппить поле блокировки можно (и нужно), но только для админа