Здравствуйте, ·, Вы писали:
·>Отсутствие гибкости. Rest — это синхронный p2p request-response только. Притом с большим оверхедом в виде http обёртки. ·>Сообщения — это кто угодно с кем угодно, streaming, множество отправителей/получателей и т.п. В MSA без такого трудно обойтись.
Вот с чем никогда в жизни не работал — так это с описываемой вами архитектурой.
Как в ней решаются вопросы
1. Согласованности: чтобы вся эта слабосвязанная мешанина реально делала то, что нужно?
2. Зависимостей — как мне понять, сколько сервисов нужно запустить, чтобы реализовался сценарий X? Чтобы не получилось, как в анекдоте: "один копает ямы, а другой закапывает; ещё один должен был туда деревья вставлять, но он не пришёл".
3. Предотвращения лайв-локов: один сервис ждёт с шины сообщение А, потом отправит сообщение Б. Другой сервис как раз ждёт сообщение Б, чтобы отправить А
Если есть какая-то книжка на эту тему — отправьте в неё, пожалуйста
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: Помогите правильно спроектировать микросервисное при
Здравствуйте, Sinclair, Вы писали:
S>Вот с чем никогда в жизни не работал — так это с описываемой вами архитектурой.
В такой архитектуре, думают не через сценарии, а через доменные сущности, их состояния и переходы между ними. Сквозные сценарии либо реализуются сами собой, в том числе и те, о которых бизнес аналитики даже и не подозревали, либо путем создания дополнительных доменных сущностей по общему правилу:
1. Сервис отвечает за свои, и только свои, доменные объекты и является для них единственным источником правды
2. Сервис принимает команды на изменение состояния своих объектов
3. Сервис отправляет нотификации о ВСЕХ изменениях ВСЕХ своих объектов независимо от того, нужна кому-то эта информация или не нужна
4. Сервис может подписываться только на нотификации об изменениях в других сервисах и только на команды, адресованные непосредственно этому сервису. Нельзя подписаться на команды другого сервиса.
S>1. Согласованности: чтобы вся эта слабосвязанная мешанина реально делала то, что нужно?
В рамках доменной области у тебя всегда будет eventual consistency, до той степени, какую обеспечивает твоя доменная модель. Реальный мир устроен схожим образом: большинство бизнес процессов могут прерваться на середине и в этом нет ничего страшного.
S>2. Зависимостей — как мне понять, сколько сервисов нужно запустить, чтобы реализовался сценарий X? Чтобы не получилось, как в анекдоте: "один копает ямы, а другой закапывает; ещё один должен был туда деревья вставлять, но он не пришёл".
Никак. Тебе как разработчику нужно понять какой последовательности изменений состояния доменных сущностей соответствует твой сценарий, либо, если это невозможно, добавить новые доменные сущности. Ровно как в REST, если изменение не укладывается в CRUD над имеющимися ресурсами, ты добавляет новый ресурс в который изменение укладывается.
Ситуации в духе "но тот, который деревья сажает сегодня не пришел" для такой архитектуры скорее норма, чем исключение.
S>3. Предотвращения лайв-локов: один сервис ждёт с шины сообщение А, потом отправит сообщение Б. Другой сервис как раз ждёт сообщение Б, чтобы отправить А
Это не RPC, сервис ничего не ждёт. Сервис отправляет запрос на изменение и забывает о нем. Если сервису важно, чтобы запрос отработал в за какой-то тайм-аут, он проверяет изменения спустя какое-то время и если изменений не произошло, шлет команду на отмену.
Пример из соседнего поста: есть пользователи, есть туры, нужно рекламировать пользователям туры, в которые они могли бы поехать, но ещё не ездили.
Рассылки это новый домен относительно пользователй и туров, значит нужен новый сервис. Этому сервису необходимы пользователи, туры и поездки, значит подписываемся на изменения в трёх доменах. От пользователей храним е-мейл и возраст, от тура храним описание, от поездки храним сам факт связи пользователя и тура. Все это складываем в контекст и из контекста извлекаем что кому рекомендовать. При рассылке отправляем нотификации: создана рассылка для пользователя X приглашающая в туры {Y}. И такая система получается очень гибкой и расширяемой. Если нам нужно поменять правила рассылки, мы меняем один сервис. Если нужны новые данные, добавляем подписки. Если нужно действие после рассылки, добавляем нового подписчика. И т.п.
Разумеется, у такой архитектуры полно недостатков: нужен глубокий анализ домена, нужно бить по рукам тем, кто пытается домен ломать, полная схема взаимодействий становится очень сложной и превращается в комбинаторную стейт машину через которую практически невозможно протащить единичный сценарий, авторизация становится нетривиальным занятием. Но зато можно поддерживать небольшой командой в 10-15 человек несколько сотен микросервисов почти не напрягаясь.
S>Если есть какая-то книжка на эту тему — отправьте в неё, пожалуйста
Здравствуйте, Sinclair, Вы писали:
S>Вот с чем никогда в жизни не работал — так это с описываемой вами архитектурой. S>Как в ней решаются вопросы S>1. Согласованности: чтобы вся эта слабосвязанная мешанина реально делала то, что нужно? S>2. Зависимостей — как мне понять, сколько сервисов нужно запустить, чтобы реализовался сценарий X? Чтобы не получилось, как в анекдоте: "один копает ямы, а другой закапывает; ещё один должен был туда деревья вставлять, но он не пришёл". S>3. Предотвращения лайв-локов: один сервис ждёт с шины сообщение А, потом отправит сообщение Б. Другой сервис как раз ждёт сообщение Б, чтобы отправить А
S>Если есть какая-то книжка на эту тему — отправьте в неё, пожалуйста
Как у Вас это: M>Разумеется, у такой архитектуры полно недостатков: нужен глубокий анализ домена, нужно бить по рукам тем, кто пытается домен ломать, полная схема взаимодействий становится очень сложной и превращается в комбинаторную стейт машину через которую практически невозможно протащить единичный сценарий, авторизация становится нетривиальным занятием.
сочетается с этим: M> Но зато можно поддерживать небольшой командой в 10-15 человек несколько сотен микросервисов почти не напрягаясь.
?
Здравствуйте, Sinclair, Вы писали:
S>Это заблуждение. Авторизация проверяется совершенно отдельно. Ещё не хватало в представлении ресурса показывать разный набор ссылок в зависимости от transient штуки вроде Bearer-токена.
Можно это как-то обосновать? Мне кажется, мы о разных вещах говорим. Есть ресурс, есть набор связанных ресурсов с которыми можно проделывать какие-то операции. HATEOS позволяет НЕ ВЫЗЫВАЯ операций предсказать, будут они доступны клиенту или нет. Например, в списке сотрудников рядом с именем Sinclair либо показывать кнопку "объявить выговор" или не показывать. И чтобы это реализовать без HATEOAS часть логики авторизации, кому можно налагать взыскания и на кого, вынужденно скопируется из сервиса в его клиента. А поскольку мы сейчас обсуждаем микросервисную архитектуру, где акторами являются не только пользователи, но и другие сервисы, эта логика начинает неконтролируемо расползаться по системе. Как в примере из соседнего поста, когда сервис туров следит чтобы определенные туры были доступны только совершеннолетним пользователям и эта же логика дублируется в сервисе рекомендаций чтобы не показывать несоврешеннолетним то, что им и так недоступно. HATEOAS бы эту проблему решил докинув ресурс user список доступных для него туров.
S>Да, пейджинг — это один из немногих случаев корректного и полезного применения ссылок. Но вообще, его изготовить правильно — очень сложно. Сильно сложнее, чем кажется на первые три взгляда. В частности, "список страниц, которые можно дёрнуть" — плохая идея, которая ломается в большом количестве сценариев.
Опять же, можешь раскрыть тему? Пейджинг это простая штука если перестать думать о нем, как о RPC и начать думать как об отдельном ресурсе. Ты СОЗДАЕШЬ отдельный объект "поисковый запрос" и в ответ получаешь список страниц с результатами этого запроса. И, внезапно, у тебя уже нет проблем со стабильностью, сортировки, изменению состава страниц, кэшированию и т.п. Результат фиксируется в момент создания запроса и далее до нового поиска уже не меняется.
S>В частности, приделывание к платёжке ссылки refund с методом POST — это RMM не L3, а L1. Потому, что нормальный способ — это PUT либо PATCH, которые прямым либо косвенным образом меняют состояние платежа на refunded.
Нет. POST refund это создание ДРУГОГО ресурса. Другого потому что у него другой URI. Нужно думать о рефанде не как об изменении платежа, а как ос создании нового объекта — сторнирующей операции, потому что сам платеж изменять не позволяет принцип двойной бухгалтерии известный с 17 века: сделанная проводка не может быть удалена.
S>Конечно позволяет. Вот у вас есть объект, у него есть представление. Всё, можно делать PUT этого представления. Если что-то запрещено бизнес-правилами — приедет 409. Если правами доступа — 403.
Это требует проделать вызов. В микросервисной архитектуре такой подход означает что сценарий клиента может развалиться в середине выполнения и, как следствие, для каждого сложного сценария потребуется делать распределенные транзакции с откатом. Так, конечно, можно делать, но это очень дорого. Поэтому в большинстве случаев ты сперва делаешь пречек собирая все доступные ресурсы, проверяешь пререквизиты и только потом начинаешь выполнять изменения. Это опять же сильно упрощает логику.
S>Ну, у нас же нет REST-полиции. Никто вас не арестует за такую реализацию (и за любую другую). Но на практике людям больше нравятся сервисы, которые не требуют от них делать лишние запросы, увеличивая трафик и латентность.
Людям вообще нравится RPC, даже по этой теме видно. Но RPC не дает необходимой для микросервисов изоляции и довольно быстро превращает приложение в распределенный монолит.
Здравствуйте, TG, Вы писали:
TG>API приложения полностью зависит от нужд потребителя. Договариваться придётся.
Дизайн от сценариев лишь один из возможных подходов. Если ты дизайнишь API в расчете на пользователя, пользователь, конечно рад, но при каждом изменении сценариев потребителя тебе приходится подстраивать под них свое API, а если потребителей много то еще и увязывать их сценарии между собой. В результате ты делаешь кучу ненужной работы, переусложняешь свой сервис и в конце концов обнаруживаешь себя внутри распределенного монолита.
Альтернатива — дизайн от доменной модели, когда ты определяешь доменные сущности, их состояния и переходы между ними и выставляешь полное API для манипулирования этими сущностями не беспокоясь о том, нужно кому-то это прямо сейчас или нет. Это позволяет клиентам твоего сервиса реализовать любой сценарий не дергая тебя и не требуя изменений твоего API. Это и есть полноценная микросервисная архитектура.
Здравствуйте, Miroff, Вы писали:
TG>>API приложения полностью зависит от нужд потребителя. Договариваться придётся.
M>Дизайн от сценариев лишь один из возможных подходов. Если ты дизайнишь API в расчете на пользователя, пользователь, конечно рад, но при каждом изменении сценариев потребителя тебе приходится подстраивать под них свое API, а если потребителей много то еще и увязывать их сценарии между собой. В результате ты делаешь кучу ненужной работы, переусложняешь свой сервис и в конце концов обнаруживаешь себя внутри распределенного монолита.
Почему ненужной? Пользователи/клиенты же просили.
И если клиент готов платить, почему не делать?
А если мы не можем удобно увязать сценарии разных клиентов, то, может, им просто нужны разные сервисы и это мы не правы, пытаясь скрестить ужа с ежом?
M>Альтернатива — дизайн от доменной модели, когда ты определяешь доменные сущности, их состояния и переходы между ними и выставляешь полное API для манипулирования этими сущностями не беспокоясь о том, нужно кому-то это прямо сейчас или нет. Это позволяет клиентам твоего сервиса реализовать любой сценарий не дергая тебя и не требуя изменений твоего API. Это и есть полноценная микросервисная архитектура.
Что Вы понимаете под доменной моделью?
И что значит "полное API"?
Здравствуйте, TG, Вы писали:
TG>И если клиент готов платить, почему не делать?
Если цель доить заказчиков, это отличный вариант. Именно так и получаются команды в 400 разработчиков для поддержки небольшой региональной логистической системы.
TG>Что Вы понимаете под доменной моделью?
Здравствуйте, Sharov, Вы писали:
S>·>Отсутствие гибкости. Rest — это синхронный p2p request-response только. Притом с большим оверхедом в виде http обёртки. S>·>Сообщения — это кто угодно с кем угодно, streaming, множество отправителей/получателей и т.п. В MSA без такого трудно обойтись. S>А что мешает его сделать асинхронным?
Спека http. Я наверное неточно выразился, я имел в виду, что на каждый запрос приходит ответ, который надо прочитать, даже если это "204 No Content". Притом ответ ровно один.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Miroff, Вы писали:
TG>>И если клиент готов платить, почему не делать? M>Если цель доить заказчиков, это отличный вариант. Именно так и получаются команды в 400 разработчиков для поддержки небольшой региональной логистической системы.
Пусть об этом голова у менеджмента болит.
TG>>И что значит "полное API"? M>Возможность достигнуть любого состояния системы посредством API. CRUD на все доменныек сущности плюс переходы по всем возможным состояниям.
Так сервис может не ограничиваться CRUD-ом и состояниями.
Построение маршрута там же Яндекс.Карты, например.
Какое там "полное API" для всех клиентов можно выкатить?
Здравствуйте, TG, Вы писали:
TG>Так сервис может не ограничиваться CRUD-ом и состояниями. TG>Построение маршрута там же Яндекс.Карты, например. TG>Какое там "полное API" для всех клиентов можно выкатить?
Посмотри на API OpenStreetMap, версия 0.7 вышла 15 лет назад когда, еще JSON не изобрели, и с тех пор ее не апдейтили потому что и так хорошо вышло. Так бывает, когда API проектируют инженеры, а не менеджеры.
Здравствуйте, Miroff, Вы писали:
TG>>Так сервис может не ограничиваться CRUD-ом и состояниями. TG>>Построение маршрута там же Яндекс.Карты, например. TG>>Какое там "полное API" для всех клиентов можно выкатить?
M>Посмотри на API OpenStreetMap, версия 0.7 вышла 15 лет назад когда, еще JSON не изобрели, и с тех пор ее не апдейтили потому что и так хорошо вышло. Так бывает, когда API проектируют инженеры, а не менеджеры.
Это не означает, что все довольны.
Вот, допустим, я заказчик/клиент Яндекс.Карт, OSM и т.д.
Задача: построить маршрут с условиями:
— маршрут не должен проходить по дорогам общего пользования (в том числе грунтовым) и вообще не должен приближаться к ним ближе, чем на 5 км.
— если без пересечения дорог общего пользования не обойтись, то пересечение маршрута с ними должно идти под углом, близким к прямому
— нельзя прокладывать маршрут по: болотам, лесам, возвышенностям
— нельзя прокладывать маршрут по определенным зонам, задаваемым пользователем в запросе.
Какое готовое API известных сервисов картографии такое уже умеет?
·>Сборка, деплой и перезапуск мелкого сервиса занимает минуты от момента мержа PR. Тогда как типичная выкатка монолита — приятно проведённые выходные.
Беда приходит тогда, когда требуется поменять код, общий для многих/всех микросервисов. Например, добавить (m)TLS, или еще что-то такое. Любой инфраструктурный проект в МСА вызывает многолетнюю попаболь, и приводит к жутчайшим штуковинам типа Istio и прочим service-mesh'ам.
Здравствуйте, Sharov, Вы писали:
S>А в чем проблема взаимодействовать по REST'у?
В том, что его применяют там, где он не нужен. У меня в последнее время есть сомнения что народ в массе понимает что такое REST и зачем он нужен.
Здравствуйте, Miroff, Вы писали:
M>Можно это как-то обосновать?
Можно. Когда я делаю GET на ресурс, я получаю его представление. Крайне желательно, чтобы это представление зависело от состояния ресурса, а не от состояния пользователя, который его получает.
Например, можно воткнуть reverse proxy перед сервером приложения, и тогда он сможет снять значительную часть нагрузки с сервера.
Как только мы начинаем менять представление объекта в зависимости от пользователя, о кэшировании можно забыть.
M>Мне кажется, мы о разных вещах говорим. Есть ресурс, есть набор связанных ресурсов с которыми можно проделывать какие-то операции.
Тут всё верно. Ресурсы обычно связаны друг с другом не искусственными линками, а естественным образом, спрятанным в представлении.
Возьмём, к примеру, ресурс Payment.
Вот как выглядит представление этого ресурса через PayPal API:
Этот ресурс, судя по всему, связан с другим ресурсом — invoice. Но никаких намёков на то, как получить информацию об этом ресурсе, внутри ресурса нет. И линков тоже нет. Зато есть линки про то, как изменить состояние этого же ресурса. Так что это никакой не REST.
В настоящем хардкорном RMM3 REST вместо атрибута invoice_id был бы атрибут invoice со значением "https://www.paypal.com/invoice/details/INVOICE-123".
M>HATEOS позволяет НЕ ВЫЗЫВАЯ операций предсказать, будут они доступны клиенту или нет.
Нет, ничего подобного HATEOAS не предполагает. Нет никакой гарантии, что операции будут доступны. Нет никакой гарантии, что никаких других операций нет.
M>Например, в списке сотрудников рядом с именем Sinclair либо показывать кнопку "объявить выговор" или не показывать. И чтобы это реализовать без HATEOAS часть логики авторизации, кому можно налагать взыскания и на кого, вынужденно скопируется из сервиса в его клиента.
Нет конечно. Зачем? Это крайне контрпродуктивная идея.
Во-первых, "объявление выговора" в REST-системе — это не операция с "сотрудником с именем Синклер". Это создание ресурса "выговор". А точнее — создание ресурса "приказ об объявлении выговора".
В котором могут фигурировать, к примеру, несколько сотрудников. И права на создание этого выговора весьма косвенно связаны с перечисленными там сотрудниками.
Во-вторых, построение безопасности путём скрытия действий в UI — это популярная, но плохая идея. В основном — потому, что она неимоверно бесит пользователей. Вот я сижу перед списком пользователей, хочу объявить выговор. Поискал в меню — нет такой команды. Поискал в свойствах сотрудника — нет такой кнопки. Полез в инструкцию — сказано "для объявления выговора нажмите на кнопку "Объявить выговор". И вот я уже полчаса думаю, то ли я дурак, то ли инструкция устарела, то ли программа глючит. Внезапно, "некрасивый" способ — дать мне нажать на кнопку, а потом вывести "403 у вас недостаточно прав для объявления выговора этому сотруднику" — в разы гуманнее по отношению к пользователю, чем предлагаемая вами идея.
M>А поскольку мы сейчас обсуждаем микросервисную архитектуру, где акторами являются не только пользователи, но и другие сервисы, эта логика начинает неконтролируемо расползаться по системе.
Ничего никуда не расползается. Зачем? Микросервис просто делает свою работу. Поймите, нельзя строить микросервис на том, что "я прочитал ресурс — если нет ссылки, значит нельзя рефандить и надо идти по ветке А, а если ссылка есть — то надо идти по ветке Б и выполнять рефанд". Возможно, настройки прав или ешё что-то поменялось между чтением ресурса и переходом по ссылке; всегда можно получить в ответ "403". Так что вместо вот этой вот двухшаговой наркомании "если ссылка, то идём по ветке Б, и если получили 403, то переключаемся обратно на ветку А". Просто идём и пытаемся сделать рефанд, не делая никаких предположений о правах там и всём остальном.
M>Как в примере из соседнего поста, когда сервис туров следит чтобы определенные туры были доступны только совершеннолетним пользователям и эта же логика дублируется в сервисе рекомендаций чтобы не показывать несоврешеннолетним то, что им и так недоступно. HATEOAS бы эту проблему решил докинув ресурс user список доступных для него туров. ,
Это очень, очень плохая архитектура. В первую очередь потому, что вы инвертируете зависимости. Теперь у вас ресурс user (и его микросервис) обязан знать всё о турах (которые вообще-то обрабатываются другим микросервисом).
А ресурс "тур", ясное дело, должен знать всё о пользователях. Поздравляю, вы получили не просто монолит, а трудноразвиваемый монолит.
M>Опять же, можешь раскрыть тему? Пейджинг это простая штука если перестать думать о нем, как о RPC и начать думать как об отдельном ресурсе. Ты СОЗДАЕШЬ отдельный объект "поисковый запрос" и в ответ получаешь список страниц с результатами этого запроса. И, внезапно, у тебя уже нет проблем со стабильностью, сортировки, изменению состава страниц, кэшированию и т.п. Результат фиксируется в момент создания запроса и далее до нового поиска уже не меняется.
И это — тоже очень, очень плохая архитектура. Смотрите как это работает: "поисковый запрос" — это эфемерный ресурс. Последнее, что мы хотим — это хранить его. В первую очередь по соображениям масштабирования. Сколько пользователей вы ожидаете ежедневно? Двух? Как только у вас появляется хотя бы несколько сотен тысяч запросов в сутки, хранение (даже в течение ограниченного времени) их эфемерных запросов становится неподъёмным.
Как раз наоборот — пользователь, который получил в ответ на поиск информацию о том, что нашлось 100000 страниц, скорее всего выполнит новый запрос, потому что этот оказался недостаточно точным.
Нет, бывают такие ситуации, когда поиск сам по себе очень дорогой, и мы как отдельную фичу предлагаем "сохранение запросов", да ещё и иногда с оповещениями об изменениях. Но в простых гражданских случаях это не работает, и поиск делается поверх реалтаймовых данных, а не поверх какого-то снапшота, полученного неведомо когда.
И вот в такой обстановке "традиционный" пейджинг в стиле "дай мне позиции с 120 по 139" — тупиковая ветвь проектировщицкой мысли. Единственное его достоинство — простота реализации.
Зато недостатки у него принципиальные — в частности, невозможность восстановить целостную картину.
Есть несколько способов решить эту проблему. Один из них — примерно то, что вы предлагаете, "думать о запросе как об отдельном ресурсе", только правильно реализованный.
Правильно — значит иметь один ресурс "поисковый запрос". Несмотря на его эфемерность, он вполне реален. То есть не два разных ресурса "пользователи на букву А с 0 по 99" и "пользователи на букву А с 100 по 199", а один ресурс "пользователи на букву А". Если этот ресурс слишком велик для прожёвывания его одним запросом, то мы добавляем к запросу range header и тащим комфортными для нас частями.
А непротиворечивость гарантируется тем, что последующие запросы мы дооборудуем хидером if-match/if-none-match, что позволяет нам заметить момент, когда данные на серверной стороне поменялись, и закешированную версию нужно выкинуть.
M>Нет. POST refund это создание ДРУГОГО ресурса. Другого потому что у него другой URI. Нужно думать о рефанде не как об изменении платежа, а как ос создании нового объекта — сторнирующей операции, потому что сам платеж изменять не позволяет принцип двойной бухгалтерии известный с 17 века: сделанная проводка не может быть удалена.
Отлично, давайте зайдём с этого конца. Хотя там, если покопать в API, будет пачка методов по изменению именно того же объекта (https://developer.paypal.com/docs/api/payments/v2/#authorizations_reauthorize).
В нормальном REST избегают POST методов, поскольку в случае потери результата POST крайне сложно понять, чем там кончилось дело, и что делать теперь.
К сожалению, авторы PayPal API книжек по REST-у не читали. Поэтому у них нет ничего идемпотентного.
M>Это требует проделать вызов. В микросервисной архитектуре такой подход означает что сценарий клиента может развалиться в середине выполнения и, как следствие, для каждого сложного сценария потребуется делать распределенные транзакции с откатом. Так, конечно, можно делать, но это очень дорого. Поэтому в большинстве случаев ты сперва делаешь пречек собирая все доступные ресурсы, проверяешь пререквизиты и только потом начинаешь выполнять изменения.
Простите, но вы рассказываете какие-то небылицы. Что такое "пречеки"? Мы пробежались по всем нужным ресурсам, и убедились, что все нужные нам "действия" торчат в виде линков?
Ну так это ничего не значит. Посмотрите в тот же PayPal API — там очень редко бывает так, что достаточно POST с пустым телом. То есть — результат будет зависеть от параметров запроса. А их вы никаким пречеком не проверите (если в API нет специального метода precheck). Поэтому — да, либо rollback (если вдруг у нас есть детерминированный способ сделать rollback), либо rollforward. Чудес не бывает.
Если вы собрались изображать в любой архитектуре, хоть микросервисной, хоть монолитной, композитный сценарий вроде "запросили кредит — перевели деньги на расчётный счёт — расплатились за покупку товара", то он всегда может сфейлиться на каждом этапе. В прошлом веке для этого применяли протокол двухфазного коммита. Сейчас у нас два варианта:
— реализовать двухфазный коммит (каждый из этапов на самом деле делается в виде двух отдельных методов типа "подготовить ресурс" и "использовать ресурс"; при этом подготовленный ресурс после некоторого таймаута саморассасывается.
— понимать, что мы можем застрять посреди процесса — например, с выданным кредитом, но без дефицитной игровой приставки, которую успели купить.
Никакие "пречеки" тут не помогут, если нет реального резервирования ресурсов.
M>Людям вообще нравится RPC, даже по этой теме видно. Но RPC не дает необходимой для микросервисов изоляции и довольно быстро превращает приложение в распределенный монолит.
RPC людям нравится не за то, что у него есть преимущества, а за то, что они не сразу видят его недостатки.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Во-вторых, построение безопасности путём скрытия действий в UI — это популярная, но плохая идея. В основном — потому, что она неимоверно бесит пользователей. Вот я сижу перед списком пользователей, хочу объявить выговор. Поискал в меню — нет такой команды. Поискал в свойствах сотрудника — нет такой кнопки. Полез в инструкцию — сказано "для объявления выговора нажмите на кнопку "Объявить выговор". И вот я уже полчаса думаю, то ли я дурак, то ли инструкция устарела, то ли программа глючит. Внезапно, "некрасивый" способ — дать мне нажать на кнопку, а потом вывести "403 у вас недостаточно прав для объявления выговора этому сотруднику" — в разы гуманнее по отношению к пользователю, чем предлагаемая вами идея.
Ну, идея как-то промаркировать элементы в списке, с которыми я могу что-то делать и с которыми не могу, вполне нормальная.
Жмякнуть на кнопку, подождать стандартный таймаут в 60 сек. и получить отлуп, может выбешивать не меньше.
Здравствуйте, TG, Вы писали:
TG>Ну, идея как-то промаркировать элементы в списке, с которыми я могу что-то делать и с которыми не могу, вполне нормальная.
Нет. TG>Жмякнуть на кнопку, подождать стандартный таймаут в 60 сек. и получить отлуп, может выбешивать не меньше.
Не очень понятно, откуда взялся "стандартный таймаут". Большинство сервисов отвечают в течение 1-2 секунд. Это я говорю про обращения с другого континента.
А уж если им и делать ничего не надо, а только вернуть ошибку — то ещё быстрее.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, TG, Вы писали:
TG>>Ну, идея как-то промаркировать элементы в списке, с которыми я могу что-то делать и с которыми не могу, вполне нормальная. S>Нет.
Сайты с бесплатными объявлениями бывают вываливают в поиске архивные объявления и какие-то кнопки там неактивны.
Мы такой подход не одобряем (с), но тамошние маркетологи, видимо, имеют другое мнение.
Я не призываю делать бан кнопок через HATEOAS. Но чем сама идея такого UI плоха?
TG>>Жмякнуть на кнопку, подождать стандартный таймаут в 60 сек. и получить отлуп, может выбешивать не меньше. S>Не очень понятно, откуда взялся "стандартный таймаут".
"Стандартный таймаут" — который часто выставляет фреймворк, например, WCF. Кто-то вообще не заморачивается на эти таймауты и оставляет всё по умолчанию.
S> Большинство сервисов отвечают в течение 1-2 секунд. Это я говорю про обращения с другого континента. S>А уж если им и делать ничего не надо, а только вернуть ошибку — то ещё быстрее.
Это, скажем так, в норме. За городом на мобильном интернете лаг уже составляет секунды.
Да и в "городском" энтерпрайзе пользователи могут столкнуться с тормозами, если они сидят через VPN и админы как-то криво "раскрасили" трафик.
Здравствуйте, TG, Вы писали:
TG>Я не призываю делать бан кнопок через HATEOAS. Но чем сама идея такого UI плоха?
Сама идея такого UI плоха тем, что
1. не даёт пользователю никакой информации о причинах того, что кнопка неактивна. То ли у пользователя нет прав; то ли у объявления истёк срок доступности этой кнопки; то ли просто баг на сайте.
2. даёт пользователю неверную информацию — состояние кнопки вычисляется в момент t0, а нажатие на эту кнопку происходит в произвольный момент t1, где разница между t1 и t0 может составлять дни, а не секунды.
TG>"Стандартный таймаут" — который часто выставляет фреймворк, например, WCF. Кто-то вообще не заморачивается на эти таймауты и оставляет всё по умолчанию.
Я не понимаю, какое отношение стандартный таймаут имеет к вопросу о проектировании UI.
TG>Да и в "городском" энтерпрайзе пользователи могут столкнуться с тормозами, если они сидят через VPN и админы как-то криво "раскрасили" трафик.
И это всё ещё лучше, чем прятать от пользователя возможность без объяснения причин.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Можно. Когда я делаю GET на ресурс, я получаю его представление. Крайне желательно, чтобы это представление зависело от состояния ресурса, а не от состояния пользователя, который его получает. S>Например, можно воткнуть reverse proxy перед сервером приложения, и тогда он сможет снять значительную часть нагрузки с сервера. S>Как только мы начинаем менять представление объекта в зависимости от пользователя, о кэшировании можно забыть.
Желание похвальное, только эта абстракция практически сразу протекает. Ты же сам рассуждаешь о представлениях ресурса, а представления зависят от того, кто на них смотрит. Кому-то можно показывать одно, кому-то другое, третьему вообще ничего нельзя показывать. В результате, во всех без исключения проектах, которые я видел, кэширование за пределами контроллируемого контура отключено по-умолчанию и включается только для определенным образом проверенных ресурсов.
S>Во-вторых, построение безопасности путём скрытия действий в UI — это популярная, но плохая идея. В основном — потому, что она неимоверно бесит пользователей. Вот я сижу перед списком пользователей, хочу объявить выговор. Поискал в меню — нет такой команды. Поискал в свойствах сотрудника — нет такой кнопки. Полез в инструкцию — сказано "для объявления выговора нажмите на кнопку "Объявить выговор". И вот я уже полчаса думаю, то ли я дурак, то ли инструкция устарела, то ли программа глючит. Внезапно, "некрасивый" способ — дать мне нажать на кнопку, а потом вывести "403 у вас недостаточно прав для объявления выговора этому сотруднику" — в разы гуманнее по отношению к пользователю, чем предлагаемая вами идея.
Современная ИБ говорит, что если какой-то функционал пользователю недоступен он и не должен знать о существовании этого функционала. Иначе мотивированный пользователь начнет искать способ для эскалации привилегий и, чем черт не шутит, вдруг найдет. К тому же, с точки зрения UX скрытие кнопки эквивалентно вызову с ошибкой потому что а) причин ошибки бесконечно много и в этом случае все их нужно обрабатывать и б) ИБ не велит раскрывать внутреннее устройство приложения через сообщения об ошибках, так что пользователь максимум что увидит это "тебе нельзя".
M>>Как в примере из соседнего поста, когда сервис туров следит чтобы определенные туры были доступны только совершеннолетним пользователям и эта же логика дублируется в сервисе рекомендаций чтобы не показывать несоврешеннолетним то, что им и так недоступно. HATEOAS бы эту проблему решил докинув ресурс user список доступных для него туров. ,
S>Это очень, очень плохая архитектура. В первую очередь потому, что вы инвертируете зависимости. Теперь у вас ресурс user (и его микросервис) обязан знать всё о турах (которые вообще-то обрабатываются другим микросервисом). S>А ресурс "тур", ясное дело, должен знать всё о пользователях. Поздравляю, вы получили не просто монолит, а трудноразвиваемый монолит.
У тебя в любом случае есть связь между двумя ресурсами. Ее можно показать с одного конца, с другого конца или с обоих концов. Последний вариант самый универсальный. Если связь пересекает границы микросервисов, существуют способы это реализовать не увеличивая лишнюю связность: обогащение на уровне gateway, backend for frontend, обогащения на уровне middleware, наконец, просто засунуть в HATEOAS вместо списка туров, ссылку по которой этот список можно получить. Обычно, за HATEOS как раз и отвечает не сам сервис, а middleware. Можно, конечно, замести связь под ковер, но как раз усложняет поддержку сервисов.
S>И это — тоже очень, очень плохая архитектура. Смотрите как это работает: "поисковый запрос" — это эфемерный ресурс. Последнее, что мы хотим — это хранить его. В первую очередь по соображениям масштабирования. Сколько пользователей вы ожидаете ежедневно? Двух? Как только у вас появляется хотя бы несколько сотен тысяч запросов в сутки, хранение (даже в течение ограниченного времени) их эфемерных запросов становится
неподъёмным.
А давай посчитаем Один указатель (кэшировать нужно не весь результат, а только порядок записей), пусть long -- 8 байт * 300k запросов в сутки * 1000 записей (ты же не забываешь про max_search_results)= всего 2.2Gb При этом 300к уникальных запросов это где-то 20M DAU т.е. федеральная система уровня всего СНГ по поиску с запасом влазит на одну машину. У нас, слава богу, не 2005 год и уже завезли кэши в разделяемой памяти, типа hazelcast и ehcache.
S>А непротиворечивость гарантируется тем, что последующие запросы мы дооборудуем хидером if-match/if-none-match, что позволяет нам заметить момент, когда данные на серверной стороне поменялись, и закешированную версию нужно выкинуть.
Во-первых, проверка изменились ли результаты поиска эквивалентна выполнению самого запроса, так что кэширование результатов поиска не имеет смысла и никто им не пользуется. Во-вторых, допустим клиент узнает что результаты изменились, делать-то ему что? Повторять запрос? Тогда никакой консистентности при переходе между страницами не будет и получится ровно то же самое что и с параметрами offset + limit в запросе. Настроить промежуточный кэширующий сервер, который примет полные результаты поиска, а отдавать будет с учетом range. Ну так то же хранение и получится.
S>Простите, но вы рассказываете какие-то небылицы. Что такое "пречеки"? Мы пробежались по всем нужным ресурсам, и убедились, что все нужные нам "действия" торчат в виде линков?
Мы подняли все ресурсы, входящие в сценарий, убедились, что их состояние позволяет реализовать этот сценарий и только после этого начинаем претворять сценарий в жизнь. Понимаешь, когда речь идет про микро сервисы, когда каждый сервис отвечает за свой домен и управляет одним-двумя ресурсами, почти все сценарии захватывают несколько сервисов. При этом подавляющее большинство сценариев не требует строгой консистентности, вполне достаточно eventual consistency. Поэтому нет необходимости любой сценарий превращать в сагу или двухфазный коммит. Более того, сценарии, требующие строгой консистентности встречаются настолько редко, что большинство разработчиков такого не реализовывали ни разу в жизни. В то же время, eventual consistency это все еще consistency, а если у нас сценарии разваливаются через раз, никакой консистентности не будет. Поэтому, дизайнить систему так, чтобы сценарии не разваливались, это хорошая идея. В том числе поддерживать между сервисами контракт, что если мы получили от строннего сервиса ресурс и список связанных ресурсов, то попытка обратиться к этим ресурсам не вызовет ошибки.