LINUX.ORG.RU

Protobuf для запросов с большим числом вариантов

 , ,


0

1

Привет, подскажите как лучше всего реализовать схему для сабжа. Суть - запрос для отправки уведомлений. Язык - питон+proto3. Уведомлений много разных, на один запрос может идти несколько разных вариантов разным адресатам. Уведомление внутри это некая общая база плюс пачка специфичных параметров. Пока нашёл следующие варианты:

  • разбить на разные запросы. Вариант так себе, часто надо отправить сразу Х уведомлений на одно действие разным адресатам. Делать ради этого Х round-trip так себе идея

  • сделать базу плюс жирный oneof. Выглядит убого и нечитабельно, любой code completion будет тормозить, плюс их надо два - на отправку и на геттер. Да и классы видимо разные надо будет.

  • сделать базу плюс юзать any. Поддержка его есть только в ванильной либе которую писали впервые увидевшие питон сишные нарки. Плюс это убивает на корню любой code completion и проверку типов и добавляет головняк с использованием.

  • передавать кишки уведомления строкой-жсоном. Нет, спасибо.

  • сделать базу и юзать extension типы. Да, но не в proto3, к сожалению.

Есть ещё какие-то варианты? Пока самым нормальным выглядит oneof, но тогда будет что-то типа (внутри repeated).

message SendNotification {
  string from = 1;
  google.protobuf.Timestamp when = 2;
  oneof data {
    SayA sayA = 10;
    SayB sayB = 11;
    ...
    SayXYZ sayXyz = 100500;
  }

  message SayA {
    string toEmail = 1;
    string name = 2;
    ...
  }
}

Простыню с парой сотен вариантов читать будет неудобно мягко говоря.

★★★★★

А чего, вот эти сообщения SayA, SayB они прямо сильно разные? Имхо, тебе тогда проще в .proto объявить enum типов сообщений и потом в SendNotification сделать поле тип сообщения, а сами сообщения - таки да передавать просто массивом байтов (внути хоть JSON, хоть черт лысый) и разбирать на клиенте.

То есть в .proto тогда, получается, описан будет просто некий конверт, в котоом тип сообщения указан.

paddlewan
()
Последнее исправление: paddlewan (всего исправлений: 2)
Ответ на: комментарий от paddlewan

А чего, вот эти сообщения SayA, SayB они прямо сильно разные?

Да, например параметры пуша со всеми кнопками/звуками будут сильно отличаться от параметров письма. И как раз пуш и письмо в 90% случаев надо слать вместе на одно действие.

Имхо, тебе тогда проще в .proto объявить enum типов сообщений и потом в SendNotification сделать поле тип сообщения, а сами сообщения - таки да передавать просто массивом байтов (внути хоть JSON, хоть черт лысый) и разбирать на клиенте.

Это any по факту. Минус code completion и проверки, плюс пачка геморроя с его разбором. А с вариантом енум+bytes нужно будет ещё больше кастомной логики (any можно хоть как-то разобрать штатными средствами, пусть и через пачку if и/или рефлекшн/getattr)

И кстати огромный минус any - оно очень implementation-specific. Например у меня в модуле А используется ванильная шлаколиба, в Б - более человечный protoplus, а в В вообще betterproto. И вот они все бинарно совместимы, но разобрать any из-за косяков с typeUrl во всех трех реализациях будет геморройно.

Можно было б красиво сделать через extension, но их выпилили, а мигрировать на proto2 это та ещё задача

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 4)

жирный oneof

Это вроде как рекомендуемый способ решения твоей задачи.

Есть альтернатива - поле с типом + bytes, где в массивчике лежит например тот же протобуф.

Ещё есть ощущение, что ты хочешь использовать всё это без оборачивания в человеческий интерфейс. Не делай так.

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

Ещё есть ощущение, что ты хочешь использовать всё это без оборачивания в человеческий интерфейс. Не делай так.

В плане без либы-клиента поверх протобуфа? Ну вот да, хотелось бы без дополнительных телодвижений, но к сожалению в данном случае похоже не выйдет. И кстати если не брать ванильный шлакоблок, то протобуф под питон вполне человечен

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

Ну вот да, хотелось бы без дополнительных телодвижений

Без дополнительных телодвижений это вынос обмена данными в отдельную абстракцию. У тебя уже кажется возникает путаница между пересылаемыми данными и данными самого приложения.

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

Без дополнительных телодвижений это вынос обмена данными в отдельную абстракцию. У тебя уже кажется возникает путаница между пересылаемыми данными и данными самого приложения.

Модели приложения от моделей интерфейса и так отделены. Суть вопроса как красиво сделать интерфейс

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

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

Но я вообще не в курсе, что у тебя там и как, совет из общих соображений.

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)

oneof и его варианты, естественно.

Выглядит убого и нечитабельно

Если там реально несколько сотен абсолютно разных типов сообщений, то никак кроме как простынёй описаний этих типов это не сделать, никаким инструментом.

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

любой code completion будет тормозить

Проблемы инвалидов которые его используют.

плюс их надо два - на отправку и на геттер

Для этого нужна одна строчка, а не дублирование всего описания, правда же?

slovazap ★★★★★
()
Ответ на: комментарий от ya-betmen

На сервисе уведомлений примерно так сейчас и реализовано. Единственное что неудобно это отправка с других сервисов - автокомплит рисует ипическое дерево всего что умеет запрос и от этого довольно тяжело уйти (нужно знать и класс, и имя аргумента). Можно сделать обертку и забуриться в кодогенерацию или setattr , но это дорога в ад.

Просто когда это добро начинает расти - постепенно появляется вопрос а можно ли как-то ещё.

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от slovazap

Но я что-от не поверю что в твоём протоколе реально сотни типов сообщений, а не единицы-десятки с опциональными полями

Десятки разве что верхнего уровня. А так если брать те же уведомления: signup - 6 штук на разные случаи, антифрод - ещё 5 штук, действия пользователя типа смены подписки - ещё 20-30 штук, действия системы - ещё штук 30, отложенные действия пользователя типа «удалить через Х дней» - ещё, маркетинговый буллшит - ещё… дальше продолжать? Там реально дохрена набегает

А опциональные поля - спасибо, не надо, потом хрен разберёшься почему что-то где-то не пришло. Особенно учитывая implicit default протобуфа

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 2)
Ответ на: комментарий от upcFrost

Десятки разве что верхнего уровня. А так если брать те же уведомления: signup - 6 штук на разные случаи, антифрод - ещё 5 штук, действия пользователя типа смены подписки - ещё 20-30 штук, действия системы - ещё штук 30, отложенные действия пользователя типа «удалить через Х дней» - ещё, маркетинговый буллшит - ещё… дальше продолжать? Там реально дохрена набегает

Ты ничего не доказал. Почему, например, signup 6 штук, а не одно с параметрами?

А опциональные поля - спасибо, не надо

Что-что тебе не надо? Опциональность полей в proto3 просто есть, хочешь ты этого или нет. Ты, возможно, не умеешь пользоваться протобуфами, от этого и проблемы. В опциональных полях вся его сила, они и только они обеспечивают прямую совместимость при удалении полей и обратную при добавлении. В некоторых случаях вместо oneof вообще предпочтительнее использовать просто набор опциональных полей.

потом хрен разберёшься почему что-то где-то не пришло

Ну видишь - ты ещё и смешал в одну кучу уровень транспорта данных и уровень валидации/контроля инвариантов. Последнее не задача протобуфа, и не надо делать вид что это так. Логика простая - тебе надо разбираться не только когде что-то где-от не пришло, но и когда что-то где-то пришло не то. Протобуфы этого не умеют как ни крути, поэтому если такая задача стоит, то она решается отдельным кодом. А в протобуфах остаётся чистый транспорт, расширяемый и обратно совместимый.

slovazap ★★★★★
()
Ответ на: комментарий от upcFrost

Единственное что неудобно это отправка с других сервисов - автокомплит рисует ипическое дерево всего что умеет запрос

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

ya-betmen ★★★★★
()
Ответ на: комментарий от slovazap

Если кратко - ты предлагаешь из протобуфа сделать по сути сокет с плоской и крайне размытой структурой. Спасибо, но звучит чёт так себе. Человеку, который будет вызывать сервис используя proto-спек должно быть чётко понятно какое уведомление чего требует, и это должно быть понятно без заглядывания в шаблон самого уведомления, иначе ломаются абсолютно все абстракции начиная от зоны ответственности сервиса. Либо нужно сразу писать тонну хелперов которые будут говорить юзеру что какому уведомлению нужно, но тогда сам proto-спек по сути бесполезен без наличия знаний о том что происходит внутри. В том что ты предлагаешь будет адская мешанина, которая не сильно лучше чем просить строку с жсоном внутри и потом гадать что в ней должно быть. Для такого никакой протобуф и так не нужен.

Если по пунктам:

Почему, например, signup 6 штук, а не одно с параметрами?

Потому что форматы сообщений на пуш, смс, письмо, и мессенджеры принципиально разные. Например смс нужен номер и caller ID, пушу нужны свистоперди типа звука уведомления, письму сам понимаешь. Валить все в одно сообщение это помойка. Уведомления о действитиях юзера тоже бывают крайне разные в зависимости от ситуации и желания левой пятки составителя шаблона

Опциональность полей в proto3 просто есть

Есть, но это не повод превращать сообщения в помойку. См выше.

они и только они обеспечивают прямую совместимость при удалении полей и обратную при добавлении

Вообще не относится к теме, да и сама эта псевдо-опциональность - предмет вечных срачей

В некоторых случаях вместо oneof вообще предпочтительнее использовать просто набор опциональных полей.

Явно не в этом. Человек, делающий запрос к сервису, не должен лезть искать как же там выглядит шаблон уведомления и какие он параметры берет. Это все должно быть четко описано сообщением интерфейса

А в протобуфах остаётся чистый транспорт, расширяемый

Чего ж тогда расширялку в proto3 выкинули?

обратно совместимый

WhichOneOf смотрит на тебя с укором.

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

заманкипатчи

Чёрная магия в продакшене это фу. Фабрику - хз, сотню фабрик для сотен уведомлений? Одну фабрику с енумом и kwargs? Чёт все равно отдельные классы чище выглядят

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от ya-betmen

Толстый класс с отдельным методом под каждый запрос.

Не, у нас так уже было. С такими обертками есть существенная проблема что народ туда начинает тихо пихать бизнес-логику, и потом выходит адская мешанина. Один раз проглядел на ревью - и писец.

Да и в целом на самом деле и так нормально с oneof и пачкой классов сообщений, просто получается многабукв прям до уровня жабы. Хотя даже overly explicit в любом случае лучше implicit имхо

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от ya-betmen

Не пихай туда логику

Спасибо, кэп, я этого делать и так не стал бы. Но помимо меня есть другие разработчики, и по опыту будет «джун добавил, мидл проглядел, сеньор забил» - и в итоге вся эта мегафабрика будет свалкой из запросов в базу вперемешку с бизнес-логикой. Мы вот прям сейчас как раз такое и вычищаем. Уже 7 человеконедель вычищаем

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

Если кратко - ты предлагаешь из протобуфа сделать по сути сокет с плоской и крайне размытой структурой.

Нет, я предлагаю убрать дублирование где это возможно.

Потому что форматы сообщений на пуш, смс, письмо, и мессенджеры принципиально разные. Например смс нужен номер и caller ID, пушу нужны свистоперди типа звука уведомления, письму сам понимаешь.

Вот тут например явно напрашивается один тип с optional caller id и звуком уведомления.

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

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

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

Нормальным решением был в any, но он ломает совместимость своим typeUrl и его поддержка настолько убога что лучше б его не было. Енум+bytes можно енум это доп сущность не связанная с самим сообщением, так себе. Можно было бы вместо енума заюзать message options (либо вместе с енумом, даже лучше), но их поддержка тоже мягко говоря не блещет.

Так что видимо продолжу с oneof

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от slovazap

Простыней вариантов и WhichOneOf. 200 классов это ок, 200 опциональных параметров конструктора это не ок имхо. Если б протобуф умел самое примитивное наследование - жилось бы сильно легче. Ну и WhichOneOf возвращающий строку это если честно убого и ломает совместимость если переименовать поле (несмотря на все крики о том какое оно все совместимое).

А так в целом устраивает, просто решил отойти на пару шагов назад и глянуть а можно ли сделать лучше

upcFrost ★★★★★
() автор топика