20 лет прошло с момента диссертации Роя Филдинга, а разработчики до сих пор не могут отличить REST от HTTP RPC.
REST это про гипермедиа, HATEOAS, возможность задать поведение клиента с сервера. URL в REST это адрес ресурса и ключ кеширования, он вообще не обязан иметь какой-то четкой структуры. GET без body нужен, чтобы передавать все параметры запроса в строке запроса и иметь возможность закешировать ответ на любом из прокси, чтобы не читать body. PUT, PATCH, DELETE и тд являются вариациями POST (того же эффекта можно добиться используя POST и комбинации заголовков для манипуляции кешом на клиенте); именно поэтому в HTML 5 до сих пор есть только формы GET & POST.
А вы рассказываете про RPC поверх HTTP. И там хоть делайте хоть PATCH /orders хоть POST /update-order, скорее всего вы не будете использовать заголовки для кешироания в своих микросервисах и дадите клиенту вашего сервиса (разработчику) самому решать, что кешировать, а что нет. И уж тем более у вас не будет поддержки HATEOAS.
Рецепт такой - вы пишете веб интерфейс используя ASP.NET Core или NextJS (конечный потребитель это человек через браузер) - вы уже используете REST. Вы пишите микросервисы (конечный потребитель разработчики, которые интегрируют ваш АПИ в свои приложения) - вы используете RPC.
Я бы сказал так - BFF и API Gateway это логические единицы. Физически это может быть даже один контейнер, в котором будет приложение, которое по путям /app, /checkout, /admin или по заголовку host будет маршрутизировать запросы на соответствующий модуль BFF или Gateway. А логическое ядро (команды, запросы, оркестраторы, бэкграунд обработчики) будет так же запущено в рамках того же приложения. Получаем модульный монолит. Такая архитектура знакома многим, кто разрабатывал приложения до появления тренда на микросервисы - тот же классический WordPress яркий пример такой архитектуры. Там модули вообще можно было подключать как плагины. И все на одном сервере.
Если модули не спутаны между собой, то всегда можно взять какую-то особо нагруженный модуль и при явной необходимости вынести его в отдельный инстанс/инстансы и масштабировать его независимо. То же самое и с базой данных. Может вообще оказаться, что инстанс приложения тянет и его масштабировать не надо, а БД не тянет. Для высокой нагрузки - соответствующее решение с соответствующим SLA. Для низкой нагрузки - простое решение. Например, модуль поиска видео и просмотра видео очень нагруженный и может быть потребоваться десятки инстансов БД, с репликацией и ТД чтобы выдержать нагрузку и обеспечить высокую доступность и отказоустойчивость. Эти сервера могут даже географически быть раскинуты, для обеспечения SLA. А вот модуль пользовательских плейлистов кратно менее нагружен и куда проще (т.к. там скоуп не весь сервис или категория видео, а конкретный пользователь). С таким на десятки тысяч активных пользователей и один относительно небольшой инстанс справится, да и требования к доступности и устойчивости у него наверняка поменьше. Ну не получилось добавить в свой плейлист видео, неприятно, но не конец света. На крайний случай можно на клиенте записать, что была попытка добавить и потом в бэкграунде обновить данные на сервере.
Т.е. на мой взгляд проблема не в микросервисах. А в преждевременной оптимизации, которую инициируют сами разработчики. Может ими движет психологический аспект, желание показать свои "лучшие" навыки. Может они реально верят, что делают "лучше". А по факту получается пушка для стрельбы по воробьям. И когда адепты прагматизма смотрят на объективно переусложненную для решения текущих задач систему, они понимают, что огромное количество вещей сделано "чтобы было, на будущее"; и это будущее не наступило, а цена уже заплачена, и немаленькая и счета приходят до сих пор.
UPD: А так как объективно число пользователей интернета ограничено, как и их время в сутках, то не может быть так, чтобы у всех приложений был трафик как у Озон, Вайлдберриз, Амазон, Гугл и тд. Т.е. чаще всего приложениям пользуются пару тысяч человек в общем случае одновременно, и тут объективно чаще всего монолита хватит. Даже чуть вскрою карты, я работаю в крупнейшем провайдере IP телефонии в Серверной Америке. У нас трафик на самых нагруженных сервисах, о которых мне известно, оценивается тысячами RPM, максимум десятками тысяч в самое пиковое время. И микросервисная архитектура используется во многом для независимого деплоя модулей, недели для масштабирования. Хотя там такая связность, что даже и эта возможность почти никогда не работает. Буквально многие сервисы могли быть сделаны монолитами и ничего бы в худшую сторону не изменилось.
Все куда проще. Есть клиент, например, клиентский веб портал. Вам нужно получить информацию из системы в разрезе данного конкретного пользователя. Нужно пройти аутентификацию и при отображении интерфейса делать срез / представление для данного пользователя. Бэкенд же генерик. Он позволяет мутировать данные и получать данные. Он не беспокоиться, а кто их запросил, а есть ли у него права. Он концентрируется на бизнес логике (данные и их мутации). Работу по созданию проекции для пользователя, агрегации данных и ТД выполняет BBF. Такой же BFF может быть и для приложения портал авторов, которое взаимодействует с тем же бэкендом, но уже в другой проекции, другими правилами авторизации и тд. И третий BFF будет у приложения бэк офиса, где например модерируется контент. Например в веб приложении могут использоваться к http-only cookie для хранения полезной инфы, в том числе авторизации; а мобильные приложения могут использовать более удобный для них jwt.
Чем отличается от API Gateway? Только степенью узконаправленности: API Gateway широкое направлен и чаще всего обслуживает публичный АПИ. Это выражается во всем: от широты спектра возможностей авторизации: oAuth2, API ключи и до очень обобщенного контракта API, чтобы можно было сделать буквально любого клиента или любую интеграцию.
Мой рецепт такой - если клиент "ваш" нативный - делайте БФФ, в перспективе не будет ошибкой 100%. Если вам нужно предоставит точки интеграции "внешним" сервисам в рамках вашей компании или других компаний - делайте API Gateway и проектируйте публичный API. При этом в обоих случаях ядро системы будь то монолит или микросервисы предоставляют только операции с бизнес логикой + оркестраторы или менеджеры процессов, не парятся за авторизацию и доступны только в приватной сети. За авторизацию и пользовательский контекст отвечают BFF и API Gateway.
ASP.NET MVC — это супермощная вещь, но она имеет ряд недостатков по сравнению с альтернативами.
Интерактив на странице реализовывать куда менее удобно, чем, например, в Next.js. Да, у вас доступна вся мощь ванильного JavaScript, HTMX, Bootstrap — и добавить на страницу карусели, модальные окна или простые формы вполне реально. Этого достаточно для части задач, например при рендеринге простых сайтов. Однако создать полноценный интерактивный мультифункциональный портал для клиента — вроде банковского — будет значительно сложнее (хотя и возможно: как-то же раньше справлялись, хотя современные фреймворки как раз и пришли на смену этому «как-то»).
Выбор UI-китов крайне ограничен, и большинство из них постепенно устаревают. В современном мире UI-киты чаще всего выпускаются под конкретные фреймворки. Да, бывают версии с «чистым» HTML, но их немного — и у них возникают серьёзные вопросы по объёму «копипасты»: чтобы создать модальное окно или форму с валидацией, приходится вставлять в 2–3 раза больше кода, чем в случае с компонентами.
С другой стороны, это же и плюс: работа на самом низком уровне с HTML, CSS и ванильным JavaScript заставляет искать максимально простые и прямолинейные решения там, где это возможно.
Если же рассмотреть Blazor, он тоже оказывается довольно компромиссным решением:
Да, интерактивность реализовывать проще, и удобно переиспользовать код между сервером и клиентом. Однако всё это присутствует и в Next.js — причём в куда более продвинутом виде и нативно, на «родных» веб-технологиях, а не через WebAssembly и JS Interop.
А сам JS Interop — это головная боль. Да, можно использовать JS-библиотеки, но там, где TypeScript подсвечивает синтаксис и проверяет типобезопасность на этапе компиляции, в Blazor все проверки смещаются в рантайм (включая тесты).
В целом экосистема Blazor развита слабо: под React сотни UI-китов и библиотек, а в Blazor основную работу ведут энтузиасты.
Поэтому каждый раз, когда я думаю запускать новый SaaS-пилот и пишу бэкенд на .NET, руки тянутся «ускорить» разработку, взяв ASP.NET Core или Blazor. Но, трезво взглянув на реальность, я понимаю: гораздо быстрее написать весь фронт на Next.js — там уже есть нужный UI-кит, удобные компоненты, сборка через Vite, минимизация, оптимизация, сжатие изображений — всё настроено «из коробки». Так что на ASP.NET MVC я уже смотрю с лёгкой ностальгией, хотя и пишу API на .NET.
P.S. При этом Next.js и вся Node.js-экосистема, конечно, дико кривые. Не далее как сегодня я узнал, что в Node.js HTTP-сервере жёстко задан таймаут запроса — 5 минут. Его можно переопределить, но только глобально: установка таймаута для отдельных запросов не работает. А багу этому уже за 100 лет в обед, и никаких движений по её исправлению. Ладно, это ещё можно обойти. Но Next.js не позволяет переопределять этот таймаут через свои настройки — только патчингом исходников. Ишью на GitHub по этому поводу висит уже несколько лет. И таких «подводных камней», на которых можно зависнуть на целый день, хватает — стоит лишь выйти за рамки простого рендеринга. Но всё равно, как ни странно, получается быстрее, чем на ванильном стеке, за счёт универсальности серверного и клиентского рендеринга.
Я как-то вступил в достаточно жесткое противостояние с менеджментом на тему Agile и Scrum. Менеджмент требовал полного соответствия комитменту, чтобы велосити был не толькл стабильным, так ещё и рос каждые несколько спиртов, т.к. якобы наша компетенция растет. Причем когда мы говорили, что задача на несколько спринтов и ее хрен нарежешь, заставляли нарезать, и очевидно резали почти наугад и само собой в 90% случаев не попадали, потому что то, что думали будем делать долго, делали быстро, а то, что думали сделаем быстро - делали долго. И рапорты каждый раз по одному месту. И никак этого не избежать, т.к. делали уникальный продукт (аля продвинутый зум со встроенным OBS на webrtc). И менеджмент сам не понимал - результаты на дистанции несколько месяцев феноменальные, а отчёты непонятные. Их прямо ломало от этого, они не могли четко "планировать". Хотя какое тут планирование, неизвестность за каждым углом, сегодня план один, завтра другой и это и есть Agile в своей крайней степени.
Я же объяснял, что менеджменту надо сфокусироваться на продукте, ценности и на оценке ситуации, а не на показателях отчётов по спринту в Jira. Задача будет сделана тогда, когда будет сделана. На планинге скоуп и риски оцениваются приблизительно, на основе того, что команда знает в момент оценки о задаче. Далее команда даёт ежедневный статус апдейт, какое состояние, переоценивает риски. Менеджер должен внимательно слушать, анализировать и если нужно менять вектор, забивая на цели спринта и коммитменты, если появляется что-то более приоритетное или оказывается, что риски были взвешены неправильно и надо срочно менять тактику. О том, почему они были взвешены неправильно и можно ли было что-то изменить обсудите уже на ретро, но выкореживаться, чтобы получить "хороший" отчёт вредит вообще всем.
В этом плане объективно самым гибким методом является scrumban - нет никаких спринтов, команда работает по обстоятельствам. При этом присутствует цикличность ритуалов вроде рефаймерта, демо, ретро, иногда даже хай левел планинга. Но для этого нужно, чтобы либо команда была самоорганизована и сама драйвила продукт (0.1% команд на такое способен), либо чтобы был толковый менеджер, а не "менеджер по методичке".
Менеджеры обиделись, не уверен, что прислушались. А я забил болт на их нытье и делал по кайфу, т.к. знал, что делаю максимум и с точки зрения исполнения быстрее команда не сделает.
Главная фича ORM это ченж трекинг и маппинг. И особенно удобно это в CQRS на стороне Command. Достаете из репозитория агрегат (который может быть сущностью вроде Order с вложенным OrderItems), за счёт маппинга агрегат собирается автоматически по довольно простым правилам маппинга описанным декларативно; далее мутируете агрегат, соблюдая инварианты, и далее вызываете "Сохранить". Все, ОРМ сам высчитает diff, постарается его применить к конкретной базе которая используется на проекте, применит оптимистичную конкуренцию и тд.
При этом использование ORM на стороне Query опционально и зачастую лишнее. Сторона Query в базе настолько многогранна, что буквально есть 100 способов сделать одно и то же, и в этом случае ORM всегда будет отставать от драйвера базы и будет ещё слой своих багов накидывать.
Если я использую тактические паттерны DDD на проекте, выделяю явно слой домена, агрегаты и ТД, то у меня всегда используется ОРМ, потому что это очень удобно.
Если же я просто пилю всю логику в апликейшен слое с анемичной моделью, то часто достаточно обычного драйвера базы; да, технический код подмешивается в апликейшен, но если я уже начал так делать, то скорее всего данной модуль слишком примитивен, чтобы заморачиваться с ним по DDD.
Да нет никакой субординации на собеседовании, это ваш конкретный загон.
Нантмающая сторона по своим субъективным критериям за выделенное время пытается понять, подходит ли ей специалист. Специалист пытается понять, подходит ли ему данная компания по своим субъективным критериям. Дальше либо матч есть либо нет. Кому-то подходит модель "начальник-подчиненный", кому-то более неформальный подход, а кто-то не смотрит ни на что кроме суммы офера.
Субъективность критериев на нанимающей стороне варьируется от того, насколько строгий фреймворк собеседований в компании - может быть очень строгий, унифицированный и мало зависящий от интервьюера, а может быть менее строгий, свободный и соответственно более зависящий от восприятия интервьюера. Я имею в виду то, какие вопросы задавать, какие ответы ожидать и на какие вопросы отвечать и как.
А так же баланс лояльности между соискателем и нанимателем постоянно дрейфует от состояния рынка: мало кандидатов - ниже требования на ту же позицию и кандидаты менее лояльны; много кандидатов - выше требования к кандидатам на ту же позицию, и кандидаты более лояльны к "особенностям" работодателя.
Я всегда нормально отношусь к вопросам ко мне со стороны соискателя. Просто делаю себе пометку и выделяю время на его вопросы в конце интервью, в том числе и технические вопросы. Обычно у меня интервью это полтора часа, 15 минут в конце на вопросы кандидата. Если у нас обоих есть время и вопрос интересный, могу задержаться ещё на минут 10 чтобы закончить обсуждение. Если на мой взгляд кандидат не подходит, то когда выйдет время на вопросы кандидата - вежливо ему об этом сообщу и вежливо отклоняю предложение остаться после интервью.
Более того, в работе я лично предпочитаю модели без субординации, а с зонами ответственности и компетенции. Условно я всегда должен иметь возможность дать обратную связь своему линейному менеджеру или иметь возможность высказать свое мнение о его решении. Но его решение остаётся его решением и ему за него и отвечать. Ровно как и я как тех лид могу придти с решением к команде, она может дать обратную связь. В конечном итоге мы придем к варианту, что есть несколько сопоставимых опций из которых надо выбрать и финальное решение за мной, т.к. это моя ответственность. Т.е. в такой схеме минимизируется "я начальник ты дурак". Все прозрачно, какие опции рассматриваются, какие у них плюсы и минусы и какое финальное решение и почему.
Я за собой начал замечать, что быстро теряю интерес к проекту, когда он переходит в стадию поддержки. Начитается тягомотина, скучные задачи (например, недавно мигрировали микросервисы 10-тилетнец давности на ARM, тупо сиди обновляй библиотеки и запускай тесты и правь код, пока не заработает), исправление трудно воспроизводимых багов и тд. Я чувствую, что с этим практически с такой же скоростью справился бы любой миддл, потому что такой характер работы, компетенция особо в ней не решает.
Я наблюдал ситуацию, когда проект запускала одна команда спецов, которая самоорганизованно собирала требования, проектировала, строила архитектуру, готовила инфраструктуру и ТД и запускала проект буквально за пару месяцев. Потом подмешивали в течении пару месяцев в в команду нормесов, чтобы они занимались поддержкой и обслуживанием. А спецы возвращались в пул для быстрого и качественного запуска нового проекта. Иногда оставляли капасити для консультации по разработанным проектам или подключались для нового раунда крупных доработок.
Таким образом каждый психотип при деле. Не надо таких спецов заставлять сидеть на поддержке (часто удерживают высокими ЗП, и сложно соскочить обратно в рынок) и не надо заставлять нормесов брать на себя большую ответственность и предоставлять им автономию.
В этом и суть менеджера, найти и почувствовать лучшую комбинацию.
Сейчас перекос рынка в сторону работодателя, поэтому будучи соискателем не всегда есть возможность разгуляться, на многие "Ред флаги" начинаешь закрывать глаза. То же самое было, когда рынок был сдвинут в сторону соискателя, нанимали всех подряд и так сильно не придирались.
Ну так покажите такому спецу, что вы можете и без него сделать. Потому что часто он реально берет на себя, потому что "никто другой не сможет". Буквально нет человека, который вызовется это сделать. Или же бывают выскочки, которые берутся, но все равно история заканчивается тем, что идут к такому спецу, потому что сами не справляются.
А знаете как справляется такой спец-единоличник? Он не закрывает ноутбук в 6 вечера, он сидит после работы, иногда до ночи, иногда на выходных. И именно так он и стал спецом. И когда в команде вроде кто-то взял задачу, спец морально настроился максимум чуть поссаппортить, а в итоге все скатывается в то, что ему приходится опять сидеть тащить, потому что релиз уже на носу, а сделано 10% максимум - теряется доверие.
И вот тут тоже проблема лида и менеджмента. Им такой спец удобен. Они не хотят менять тех середнячков, которых набрали, на спецов покруче. Не хотят планировать лучше. У них есть такой спец, так что все будет в шоколаде (да и по деньгам он получает на 15-20% больше всего по сравнению с нормесами). И это бесконечный цикл, который может разорвать только сам спец, но осадок все равно останется, либо нормальный менеджер.
Но я бы таких людей винить не стал и на самом деле они кажутся самыми перспективными из всех, кого можно нанять. Потому что: он уже доказал, что раз он взялся, то он сделал; он доказал, что хватает обучаемости находить решение любым проблемам; накопленна большая компетенция. Такого спеца в команду к толковому и вайбовому менеджеру, и это будет очень мощный симбиоз.
Причем я не говорю, что сидеть после работы норма. Таких спецов часто (до поры до времени) прет от сложности задачи и это как босс в Дарк Соулс - особый вид эндорфина. Но при этом есть особый аспект, что нет дедлайна на убийство босса в Дарк Соулс, от этого не зависят другие люди и ТД, а в от выполненной задачи зависит, поэтому ещё интереснее и азартнее, + элемент творчества. Но такие способности и страсть надо уметь направить, иначе получите выгоревшего сотрудника... Хотя кому сейчас не пофиг, найдут нового)
REST не REST без гипермедиа. В общем смысле Гипермедиа предназначен для общения с клиентом (браузером) - отдавать контент, управлять кешом, управлять куками и тд. И там формат URL клиента не волнует: вы часто смотрите на URL в браузере, когда лазите по Хабру? или сходу введете в адресной строке url своей статьи? или статьи, которую читали 5 минут назад?. Думаю, нет. Потому что URL в REST выступает идентификатором ресурса для вашего браузера и одновременно ключом кеширования, а не красивым путем для предсказуемости разработчика или пользователя.
В вашем случае описана коммуникация машины с машиной -> RPC over HTTP. Даже если это SPA вызывающие Web API - то URL, которые вызываются из SPA заботят только разработчика, а не пользователя приложения.
Да, некоторые концепции из REST используются при разработке Web API, например клиент-сервер и stateless. Но описанные в статье правила это это правила создания RPC сервисов поверх HTTP.
Ресурсориентированный подход хорошо себя показывает только для CRUD. Если мы говорим о чем-то посложнее, начинается появление костылей при использовании этого подхода (пример 1, пример 2, пример 3).
Лично я не вижу в этом смысла. Мне ближе что-то вроде подхода использованного в Slack. Понятная операция, понятные аргументы, не надо ничего выдумывать в дизайне.
Это интерпретация принципа Dependency Inversion. А точнее констатация.
Чистая архитектура это про отделение инфраструктуры от аппликейшена, нужна для того, чтобы можно было менять инфраструктуру не меняя код бизнес логики. Или разрабатывать и тестировать бизнес логику в отрыве от инфраструктуры (не важно какой сервер, сеть, диск, база данных и ТД), а потом подключать адаптерами ту инфраструктуру, которая лучше подходит для обеспечения бизнес логики.
UI (весь) это только один из адаптеров к входящим портам бизнес логики: web UI, консоль, HTTP API, RPC, чат бот. Может ли быть полезным проектировать адаптеры по принципам чистой архитектуры? Может, если Вы проектируете фреймворк, на котором потом будет реализовываться различные адаптеры.
В вашем случае архитектура библиотеки может быть и чистая, но это не значит, что у реакт роутера она не чистая. Поэтому вопрос здесь только в удобстве реализации адаптера, и реакт роутер пока выглядит лучше. Как минимум "гейм ченжинг" этот подход точно не назовешь.
Даже синтаксически, я бы вместо того, чтобы добавлять код проверки роута внутрь самого компонента и если не матч вернуть null - сделал бы что-то вроде
Будем считать это псевдокодом. И в билд тайме обходил бы дерево компонентов и генерировал бы граф страниц, подкидывал их в роутер в билд тайме опять же и потом в рантайме на каждую навигацию вызывал matchRoute для каждого компонента, у которого нашлась такая функция. И рендерил бы первый компонент, для которого matchRoute вернул тру.
Модуль не становится верхнеуровневым, только из-за того, что вы физически расположили выше в иерархии ваших компонентов. В классической чистой архитектуре, верхнеуровневый модуль это бизнес-логика.
Ответил выше.
С помощью этого достигается то, что реальный компонент отвечающий за отображение, например, пользователя, вообще не знает о существовании роутера и о том, что мы приняли решение отобразить его в браузере по ссылке /users/1, мы это контролируем в слое выше, в данном случая на основе pathname.
Весь этот код расположен в роутере. Поэтому страница в общем случае не знает, по какому пути она расположена. Об этом знает роутер.
Получается страницу больше беспокоят ее параметры, чем конкретный путь / роут. Возможно так же страницу беспокоит, параметр пришел из пути запроса или квери стринги - может повлиять на локальное кеширование.
Можно себе представить другой интерфейс, например чат-бот, и тогда функцию pathname будет выполнять запрос пользователя.
Во-первых, в статье я не видел, чтобы были озвучены такие залачи, чтобы роутер работал и для чат бота и для web UI и для голосового управления.
Во-вторых, в таком случае просто необходимы отдельные роутеры для чат бота и для web UI и для мобилки и для десктопа. Тут встаёт вопрос внутренней навигации между страницами. В таком (и только в таком) случае, имеет смысл уже делать какую-то абстракцию, помня, что любая абстракция более ограниченна, чем любая конкретная реализация. И тогда будет верхнеуровневый интерфейс роутера, в который будет передаваться ключ роута и его параметры (желательно это типизировать как-то). Далее к этой новой абстракции будет написано несколько адаптеров - один на react-routerдля web UI, другой для чат-бота и тд. Соответственно будет 2 точки входа (в самом приложении может быть одна, но конфигурации будет 2, соответственно 2 ручки для запуска с разными конфигурациями и корнями композиции). И тогда уже можно говорить, что такой роутер имеет смысл для решения вашей конкретной задачи с вашими контрактными ограничениями. Но абстракция этого роутера будет ограничена - будет просто ключ роута и параметры. Но не будет возможности определить, параметры пришли из пути запроса или из квери стринги или из deep link и тд (пока не произойдет приведения более общего роутера к более частному, что сломает абстракцию, и все равно привяжет имплементацию к частному).
Если смотреть с такой точки зрения - тогда всё ещё Ваше решение слишком сложное. Достаточно сделать `IAppRouter.navigate(key, params)` и forward/back/refresh/currentRoute и сделать разные адаптеры под разные контексты. И потом в страницу делать инъекцию роутера (по интерфейсу) и давать смотреть параметры текущего роута. Выбор адаптера происходит либо в build time в зависимости от конфигурации билда или в runtime в зависимости от переданной конфигурации. И ещё до кучи допилить свои интерфейсы аналоги react-router вроде Link routeKey, routeParams и тд.
Далее делаете один мок для IAppRouter и проверяете, что страница себя ведёт так, как ожидается, в зависимости от currentRoute, а так же, что вызываются соответствующие методы, когда Вы того ожидаете, вроде navigate,back,forward при нажатии на ту или иную кнопку. Все тестируется и не требует долго разбираться в Вашем уникальном подходе.
И OCP будет заключаться не в том, как легко добавлять новые страницы страницы, а в том, как легко добавлять новые типы роутеров.
Ноу, верхнеуровневый модуль это тот, который управляет низкоуровневым. Для простоты понимания - react-router определяет интерфейс: как задавать роуты, какие есть параметры, какой жизненный цикл и тд.
В контексте использования роутера из кода приложения - это отношение use, т.е. прямая зависимость. Не инверсированная.
Рискну утвердить, что Вы слишком глубоко погрузились в идеи SOLID, Clean Architecture и в целом в Энтерпрайз паттерны. У Вас в руках появился молоток, и теперь все вокруг кажется гвоздями.
Это не посредник, а сервис-локатор. Кроме того, это нарушает OCP.
По Dependency Inversion верхнеуровневый модуль здесь это react-router. Код в файле router это низкоуровневый код, по сути конфигурация роутера. Именно react-router обеспечивает OCP за счёт возможности модифицировать его поведение, внедрять хуки и ТД. Это необходимо, потому что роутер используется в миллионах приложений и надо сделать его максимально генерик. Ваше предложение не генерик - это вполне конкретное приложение с вполне конкретной конфигурацией. А конфигурация должна быть простой и достаточно часто она централизована (примеров этому масса в разных фреймворках).
Вы пытаетесь написать свой роутер. У которого своим "правильным" образом реализован DI, OCP и тд. И уже по результатам видно, что прямо кардинально лучше и чище конфигурация роутинга не стала. Можете просто сравнить число строк в вашей имплементации против react-router на вашем реальном боевом приложении. Да, теперь конфигурация децентрализованная. Ну так и у next js она децентрализованная. И dev experience получше у next js.
Если Вам нравится экспериментировать и искать более лучшее генерик решение и за счёт этого прокачиваться в кодинга - тогда ок. Но будьте осторожны с тем, чтобы тянуть такие эксперименты в боевые проекты, особенно долгоживущие.
Делайте run асинхронным. И не надо ничего маскировать. Нет ни одного случая, когда проблема описанная в начале статьи будет актуальной. Если верхнеуровневый по Dependency Inversion модуль требует синхронный интерфейс, чтобы дернуть низкоуровневый модуль, значит, сразу вызываем асинхронную инициализацию модуля низкого уровня, а потом уже дергаем верхнеуровневый модуль (прямой контроль). Либо через IoC (инвертированный контроль) по лайфсайклу все инициализируем.
По поводу обработки ошибок - обычного try/catch вместе с async/await будет достаточно в обоих случаях, если код просто синхронный и код асинхронный. Даже извращение в виде T | Promise<T> должно нормально поддерживаться, не проверял, но по идее обернуть в один try/catch обработку результата такой функции будет достаточно, если не забыть дернуть await для кейса Promise<T>.
Иначе получаем инструмент, который как будто стимулирует писать более сложный код без крайне веской на то причины. С другой стороны - бесплатно и пусть будет, мастер найдет применение.
20 лет прошло с момента диссертации Роя Филдинга, а разработчики до сих пор не могут отличить REST от HTTP RPC.
REST это про гипермедиа, HATEOAS, возможность задать поведение клиента с сервера. URL в REST это адрес ресурса и ключ кеширования, он вообще не обязан иметь какой-то четкой структуры. GET без body нужен, чтобы передавать все параметры запроса в строке запроса и иметь возможность закешировать ответ на любом из прокси, чтобы не читать body. PUT, PATCH, DELETE и тд являются вариациями POST (того же эффекта можно добиться используя POST и комбинации заголовков для манипуляции кешом на клиенте); именно поэтому в HTML 5 до сих пор есть только формы GET & POST.
А вы рассказываете про RPC поверх HTTP. И там хоть делайте хоть PATCH /orders хоть POST /update-order, скорее всего вы не будете использовать заголовки для кешироания в своих микросервисах и дадите клиенту вашего сервиса (разработчику) самому решать, что кешировать, а что нет. И уж тем более у вас не будет поддержки HATEOAS.
Рецепт такой - вы пишете веб интерфейс используя ASP.NET Core или NextJS (конечный потребитель это человек через браузер) - вы уже используете REST. Вы пишите микросервисы (конечный потребитель разработчики, которые интегрируют ваш АПИ в свои приложения) - вы используете RPC.
Согласен. Я писал об этом в данном комментарии к другой статье. https://habr.com/ru/articles/926214/comments/#comment_28553346
Я бы сказал так - BFF и API Gateway это логические единицы. Физически это может быть даже один контейнер, в котором будет приложение, которое по путям /app, /checkout, /admin или по заголовку host будет маршрутизировать запросы на соответствующий модуль BFF или Gateway. А логическое ядро (команды, запросы, оркестраторы, бэкграунд обработчики) будет так же запущено в рамках того же приложения. Получаем модульный монолит. Такая архитектура знакома многим, кто разрабатывал приложения до появления тренда на микросервисы - тот же классический WordPress яркий пример такой архитектуры. Там модули вообще можно было подключать как плагины. И все на одном сервере.
Если модули не спутаны между собой, то всегда можно взять какую-то особо нагруженный модуль и при явной необходимости вынести его в отдельный инстанс/инстансы и масштабировать его независимо. То же самое и с базой данных. Может вообще оказаться, что инстанс приложения тянет и его масштабировать не надо, а БД не тянет. Для высокой нагрузки - соответствующее решение с соответствующим SLA. Для низкой нагрузки - простое решение. Например, модуль поиска видео и просмотра видео очень нагруженный и может быть потребоваться десятки инстансов БД, с репликацией и ТД чтобы выдержать нагрузку и обеспечить высокую доступность и отказоустойчивость. Эти сервера могут даже географически быть раскинуты, для обеспечения SLA. А вот модуль пользовательских плейлистов кратно менее нагружен и куда проще (т.к. там скоуп не весь сервис или категория видео, а конкретный пользователь). С таким на десятки тысяч активных пользователей и один относительно небольшой инстанс справится, да и требования к доступности и устойчивости у него наверняка поменьше. Ну не получилось добавить в свой плейлист видео, неприятно, но не конец света. На крайний случай можно на клиенте записать, что была попытка добавить и потом в бэкграунде обновить данные на сервере.
Т.е. на мой взгляд проблема не в микросервисах. А в преждевременной оптимизации, которую инициируют сами разработчики. Может ими движет психологический аспект, желание показать свои "лучшие" навыки. Может они реально верят, что делают "лучше". А по факту получается пушка для стрельбы по воробьям. И когда адепты прагматизма смотрят на объективно переусложненную для решения текущих задач систему, они понимают, что огромное количество вещей сделано "чтобы было, на будущее"; и это будущее не наступило, а цена уже заплачена, и немаленькая и счета приходят до сих пор.
UPD: А так как объективно число пользователей интернета ограничено, как и их время в сутках, то не может быть так, чтобы у всех приложений был трафик как у Озон, Вайлдберриз, Амазон, Гугл и тд. Т.е. чаще всего приложениям пользуются пару тысяч человек в общем случае одновременно, и тут объективно чаще всего монолита хватит. Даже чуть вскрою карты, я работаю в крупнейшем провайдере IP телефонии в Серверной Америке. У нас трафик на самых нагруженных сервисах, о которых мне известно, оценивается тысячами RPM, максимум десятками тысяч в самое пиковое время. И микросервисная архитектура используется во многом для независимого деплоя модулей, недели для масштабирования. Хотя там такая связность, что даже и эта возможность почти никогда не работает. Буквально многие сервисы могли быть сделаны монолитами и ничего бы в худшую сторону не изменилось.
Все куда проще. Есть клиент, например, клиентский веб портал. Вам нужно получить информацию из системы в разрезе данного конкретного пользователя. Нужно пройти аутентификацию и при отображении интерфейса делать срез / представление для данного пользователя. Бэкенд же генерик. Он позволяет мутировать данные и получать данные. Он не беспокоиться, а кто их запросил, а есть ли у него права. Он концентрируется на бизнес логике (данные и их мутации). Работу по созданию проекции для пользователя, агрегации данных и ТД выполняет BBF. Такой же BFF может быть и для приложения портал авторов, которое взаимодействует с тем же бэкендом, но уже в другой проекции, другими правилами авторизации и тд. И третий BFF будет у приложения бэк офиса, где например модерируется контент. Например в веб приложении могут использоваться к http-only cookie для хранения полезной инфы, в том числе авторизации; а мобильные приложения могут использовать более удобный для них jwt.
Чем отличается от API Gateway? Только степенью узконаправленности: API Gateway широкое направлен и чаще всего обслуживает публичный АПИ. Это выражается во всем: от широты спектра возможностей авторизации: oAuth2, API ключи и до очень обобщенного контракта API, чтобы можно было сделать буквально любого клиента или любую интеграцию.
Мой рецепт такой - если клиент "ваш" нативный - делайте БФФ, в перспективе не будет ошибкой 100%. Если вам нужно предоставит точки интеграции "внешним" сервисам в рамках вашей компании или других компаний - делайте API Gateway и проектируйте публичный API. При этом в обоих случаях ядро системы будь то монолит или микросервисы предоставляют только операции с бизнес логикой + оркестраторы или менеджеры процессов, не парятся за авторизацию и доступны только в приватной сети. За авторизацию и пользовательский контекст отвечают BFF и API Gateway.
В реакт нет механизма запуска рендера без изменения состояния или пропсов.
ASP.NET MVC — это супермощная вещь, но она имеет ряд недостатков по сравнению с альтернативами.
Интерактив на странице реализовывать куда менее удобно, чем, например, в Next.js. Да, у вас доступна вся мощь ванильного JavaScript, HTMX, Bootstrap — и добавить на страницу карусели, модальные окна или простые формы вполне реально. Этого достаточно для части задач, например при рендеринге простых сайтов. Однако создать полноценный интерактивный мультифункциональный портал для клиента — вроде банковского — будет значительно сложнее (хотя и возможно: как-то же раньше справлялись, хотя современные фреймворки как раз и пришли на смену этому «как-то»).
Выбор UI-китов крайне ограничен, и большинство из них постепенно устаревают. В современном мире UI-киты чаще всего выпускаются под конкретные фреймворки. Да, бывают версии с «чистым» HTML, но их немного — и у них возникают серьёзные вопросы по объёму «копипасты»: чтобы создать модальное окно или форму с валидацией, приходится вставлять в 2–3 раза больше кода, чем в случае с компонентами.
С другой стороны, это же и плюс: работа на самом низком уровне с HTML, CSS и ванильным JavaScript заставляет искать максимально простые и прямолинейные решения там, где это возможно.
Если же рассмотреть Blazor, он тоже оказывается довольно компромиссным решением:
Да, интерактивность реализовывать проще, и удобно переиспользовать код между сервером и клиентом. Однако всё это присутствует и в Next.js — причём в куда более продвинутом виде и нативно, на «родных» веб-технологиях, а не через WebAssembly и JS Interop.
А сам JS Interop — это головная боль. Да, можно использовать JS-библиотеки, но там, где TypeScript подсвечивает синтаксис и проверяет типобезопасность на этапе компиляции, в Blazor все проверки смещаются в рантайм (включая тесты).
В целом экосистема Blazor развита слабо: под React сотни UI-китов и библиотек, а в Blazor основную работу ведут энтузиасты.
Поэтому каждый раз, когда я думаю запускать новый SaaS-пилот и пишу бэкенд на .NET, руки тянутся «ускорить» разработку, взяв ASP.NET Core или Blazor. Но, трезво взглянув на реальность, я понимаю: гораздо быстрее написать весь фронт на Next.js — там уже есть нужный UI-кит, удобные компоненты, сборка через Vite, минимизация, оптимизация, сжатие изображений — всё настроено «из коробки». Так что на ASP.NET MVC я уже смотрю с лёгкой ностальгией, хотя и пишу API на .NET.
P.S. При этом Next.js и вся Node.js-экосистема, конечно, дико кривые. Не далее как сегодня я узнал, что в Node.js HTTP-сервере жёстко задан таймаут запроса — 5 минут. Его можно переопределить, но только глобально: установка таймаута для отдельных запросов не работает. А багу этому уже за 100 лет в обед, и никаких движений по её исправлению. Ладно, это ещё можно обойти. Но Next.js не позволяет переопределять этот таймаут через свои настройки — только патчингом исходников. Ишью на GitHub по этому поводу висит уже несколько лет. И таких «подводных камней», на которых можно зависнуть на целый день, хватает — стоит лишь выйти за рамки простого рендеринга. Но всё равно, как ни странно, получается быстрее, чем на ванильном стеке, за счёт универсальности серверного и клиентского рендеринга.
Статья - "Тотэнхэм". И вроде не наврал нигде, но вроде все описано очень поверхностно. Ничего нового для себя не открыл.
Я как-то вступил в достаточно жесткое противостояние с менеджментом на тему Agile и Scrum. Менеджмент требовал полного соответствия комитменту, чтобы велосити был не толькл стабильным, так ещё и рос каждые несколько спиртов, т.к. якобы наша компетенция растет. Причем когда мы говорили, что задача на несколько спринтов и ее хрен нарежешь, заставляли нарезать, и очевидно резали почти наугад и само собой в 90% случаев не попадали, потому что то, что думали будем делать долго, делали быстро, а то, что думали сделаем быстро - делали долго. И рапорты каждый раз по одному месту. И никак этого не избежать, т.к. делали уникальный продукт (аля продвинутый зум со встроенным OBS на webrtc). И менеджмент сам не понимал - результаты на дистанции несколько месяцев феноменальные, а отчёты непонятные. Их прямо ломало от этого, они не могли четко "планировать". Хотя какое тут планирование, неизвестность за каждым углом, сегодня план один, завтра другой и это и есть Agile в своей крайней степени.
Я же объяснял, что менеджменту надо сфокусироваться на продукте, ценности и на оценке ситуации, а не на показателях отчётов по спринту в Jira. Задача будет сделана тогда, когда будет сделана. На планинге скоуп и риски оцениваются приблизительно, на основе того, что команда знает в момент оценки о задаче. Далее команда даёт ежедневный статус апдейт, какое состояние, переоценивает риски. Менеджер должен внимательно слушать, анализировать и если нужно менять вектор, забивая на цели спринта и коммитменты, если появляется что-то более приоритетное или оказывается, что риски были взвешены неправильно и надо срочно менять тактику. О том, почему они были взвешены неправильно и можно ли было что-то изменить обсудите уже на ретро, но выкореживаться, чтобы получить "хороший" отчёт вредит вообще всем.
В этом плане объективно самым гибким методом является scrumban - нет никаких спринтов, команда работает по обстоятельствам. При этом присутствует цикличность ритуалов вроде рефаймерта, демо, ретро, иногда даже хай левел планинга. Но для этого нужно, чтобы либо команда была самоорганизована и сама драйвила продукт (0.1% команд на такое способен), либо чтобы был толковый менеджер, а не "менеджер по методичке".
Менеджеры обиделись, не уверен, что прислушались. А я забил болт на их нытье и делал по кайфу, т.к. знал, что делаю максимум и с точки зрения исполнения быстрее команда не сделает.
Главная фича ORM это ченж трекинг и маппинг. И особенно удобно это в CQRS на стороне Command. Достаете из репозитория агрегат (который может быть сущностью вроде Order с вложенным OrderItems), за счёт маппинга агрегат собирается автоматически по довольно простым правилам маппинга описанным декларативно; далее мутируете агрегат, соблюдая инварианты, и далее вызываете "Сохранить". Все, ОРМ сам высчитает diff, постарается его применить к конкретной базе которая используется на проекте, применит оптимистичную конкуренцию и тд.
При этом использование ORM на стороне Query опционально и зачастую лишнее. Сторона Query в базе настолько многогранна, что буквально есть 100 способов сделать одно и то же, и в этом случае ORM всегда будет отставать от драйвера базы и будет ещё слой своих багов накидывать.
Если я использую тактические паттерны DDD на проекте, выделяю явно слой домена, агрегаты и ТД, то у меня всегда используется ОРМ, потому что это очень удобно.
Если же я просто пилю всю логику в апликейшен слое с анемичной моделью, то часто достаточно обычного драйвера базы; да, технический код подмешивается в апликейшен, но если я уже начал так делать, то скорее всего данной модуль слишком примитивен, чтобы заморачиваться с ним по DDD.
Да нет никакой субординации на собеседовании, это ваш конкретный загон.
Нантмающая сторона по своим субъективным критериям за выделенное время пытается понять, подходит ли ей специалист. Специалист пытается понять, подходит ли ему данная компания по своим субъективным критериям. Дальше либо матч есть либо нет. Кому-то подходит модель "начальник-подчиненный", кому-то более неформальный подход, а кто-то не смотрит ни на что кроме суммы офера.
Субъективность критериев на нанимающей стороне варьируется от того, насколько строгий фреймворк собеседований в компании - может быть очень строгий, унифицированный и мало зависящий от интервьюера, а может быть менее строгий, свободный и соответственно более зависящий от восприятия интервьюера. Я имею в виду то, какие вопросы задавать, какие ответы ожидать и на какие вопросы отвечать и как.
А так же баланс лояльности между соискателем и нанимателем постоянно дрейфует от состояния рынка: мало кандидатов - ниже требования на ту же позицию и кандидаты менее лояльны; много кандидатов - выше требования к кандидатам на ту же позицию, и кандидаты более лояльны к "особенностям" работодателя.
Я всегда нормально отношусь к вопросам ко мне со стороны соискателя. Просто делаю себе пометку и выделяю время на его вопросы в конце интервью, в том числе и технические вопросы. Обычно у меня интервью это полтора часа, 15 минут в конце на вопросы кандидата. Если у нас обоих есть время и вопрос интересный, могу задержаться ещё на минут 10 чтобы закончить обсуждение. Если на мой взгляд кандидат не подходит, то когда выйдет время на вопросы кандидата - вежливо ему об этом сообщу и вежливо отклоняю предложение остаться после интервью.
Более того, в работе я лично предпочитаю модели без субординации, а с зонами ответственности и компетенции. Условно я всегда должен иметь возможность дать обратную связь своему линейному менеджеру или иметь возможность высказать свое мнение о его решении. Но его решение остаётся его решением и ему за него и отвечать. Ровно как и я как тех лид могу придти с решением к команде, она может дать обратную связь. В конечном итоге мы придем к варианту, что есть несколько сопоставимых опций из которых надо выбрать и финальное решение за мной, т.к. это моя ответственность. Т.е. в такой схеме минимизируется "я начальник ты дурак". Все прозрачно, какие опции рассматриваются, какие у них плюсы и минусы и какое финальное решение и почему.
Я за собой начал замечать, что быстро теряю интерес к проекту, когда он переходит в стадию поддержки. Начитается тягомотина, скучные задачи (например, недавно мигрировали микросервисы 10-тилетнец давности на ARM, тупо сиди обновляй библиотеки и запускай тесты и правь код, пока не заработает), исправление трудно воспроизводимых багов и тд. Я чувствую, что с этим практически с такой же скоростью справился бы любой миддл, потому что такой характер работы, компетенция особо в ней не решает.
Я наблюдал ситуацию, когда проект запускала одна команда спецов, которая самоорганизованно собирала требования, проектировала, строила архитектуру, готовила инфраструктуру и ТД и запускала проект буквально за пару месяцев. Потом подмешивали в течении пару месяцев в в команду нормесов, чтобы они занимались поддержкой и обслуживанием. А спецы возвращались в пул для быстрого и качественного запуска нового проекта. Иногда оставляли капасити для консультации по разработанным проектам или подключались для нового раунда крупных доработок.
Таким образом каждый психотип при деле. Не надо таких спецов заставлять сидеть на поддержке (часто удерживают высокими ЗП, и сложно соскочить обратно в рынок) и не надо заставлять нормесов брать на себя большую ответственность и предоставлять им автономию.
В этом и суть менеджера, найти и почувствовать лучшую комбинацию.
Сейчас перекос рынка в сторону работодателя, поэтому будучи соискателем не всегда есть возможность разгуляться, на многие "Ред флаги" начинаешь закрывать глаза. То же самое было, когда рынок был сдвинут в сторону соискателя, нанимали всех подряд и так сильно не придирались.
Ну так покажите такому спецу, что вы можете и без него сделать. Потому что часто он реально берет на себя, потому что "никто другой не сможет". Буквально нет человека, который вызовется это сделать. Или же бывают выскочки, которые берутся, но все равно история заканчивается тем, что идут к такому спецу, потому что сами не справляются.
А знаете как справляется такой спец-единоличник? Он не закрывает ноутбук в 6 вечера, он сидит после работы, иногда до ночи, иногда на выходных. И именно так он и стал спецом. И когда в команде вроде кто-то взял задачу, спец морально настроился максимум чуть поссаппортить, а в итоге все скатывается в то, что ему приходится опять сидеть тащить, потому что релиз уже на носу, а сделано 10% максимум - теряется доверие.
И вот тут тоже проблема лида и менеджмента. Им такой спец удобен. Они не хотят менять тех середнячков, которых набрали, на спецов покруче. Не хотят планировать лучше. У них есть такой спец, так что все будет в шоколаде (да и по деньгам он получает на 15-20% больше всего по сравнению с нормесами). И это бесконечный цикл, который может разорвать только сам спец, но осадок все равно останется, либо нормальный менеджер.
Но я бы таких людей винить не стал и на самом деле они кажутся самыми перспективными из всех, кого можно нанять. Потому что: он уже доказал, что раз он взялся, то он сделал; он доказал, что хватает обучаемости находить решение любым проблемам; накопленна большая компетенция. Такого спеца в команду к толковому и вайбовому менеджеру, и это будет очень мощный симбиоз.
Причем я не говорю, что сидеть после работы норма. Таких спецов часто (до поры до времени) прет от сложности задачи и это как босс в Дарк Соулс - особый вид эндорфина. Но при этом есть особый аспект, что нет дедлайна на убийство босса в Дарк Соулс, от этого не зависят другие люди и ТД, а в от выполненной задачи зависит, поэтому ещё интереснее и азартнее, + элемент творчества. Но такие способности и страсть надо уметь направить, иначе получите выгоревшего сотрудника... Хотя кому сейчас не пофиг, найдут нового)
Очень спорное утверждение.
REST не REST без гипермедиа. В общем смысле Гипермедиа предназначен для общения с клиентом (браузером) - отдавать контент, управлять кешом, управлять куками и тд. И там формат URL клиента не волнует: вы часто смотрите на URL в браузере, когда лазите по Хабру? или сходу введете в адресной строке url своей статьи? или статьи, которую читали 5 минут назад?. Думаю, нет. Потому что URL в REST выступает идентификатором ресурса для вашего браузера и одновременно ключом кеширования, а не красивым путем для предсказуемости разработчика или пользователя.
В вашем случае описана коммуникация машины с машиной -> RPC over HTTP. Даже если это SPA вызывающие Web API - то URL, которые вызываются из SPA заботят только разработчика, а не пользователя приложения.
Это я к тому, что уже пришла пора перестать заблуждаться и называть API данного типа REST. Достаточно прочитать эту статью от создателя REST, чтобы все встало на свои места.
Да, некоторые концепции из REST используются при разработке Web API, например клиент-сервер и stateless. Но описанные в статье правила это это правила создания RPC сервисов поверх HTTP.
Ресурсориентированный подход хорошо себя показывает только для CRUD. Если мы говорим о чем-то посложнее, начинается появление костылей при использовании этого подхода (пример 1, пример 2, пример 3).
Лично я не вижу в этом смысла. Мне ближе что-то вроде подхода использованного в Slack. Понятная операция, понятные аргументы, не надо ничего выдумывать в дизайне.
Это интерпретация принципа Dependency Inversion. А точнее констатация.
Чистая архитектура это про отделение инфраструктуры от аппликейшена, нужна для того, чтобы можно было менять инфраструктуру не меняя код бизнес логики. Или разрабатывать и тестировать бизнес логику в отрыве от инфраструктуры (не важно какой сервер, сеть, диск, база данных и ТД), а потом подключать адаптерами ту инфраструктуру, которая лучше подходит для обеспечения бизнес логики.
UI (весь) это только один из адаптеров к входящим портам бизнес логики: web UI, консоль, HTTP API, RPC, чат бот. Может ли быть полезным проектировать адаптеры по принципам чистой архитектуры? Может, если Вы проектируете фреймворк, на котором потом будет реализовываться различные адаптеры.
В вашем случае архитектура библиотеки может быть и чистая, но это не значит, что у реакт роутера она не чистая. Поэтому вопрос здесь только в удобстве реализации адаптера, и реакт роутер пока выглядит лучше. Как минимум "гейм ченжинг" этот подход точно не назовешь.
Даже синтаксически, я бы вместо того, чтобы добавлять код проверки роута внутрь самого компонента и если не матч вернуть null - сделал бы что-то вроде
Будем считать это псевдокодом. И в билд тайме обходил бы дерево компонентов и генерировал бы граф страниц, подкидывал их в роутер в билд тайме опять же и потом в рантайме на каждую навигацию вызывал matchRoute для каждого компонента, у которого нашлась такая функция. И рендерил бы первый компонент, для которого matchRoute вернул тру.
Ответил выше.
Весь этот код расположен в роутере. Поэтому страница в общем случае не знает, по какому пути она расположена. Об этом знает роутер.
Получается страницу больше беспокоят ее параметры, чем конкретный путь / роут. Возможно так же страницу беспокоит, параметр пришел из пути запроса или квери стринги - может повлиять на локальное кеширование.
Во-первых, в статье я не видел, чтобы были озвучены такие залачи, чтобы роутер работал и для чат бота и для web UI и для голосового управления.
Во-вторых, в таком случае просто необходимы отдельные роутеры для чат бота и для web UI и для мобилки и для десктопа. Тут встаёт вопрос внутренней навигации между страницами. В таком (и только в таком) случае, имеет смысл уже делать какую-то абстракцию, помня, что любая абстракция более ограниченна, чем любая конкретная реализация. И тогда будет верхнеуровневый интерфейс роутера, в который будет передаваться ключ роута и его параметры (желательно это типизировать как-то). Далее к этой новой абстракции будет написано несколько адаптеров - один на
react-routerдля web UI, другой для чат-бота и тд. Соответственно будет 2 точки входа (в самом приложении может быть одна, но конфигурации будет 2, соответственно 2 ручки для запуска с разными конфигурациями и корнями композиции). И тогда уже можно говорить, что такой роутер имеет смысл для решения вашей конкретной задачи с вашими контрактными ограничениями. Но абстракция этого роутера будет ограничена - будет просто ключ роута и параметры. Но не будет возможности определить, параметры пришли из пути запроса или из квери стринги или из deep link и тд (пока не произойдет приведения более общего роутера к более частному, что сломает абстракцию, и все равно привяжет имплементацию к частному).Если смотреть с такой точки зрения - тогда всё ещё Ваше решение слишком сложное. Достаточно сделать `IAppRouter.navigate(key, params)` и forward/back/refresh/currentRoute и сделать разные адаптеры под разные контексты. И потом в страницу делать инъекцию роутера (по интерфейсу) и давать смотреть параметры текущего роута. Выбор адаптера происходит либо в build time в зависимости от конфигурации билда или в runtime в зависимости от переданной конфигурации. И ещё до кучи допилить свои интерфейсы аналоги
react-routerвродеLink routeKey, routeParamsи тд.Далее делаете один мок для
IAppRouterи проверяете, что страница себя ведёт так, как ожидается, в зависимости отcurrentRoute, а так же, что вызываются соответствующие методы, когда Вы того ожидаете, вродеnavigate,back,forwardпри нажатии на ту или иную кнопку. Все тестируется и не требует долго разбираться в Вашем уникальном подходе.И OCP будет заключаться не в том, как легко добавлять новые страницы страницы, а в том, как легко добавлять новые типы роутеров.
Ноу, верхнеуровневый модуль это тот, который управляет низкоуровневым. Для простоты понимания -
react-routerопределяет интерфейс: как задавать роуты, какие есть параметры, какой жизненный цикл и тд.В контексте использования роутера из кода приложения - это отношение use, т.е. прямая зависимость. Не инверсированная.
Рискну утвердить, что Вы слишком глубоко погрузились в идеи SOLID, Clean Architecture и в целом в Энтерпрайз паттерны. У Вас в руках появился молоток, и теперь все вокруг кажется гвоздями.
По Dependency Inversion верхнеуровневый модуль здесь это
react-router. Код в файлеrouterэто низкоуровневый код, по сути конфигурация роутера. Именноreact-routerобеспечивает OCP за счёт возможности модифицировать его поведение, внедрять хуки и ТД. Это необходимо, потому что роутер используется в миллионах приложений и надо сделать его максимально генерик. Ваше предложение не генерик - это вполне конкретное приложение с вполне конкретной конфигурацией. А конфигурация должна быть простой и достаточно часто она централизована (примеров этому масса в разных фреймворках).Вы пытаетесь написать свой роутер. У которого своим "правильным" образом реализован DI, OCP и тд. И уже по результатам видно, что прямо кардинально лучше и чище конфигурация роутинга не стала. Можете просто сравнить число строк в вашей имплементации против
react-routerна вашем реальном боевом приложении. Да, теперь конфигурация децентрализованная. Ну так и у next js она децентрализованная. И dev experience получше у next js.Если Вам нравится экспериментировать и искать более лучшее генерик решение и за счёт этого прокачиваться в кодинга - тогда ок. Но будьте осторожны с тем, чтобы тянуть такие эксперименты в боевые проекты, особенно долгоживущие.
Просто перед тем как делать, надо думать.
Делайте
runасинхронным. И не надо ничего маскировать. Нет ни одного случая, когда проблема описанная в начале статьи будет актуальной. Если верхнеуровневый по Dependency Inversion модуль требует синхронный интерфейс, чтобы дернуть низкоуровневый модуль, значит, сразу вызываем асинхронную инициализацию модуля низкого уровня, а потом уже дергаем верхнеуровневый модуль (прямой контроль). Либо через IoC (инвертированный контроль) по лайфсайклу все инициализируем.По поводу обработки ошибок - обычного try/catch вместе с async/await будет достаточно в обоих случаях, если код просто синхронный и код асинхронный. Даже извращение в виде T | Promise<T> должно нормально поддерживаться, не проверял, но по идее обернуть в один try/catch обработку результата такой функции будет достаточно, если не забыть дернуть await для кейса Promise<T>.
Иначе получаем инструмент, который как будто стимулирует писать более сложный код без крайне веской на то причины. С другой стороны - бесплатно и пусть будет, мастер найдет применение.