All streams
Search
Write a publication
Pull to refresh
5
0
Сергей Ретивых @as_serg

Back-end development

Send message

Прочитал этот тред и рядом, понял что мы решаем разные задачи)

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

Это не наша забота. Мы - мастер-система. Ядро АБС банка. Наша задача собрать данные и выложить их в очередь

Мне кажется после этого нет никакого смысла приводить примеры того, как вы могли бы использовать ООП. Вы выбрали себе специальный инструмент, который хорошо решает вашу задачу.

Все это не более чем общие слова.

Для Вас они звучат так, потому что вы с такими задачами просто и не сталкиваетесь. Вам нужно взять данные из талицы, возможно слегка трансформировать и передать дальше. Тут даже на уровне терминов нет ничего общего)

Интересная дискуссия, хочу продолжить рассуждения. Почему-то в процедурном подходе вы спокойно мыслите всей пачкой объектов, а когда речь заходит за ООП, то хотите строго выполнять работу как некоторые методы от конкретных объектов назначения. Нам нужно обрабатывать объекты также пачками. То есть скорее всего у нас эта задача выполняется по расписанию, и корневой объект это некоторый NotificationTask. А внутри него никто не мешает нам собрать необходимые подготовленные данные целиком (даже странно так не делать), то есть не по одному забирать клиентов, а сразу нужных с фильтрами и джойнами. Дальше все упирается в мощь и понятность выбранной ORM, и реально возможны ситуации где написать сырой запрос окажется проще. Но мне кажется некорректным утверждение про "разлапистый" запрос, потому что в итоге он сам будет быстрый, но не известно насколько быстро он будет написан программистом.

принимает на вход набор данных из выборки и у нас нет необходимости тратить время и ресурсы процессора на создание объекта

Здесь под выборкой имеется в виду курсор базы данных, из которой фетчатся данные? И дальше работа с сырыми данными-строками в функции-рассылке? Эффективно, но когнитивно кажется неудобно, легко например порядок полей попутать, или дату криво распарсить, и потом долго искать проблему. А иначе мы в любом случае эти данные как-то сохраняем в памяти, и сравнение с созданием объектов тогда уже выглядит как экономия на пакетах.

Насчет связей. Сами по себе они по задумке призваны нам помогать) Как буквально не удалить объект, на который кто-то другой ссылается, так и косвенно. Например, банк присылает уведомление по смс. И решено мониторить, сколько СМС на пользователя в среднем тратится в месяц, а для этого данные складываются еще в одну табличку. В вашем процедурном подходе (если только задача не была максимально исчерпывающе описана аналитиком) вы забудете это сделать, т.к. просто не знаете про это. В случае с ООП вы вероятно захотите узнать, умеет ли объект делать что-то подобное уже сейчас. И между делом с удивлением обнаружите, что есть такой сайд-эффект, и решите что делать дальше.

Действительно, ООП само по себе дает возможность излишнего усложнения и запутывания кода (ради кода, а не бизнес-процесса), а ленивые методы это всегда проблема N+1 запроса. Но тогда справедливо будет говорить, что процедурный подход дает возможность каждую задачу делать заново и плодить свои велосипеды, а сложность кода снижать просто игнорированием сложности бизнес-процесса. Впрочем, и в ООП можно не разобраться в структуре и проигнорировать часть бизнес-процесса, а в прямом процедурном подходе написать запутанный спагетти-код.

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

в теории хранение под uuid в 4 раза большего места выглядит значительно. На практике это не имеет решающего значения, т.к. помимо идентификаторов в моделях обычно хранятся на порядок больше полезных данных и относительный вклад небольшой. я не помню проблем из-за того, что размер базы сам по себе просто "очень большой", или индекс по полю c uuid работает медленнее интового. Если прям нужно экономить место, ну или вы видите явные преимущества данного типа - используйте конечно. Но не забудьте еще учесть возникающие сайд эффекты - проверьте API на возможность прямого доступа без проверки владения ресурсом, ну и вероятность мержа этой таблицы с какой-то другой из третьего источника в будущем. По мне проще, переплатить сразу местом хранения, чем читать новости про очередную слитую базу, в данном случае из-за банального недосмотра прав доступа и инкрементальных id)

здесь как вам удобно)

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

Я в основном имею дело с вэбом, и идея 200 ОК - {"error": "user_not_found"} контринтуитивна, и вызывает так сказать эмоции. Потому что я не ожидаю такое увидеть, и мне нужно тратить лишнее время чтобы понять подход и задумку. Потому что для меня ошибки транспорта обычно не связаны с архитектурой моего приложения, или легко локализуются (через поля c указанием сервиса-источника исключения). Но я допускаю, что могут быть задачи, где такой подход ок, наравне как и подход POST-API например. Но каждый из подходов сам в себе не регламентирует описание именно бизнесовых ошибок, тем более что они еще и по ходу пьесы всплывают частенько и не добавляются в спецификации. Повторюсь, я за полный справочник ошибок, их формальную привязку к месту возможного появления, и консистентность в выбранном подходе.

по умолчанию, для меня плюсы uuid перевешивают недостатки, поэтому я беру его. В случае, когда нужно оценивать ситуацию, я скорее начну рассматривать варианты использования ulid или какого-нибудь ksuid (совместимые с uuid), нежели обычного инта из сиквенса)

А теперь представьте у банка проблема и один из их сервисов упал и его будут чинить сутки. Вы предпочтете видеть 0 или устаревшие данные с надписью - "Данные актуальны на ... и могут не содержать результаты последних операций"?

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

Тем более обидно когда на том стороннем сервисе данные меняются очень редко, а мне эти данные нужны всегда.

Да, еще один хороший аргумент для децентролизации данных или заведения кеша

Любая проблема на этом сервисе влияет на всю систему. Это реальная жопа.

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

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

Что случится с сервисом "статьи" если сервис "юзеры" упадет?

В самом простом случае, если не озадачиться, то упадет. Но никто не мешает программно руками обрабатывать внутреннюю ошибку и 1) заполнять поля дефолтными/пустыми значениями 2) придумать механизм трансляции ошибки в ответе рядом с основной структурой.

Согласованность данных так же нужно программно отслеживать, так же обрабатывая ответ нижестоящего микросервиса и откатывая в своем в случае ошибки. Тем не менее, при таком подходе у нас данные согласованы 99.9% времени, в отличие от подхода подписки на обновления, где этот лаг может быть выше.

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

Также в соседней ветке с AlexSpaizNet я отметил, что я не против шины, это понятный резонный механизм, но стоит ли его везде тащить на старте? Мне это видится закономерным этапом развития системы.

В итоге, если сервис авторов крашится, или еще чего - на уже существующий функционал это не влияет.

На функционал да, но данные будут неактуальными, это лишь скрытие проблемы. Что лучше при ошибке обновления данных - оставить то же значение или показать пустое значение / текст ошибки?

Если кто-то из другой команды ломает свой сервис, то у меня сломается все, потому что мой апи напрямую зависит от чужого апи

Никто не запрещает сделать простую обработку и возвращать частичные данные при отказе сервиса нижестоящего сервиса.

Время ответа моего апи будет зависеть от времени ответа чужого апи.

Это правда.

А что если мои запросы убивают тот второй сервис потому что он был не готов к таким фильтрам и сортировками?

Хороший признак того, что данные стоит децентролизовать и хранить у себя

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

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

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

хоститься в облаке)

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

Индексы же имеют отношение к хранению данных, а значит речь в первую очередь подразумевается БД. Ее обычно имеет смысл держать на отдельной машине, потому что база всегда ресурсы требует, независимо от архитектуры. Разница лишь в том, что при монолите у вас скорее всего будет развеситая схема с кучей констрейнтов и внешних ключей, а в случае микросервисов - набор слабосвязанных таблиц, в которых нужные констрейнты не существуют, а реализуются программно, и достигается "согласованность в конечном счете". Да, в монолите проще написать неэффективный SQL, который будет долго исполняться. А в микросервисах вы вообще просто данные потеряете и база будет несогласована.

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

По вашему когда пользователь отправляет запрос на получение профиля, нужно запускать асинхронную таску и это будет быстрее чем прямой синхронный вызов?

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

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

В случае монолита и явном вызове функции приведенный вопрос равносилен тому, что вы меняете сигнатуру - и не озадачиваетесь проверить, а в каких местах она используется. Проще просто не менять сигнатуру. Независимость достигается именно разграничением границ и сохранением их неизменности

Действительно, микросервисы это сложно. Действительно, стартовать проще монолит. Я не призываю никого начинать новые проекты на микросервисах в этой статье.

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

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

Действительно, способ 2 выглядит лучше, потому что по биллингу дешевле. Но теоретически второй способ зависит от выбранной точки, до которой строится маршрут, и из-за этого кратчайшее расстояние до полигона не всегда может быть таковым по факту, в том числе из-за особенностей дорог. image
Тем не менее, в целом Способ 2 выглядит предпочтительнее
Всё, справедливое замечание, спасибо.
Отмечу это в статье
Можно конкретно текст, на основании которого сделан такой вывод?

Я вижу вот что:
— Usage is tracked for each Product SKU
— Cost is calculated by SKU Usage x Price per each use
— SKU: Distance Matrix (0.005 USD per each)
— SKU: Directions (0.005 USD per each)
Из этого я делаю вывод, что оплата и того и другого запроса одинаковая.

Вероятно то, о чем вы говорите, основано вот на этой строчке.
Each query sent to the Distance Matrix API generates elements, where the number of origins times the number of destinations equals the number of elements.

Действительно, каждый запрос distance_matrix в моём алгоритме создает 7 elements, но биллинг не по ним выставляется. Есть только ограничения на elements — Maximum 100 elements per server-side request, 1000 elements per second.

Поправьте меня, если я не прав
чуть подредактировал статью, чтобы было понятнее в этом месте
нет, везде будет 1 запрос. Есть точка. Мы проверили с помощью геометрии, что ее координаты не принадлежат нашему полигону. Значит нужно применять любой способ расчета.
В первом способе у нас есть несколько вершин (высчитанных благодаря KD-дереву), мы спрашиваем гугл — скажи мне, вот до этой, этой или этой точки, какой кратчайшее расстояние до любой из них?
Во втором способе мы знаем предопределенную точку внутри полигона, и говорим гуглу — дай маршрут до этой точки. Он построит несколько маршрутов, но это всё 1 запрос будет. Дальше мы рассчитаем, какова длина внешней части пути.
Когда к нам придет новая точка, нам так и так делать новый запрос в гугл, потому что будут новые ближайшие вершины или новый маршрут в центр, это неизбежно
Исходная задача — найти расстояние именно до полигона. Пример из жизни самый банальный — стоимость доставки. Внутри МКАД бесплатно, а вне мкад +1р. за километр. Вы не можете просто взять 2 точки, потому что не знаете где будет вторая — пересечение непосредственно с МКАД.
1

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Works in
Date of birth
Registered
Activity