All streams
Search
Write a publication
Pull to refresh
4
0

Software Engineer | Java / JS / Android / AEM

Send message
Да, знакомая ситуация. Но тут ведь другая проблема:
Спроектированная система была не предназначена для такого – это архитектурная проблема.

Решение архитектурных проблем – это всегда больно. Даже если бы у Вас не было бы throw, но была бы логика хождения в базу и изменения какого-то общего состояния. Вы бы мучались не меньше. Но в этом случае вы бы проклинали «Stateful». Но вы упускаете тот момент, что Вы переделывали Архитектуру уже работающего приложения.

Вы переделывали самолет в вертолет во время полета, а жалуетесь на то, что заклепки хуже болтов.
1. Мои предыдущие комменты в этом треде относятся конкретно к данной статье, а не к всевозможным особенностям реализации каких-то фреймворков.

2. Теперь конкретно ваш пример:
Вот был у вас однопоточный код, стал он выполняться двое суток. Надо параллелить, а у вас внутри везде throw.
Это самое плохое решение в данном случае.

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

Такие проблемы не появляются на ровном месте, а если вы с нею столкнулись, то у вас проблемы гораздо большего масштаба, чем «у нас в коде везде throw».
Паттерны, как уже говорилось, это не про GoF. Паттерны – это про подходы к написание и структурированию кода.

Если какой-то подход имеет строго выраженную идею, структуру и применяется для достижения конкретных целей – это паттерн.

Каррирование, Промисы, Коллбеки – это все паттерны.
TPL
Это уже смесь фп и ооп подходов и является нетрадиционным. Потому этот кейс не стоит брать во внимание – там другие правила.

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

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

Это все тот же ".catch()" в промисах, но в промисах он менее гибкий.
Добавлю еще про манипуляции в статье:
А вот этот код но уже с обработкой ошибок:image


На самом деле, данный код совсем не соответствует семантике приведенного в пример «ужасного ООП с проверкой ошибок».

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

ФП вариант с проверкой ошибок соответствует ООП варианту без проверки, где каждый метод может кинуть RuntimeException в случае ошибки.
Возможно я не до конца понимаю проблему, но в php это было бы примерно так же как я описал. Или основной консерн – отдельные инстансы ноды или кластер?

ps: да и вообще, нода не предназначена для генерации / парсинга и любых других CPU-bound задач. Ее удел – взять темплейт, подставить значения и отдать наружу. Или сходить на другие сервисы, получить контент и связать его воедино.
Можно попробовать следующий подход:
— быстро сгенерировать список уникальных имен для файлов и отдать клиенту
— параллельно запустить процесс генерации файлов

Клиент получает список file_ids – и запрашивает их на скачивание, получает каждый по готовности.

ps: в целом, это даже не проблема ноды, это довольно распространенная задача при генерации контента.
Если я правильно понимаю, это работать не будет по двум причинам:
1. res.end(200) вы отдаете после того, как сгенерировали все 4 файла, а это уже через 4 сек. и если это так – ответ не уйдет принимающей стороне все это время.
2. разве мульти-парт контент может быть отдан для скачивания?
Значит ли это, что JavaEE уходит в мир иной и в скором времени мы увидем действительно крутые и легковесные решения на базе JavaSE?

У NetBeans была не сильно большая популярность, его отдали в Apache Foundation.
Знаете, у меня в прошлом проекте были «аналитики», которые тоже говорили: «А чо, поменял конфиги, рестартовал и погнали». А то что на входе непрерывный поток данных (финансовых), который можно потерять пока рестартуем — это им в голову не приходило. Всякие проекты бывают, попросту говоря.
Инструмент выбирается под проект. Я не говорил, что Spring – это абсолютное зло, он хорош для много. Я лишь сказал, что для большинства задач, с которыми я сталкивался в своей карьере он излишне «жирный».

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

Спринг крутой, он позволяет быстро написать то, что будет работать.
Но вот я, к примеру, всегда хотел выкинуть из спринга все ненужное мне в конкретном проекте. У меня API сервер, который умеет только JSON и ничего больше. Но спринг такой классный, что подсовывает мне дополнительно кучу кода, который нужен для поддержки xml, html, а еще загружает классы для AOP и кучу своих связок для других вещей, которые нафик не нужны. А ведь RAM то ограничен.

Но это уже offtop с моей стороны пошел.
Для анонимных классов есть правило: «У анонимного класса не должно быть состояния».

Если нужно состояние – сделайте это обычным классом; а если при этом нужно связать с текущим – передайте ссылку на текущий, методы для взаимодействия сделайте package-private.

пояснение: Если у класса есть состояние, значит у него довольно сложная логика и этот класс должен быть протестирован через те же unit-тесты. Его поддержка внутри другого класса будет не тривиальной, потому следует разделить эти 2 сущности.
Доброго дня. Анонимные и Внутренние классы – это разные вещи.
До DI с этим без особых усилий жили. Да, кода было больше, но код был явный.

Что мешает написать:
HttpServer
 .withCodecRegistry(
   new CodecRegistry()
     .bind(APPLICATION_JSON, new JsonCodec( ... ))
     .bind(APPLICATION_XML, new XmlCodec( ... ))
 )
 .withRouteRegistry(
   new RouteRegistry()
      .bind(GET("/"), (r) -> Response.ok());
 )
 ...
 .run(8080);
Или что-то подобное…
Более того, чтобы файл не был на тысячи строк, можно этот конфиг разбить на несколько: RouteConfig, ErrorHandlerConfig, CodecConfig, etc.

Для проектов, у которых меньше 50-100 эндпоинтов это будет вполне приемлемый подход.
Это не будет слишком избыточным кодом или страшным бойлерплейтом, это будет вполне удобный и красивый конфиг.
Я брал код из статьи: github.com/alek-sys/spring-functional-microframework

и пробовал выполнить:
ab -k -c 10 -n 1000 «localhost:8080/»
ab -k -c 1 -n 1 «localhost:8080/»

Оба зависают.
А через браузер отлично отдает ответ.
Есть отлично правило хорошего кода: «Используйте только статические внутренние классы».
И есть еще лучше: «Старайтесь не использовать внутренние классы вообще».

Ситуации, когда действительно нужен не статический внутренний класс, крайне редкие. Если вам встретился такой, то скорее всего вы наткнулись на ошибку или на плохой код, который нужно переписать.
Думаю, не превратят.
Это же Spring, не стоит их недооценивать :-)

Кстати, попробовал потыкать webflux из примера на ApacheBenchmark – он вообще не отвечает на запросы. Ну а по консоли видно, что это сейчас на стадии альфы или в лучшем случае беты.
На самом деле, это именно то, что нужно.
Бекенд в чистом видел – это API сервер, который ничего не должен знать про фронтенд. Сейчас все больше и больше мобильных клиентов, которым нужно отдавать только данные. Веб-сайты постепенно отходят на задний план.

Да серверный ренден еще остается, но для этого будут нужны «тяжеловесные» фреймворки. Для остальних же, нужны крайне легковесные, которым хватало бы каких-нибудь дешевых t1.micro.

Я сам не сильно люблю Spring, но Spring WebFlux – это совсем другое дело, если все так, как выглядит, то это замечательный инструмент (пока они не превратят его в Spring WebFlux Boot).
Да именно, и я прекрасно это понимаю.
Но к сожалению, очень многие этого не понимают. А подобные статьи лишь усиливают мнение, что Асинхронность – это идеальный подход без недостатков.

В статье явно прослеживается посыл: «синхронность – плохо, асинхронность – хорошо. точка.»
Распишу пример в цифрах:
Задача занимает 1000 ед. времени.
Задача может быть разделена на 4 подзадачи по 250 ед. времени.
Синхронизация «железных потоков» 10 ед. времени.
Сохранение состояния континуации 1 ед. времени (допустим она сверхлегкая).
Есть 4 потока и 8 задач.

вариант1. Каждая подзадача зависит от предыдущей и не может быть выполнена в параллели.
(подготовить запрос в базу, сходить в базу, обработать результат, отдать наружу)
вариант2. Каждая подзадача независима и может быть выполнена в параллели.
(проксировать запрос на 4 других сервиса, и больше ничего не делать)
вариани3. Каждая 2 подзадачи независимы, 2 – зависимы.
(подготовить 2 запроса, отправить 2 запроса в 2 разных системы, смержить ответы и отдать наружу)

«Синхронная» многопоточная модель:
ход выполнения: 2 задачи + 2 синхронизации
8 задач = 1000 + 10 + 1000 + 10 = 2020 ед. времени (в каждом потоке).
1 задача будет выполнена за 1010 ед. времени.

«Асинхронная» многопоточная модель:
#1: Каждая подзадача зависит от предыдущей и не может быть выполнена в параллели.
ход выполнения: 8 подзадач + 8 сохранений состояния + 8 синхронизаций.
8 задач = (8 * 250) + (8 * 1) + (8 * 10) = 2088 е.д. времени.
1 задача будет выполнена за: (4 * 250) + (4 * 1) + (4 * 10) = 1044 ед. времени

#2: Каждая подзадача независима и может быть выполнена в параллели.
ход выполнения: 8 подзадач + 8 сохранений состояния + 8 синхронизаций.
8 задач = (8 * 250) + (8 * 1) + (8 * 10) = 2088 е.д. времени.
1 задача будет выполнена за: (1 * 250) + (1 * 1) + (1 * 10) = 261 ед. времени

#3: Каждая 2 подзадачи независимы, 2 – зависимы.
ход выполнения: 8 подзадач + 8 сохранений состояния + 8 синхронизаций.
8 задач = (8 * 250) + (8 * 1) + (8 * 10) = 2088 е.д. времени.
1 задача будет выполнена за: (3 * 250) + (3 * 1) + (3 * 10) = 788 ед. времени
*но тут все на много интереснее: этот вариант не может быть идеально положен на таймлайн потоков, потому 3-4 задачи из 8 будет раскиданы по таймлайну и выполнены в среднем за:
(5 * 250) + (5 * 1) + (5 * 10) = 1305 ед. времени, на ~25% дольше чем в синхронной модели.

Подводя итог:
— Синхронная модель = стабильность и предсказуемость по времени обработки и отдачи результата.
— Асинхронная модель = менее предсказуема в поведении и необходимо изучать среду в которой будет использована данная модель и требования к системе.

В идеальном варианте (#2) такой подход может дать в ~3.5 раза больше скорость обработки. Но таких задач на практике крайне мало, если есть вообще.
В более реальном случае (#1), такой подход может даже стабильно замедлить систему.
Или сделать ее нестабильной (#3) – когда все зависит от планировщика подзадач и часть задач может отдаваться на 25% быстрее, а часть на 25% медленнее, чем в синхронной модели.

ps: в примере #3, если нагрузка будет не сильно плотной, то система получит стабильный прирост на ~25%, если же нагрузка на систему возрастет, то система станет менее стабильной и может перейти даже в разряд деградации производительности. Потому, в этом случае, нужно делать замеры и подстраиваться под возрастающую нагрузку заблаговременно.

pps: данные примеры – это крайне упрощенная теория, а не реальные исследования. В реальном мире все на много сложнее и сильно может отличаться от приведенных цифр!

Information

Rating
Does not participate
Location
Киев, Киевская обл., Украина
Registered
Activity