Ну так в SPA у вас тот же код, который раньше был серверным, теперь на клиенте.
С чего бы это серверному коду быть на клиенте? Да и каким образом это возможно, ПХП выполнялся на сервере, реакт и другие — на клиенте. Он при всем желании не полезет в базу, не прочтет файл с диска на сервере и т.п.
Легко догадаться, что так делать не надо.
Знаете, я был практически уверен, что вы скажете "зелен виноград".
Так делать не хочется, пока не попробуешь :) Но это же мощнейший инструмент, который позволяет кучу задач (особенно при разработке библиотек и компонентов) решить изящным способом.
По сути, это и есть полноценная композиция компонентов, а не огрызок, как в Ангуляре.
Во втором ангуляре шаблоны компилируются в js-объект, точно так же, как jsx.
Совсем не так же. В Реакте можно оперировать результатами там же, в коде, то есть это по сути другой вид записи. В Ангуляре результат компиляции коду компонента не доступен.
Но вот только в ПХП был серверный код бизнес-логики вперемешку с HTML.
Дело не в JSX, конечно же. Есть несколько альтернатив ему, в том числе, и весьма похожие на шаблонизатор Ангуляра, которые позволяют писать как бы HTML, и компилировать его в вызовы createElement.
Но их никто не использует. Потому что настоящая мощь Реакта — в том факте, что компоненты — это обычные JS объекты. Я не устаю писать это в каждом комментарии в сраче о JSX.
Шаблон Ангуляра — это, конечно, похоже на ХТМЛ (особенно, если ты видел пример из getting started и не видел реальный production код). Но это все-равно строка.
И если нужно взять все вложенные компоненты, и оставить только часть из них по условию — то начинаются костыли. Но для этого хотя бы есть средства.
А вот как взять потомков одного типа, и склонировать их для каждого элемента массива и отрендерить в отдельное место? Или обернуть их в другой компонент? В реакте это делается элементарно. В языках с шаблонизаторами это либо вообще нельзя сделать (как в ангуляре 1), либо делается через ковыряние в потрохах (как во Вью).
Редакс прекрасно типизируется. С type-guards типизируются редьюсеры. connect вообще-то типизированный, но с отвратительными тайпингами, которые не используют новые фишки Typescript вроде mapped types.
Это исчезает из многих роутеров, например, из последнего react-router.
Для этого есть причина, как я понял, — это поддержка сложных динамических урлов, нефиксированной вложенности и прочих редких случаев. По сути, отдельной страницы для урла может и не быть, будет лишь динамический набор компонентов.
Однако, отсутствие opt-out статического именованного роутинга реально напрягает, сейчас он нужен в 90% случаев.
Формы в редаксе — это головная боль, конечно. В основном сложности возникают, когда пытаешься перенести "подход ангуляра" с двусторонним байндингом, валидацией инпутов и прочим на реактивность и иммутабельность.
Для себя я многое переосмыслил в подходах к разработке форм с редаксом, когда понял, что ошибки валидации не нужно нигде хранить (кроме тех, конечно, которые пришли с сервера). Их нужно вычислять.
Я имел ввиду объект, который содержит методы типа ПолучитьВсехАктивныхПользователей, ОбновитьОнлайнСтатус, и т.п., которые отражают предметную область.
Спецификации — это (компонуемое) описание операций над набором данных, по сути + универсальный метод их исполнения.
Мне нравится репозиторий как набор запросов.
Только не один гигантский на весь проект, а набор маленьких по сущностям и фичам.
В чем преимущество
легко мокать (если они маленькие).
сразу видно какие именно операции с базой могут выполняться
При этом необязательно отказываться от всего описанного в статье: репозиторий вполне может возвращать IQueryable, и к нему можно применять спецификацию.
Обобщенный репозиторий плох тем, что он не ограничивает никак ни чтение данных (т.к. доступна вся таблица), ни запись.
Свой же репозиторий может в методе All() возвращать не всю таблицу, а только доступные пользователю записи, например.
Также репозиторий как набор запросов позволяет легко и безболезненно для остального кода проводить оптимизацию отдельных методов, перенося их в базу как функции или вьюшки.
Насколько я понимаю, в CQRS зависимость почти всегда односторонняя: обработчики команд могут обновлять данные для чтения, но никогда не читают их.
В случае, когда нужно перед записью данных их прочитать (а нужно практически всегда), то читают из данных для записи (если нет event sourcing, а данные реляционные), или обработчик команд имеет внутреннее состояние, которое и используется для принятия решений (и обновляется при вызовах команд) — это в случае, если есть ивент сорсинг и данные для записи так просто не прочесть.
Ну и опять же, даже в случае с ивент сорсингом, можно хранить дополнительные реляционные данные (хотя это и влечет дополнительные проблемы).
CQRS, если он с командами и event sourcing-ом, имеет много больше точек оптимизации, плюс некоторые фичи, типа получения состояния на любой момент времени.
И разделение кода на кучки — это так само получается, если все это реализовывать.
ПС Разделение кода на C и Q — это, вообще-то, здравая идея, которая называется CQS (command-query separation). Этот принцип говорит, что каждый метод в приложении должен либо возвращать данные, либо их модифицировать, но модификация при чтении — это плохая идея.
redux очень похож на CQRS+event sourcing. Данные для чтения — это state, данные для записи — это бекэнд. В серверную архитектуру его, конечно, один в один не перенести, но подходы и проблемы очень похожи.
В реальных приложениях практически невозможно использовать "чистые" команды, которые не возвращают результата. Нужно будет обрабатывать ошибки, а также получать какие-то минимальные результаты, например, идентификаторы созданных сущностей и т.д.
Не стоит использовать CQRS без необходимости, его нужно применять только в самых нагруженных местах (или в системах, которые хорошо на него ложатся, например, основанных на событиях, типа приложений для такси). Реализовывать на нем CRUD — это удовольствие ниже среднего.
Только это будет не CQRS, и не будет его преимуществ. В CQRS данные для чтения имеют структуру, оптимизированную для чтения, поэтому чтение будет быстрое за счет более медленной записи. Например, вместо расчета прибыли при создании отчета (путем суммирования всех проводок) мы рассчитываем прибыль при создании каждой проводки.
В данных для записи консистентность будет строгая (как в обычном приложении). А вот между базами для чтения и для записи будет eventual consistency, то есть они будут согласованы когда-нибудь, но не прямо сейчас. Именно поэтому в обработчиках команд обычно не используется Read DB.
В CQRS данные для чтения (для запросов) могут отличаться от данных для обработки (для команд). Поэтому дублирования (и гонки) тут не будет, скорее всего.
С чего бы это серверному коду быть на клиенте? Да и каким образом это возможно, ПХП выполнялся на сервере, реакт и другие — на клиенте. Он при всем желании не полезет в базу, не прочтет файл с диска на сервере и т.п.
Знаете, я был практически уверен, что вы скажете "зелен виноград".
Так делать не хочется, пока не попробуешь :) Но это же мощнейший инструмент, который позволяет кучу задач (особенно при разработке библиотек и компонентов) решить изящным способом.
По сути, это и есть полноценная композиция компонентов, а не огрызок, как в Ангуляре.
Совсем не так же. В Реакте можно оперировать результатами там же, в коде, то есть это по сути другой вид записи. В Ангуляре результат компиляции коду компонента не доступен.
Но вот только в ПХП был серверный код бизнес-логики вперемешку с HTML.
Дело не в JSX, конечно же. Есть несколько альтернатив ему, в том числе, и весьма похожие на шаблонизатор Ангуляра, которые позволяют писать как бы HTML, и компилировать его в вызовы
createElement.Но их никто не использует. Потому что настоящая мощь Реакта — в том факте, что компоненты — это обычные JS объекты. Я не устаю писать это в каждом комментарии в сраче о JSX.
Шаблон Ангуляра — это, конечно, похоже на ХТМЛ (особенно, если ты видел пример из getting started и не видел реальный production код). Но это все-равно строка.
И если нужно взять все вложенные компоненты, и оставить только часть из них по условию — то начинаются костыли. Но для этого хотя бы есть средства.
А вот как взять потомков одного типа, и склонировать их для каждого элемента массива и отрендерить в отдельное место? Или обернуть их в другой компонент? В реакте это делается элементарно. В языках с шаблонизаторами это либо вообще нельзя сделать (как в ангуляре 1), либо делается через ковыряние в потрохах (как во Вью).
То же самое, первый Ангуляр любил до Реакта. Второй, правда, попробовал еще до Реакта и невзлюбил сразу.
Ну и опять же, недавно добавили неявный вывод типа, возвращаемого из функции, что могло бы помочь сделать человеческие тайпинги для коннекта.
Редакс прекрасно типизируется. С type-guards типизируются редьюсеры.
connectвообще-то типизированный, но с отвратительными тайпингами, которые не используют новые фишки Typescript вроде mapped types.Это исчезает из многих роутеров, например, из последнего react-router.
Для этого есть причина, как я понял, — это поддержка сложных динамических урлов, нефиксированной вложенности и прочих редких случаев. По сути, отдельной страницы для урла может и не быть, будет лишь динамический набор компонентов.
Однако, отсутствие opt-out статического именованного роутинга реально напрягает, сейчас он нужен в 90% случаев.
Формы в редаксе — это головная боль, конечно. В основном сложности возникают, когда пытаешься перенести "подход ангуляра" с двусторонним байндингом, валидацией инпутов и прочим на реактивность и иммутабельность.
Для себя я многое переосмыслил в подходах к разработке форм с редаксом, когда понял, что ошибки валидации не нужно нигде хранить (кроме тех, конечно, которые пришли с сервера). Их нужно вычислять.
Есть и встроенный в TS Server auto-import
Я имел ввиду объект, который содержит методы типа ПолучитьВсехАктивныхПользователей, ОбновитьОнлайнСтатус, и т.п., которые отражают предметную область.
Спецификации — это (компонуемое) описание операций над набором данных, по сути + универсальный метод их исполнения.
Ок, спасибо за поправку. Я употребил "репозиторий" в смысле объекта для доступа к данным в хранилище.
Мне нравится репозиторий как набор запросов.
Только не один гигантский на весь проект, а набор маленьких по сущностям и фичам.
В чем преимущество
При этом необязательно отказываться от всего описанного в статье: репозиторий вполне может возвращать IQueryable, и к нему можно применять спецификацию.
Обобщенный репозиторий плох тем, что он не ограничивает никак ни чтение данных (т.к. доступна вся таблица), ни запись.
Свой же репозиторий может в методе All() возвращать не всю таблицу, а только доступные пользователю записи, например.
Также репозиторий как набор запросов позволяет легко и безболезненно для остального кода проводить оптимизацию отдельных методов, перенося их в базу как функции или вьюшки.
Насколько я понимаю, в CQRS зависимость почти всегда односторонняя: обработчики команд могут обновлять данные для чтения, но никогда не читают их.
В случае, когда нужно перед записью данных их прочитать (а нужно практически всегда), то читают из данных для записи (если нет event sourcing, а данные реляционные), или обработчик команд имеет внутреннее состояние, которое и используется для принятия решений (и обновляется при вызовах команд) — это в случае, если есть ивент сорсинг и данные для записи так просто не прочесть.
Ну и опять же, даже в случае с ивент сорсингом, можно хранить дополнительные реляционные данные (хотя это и влечет дополнительные проблемы).
БД внутри — это как раз cqrs + event sourcing, они журнал операций ведут.
АПИ CQRS систем — это отражение их eventual consistency. То есть факта, что отправив команду системе, результат нужно получать другим способом.
Ну, в общем-то, да. Можно.
CQRS, если он с командами и event sourcing-ом, имеет много больше точек оптимизации, плюс некоторые фичи, типа получения состояния на любой момент времени.
И разделение кода на кучки — это так само получается, если все это реализовывать.
ПС Разделение кода на C и Q — это, вообще-то, здравая идея, которая называется CQS (command-query separation). Этот принцип говорит, что каждый метод в приложении должен либо возвращать данные, либо их модифицировать, но модификация при чтении — это плохая идея.
Если они не отличаются, CQRS не нужен.
Смысл его в том, чтобы оптимизировать производительность приложения, выполняя расчеты (трансформации данных) во время записи (а не во время чтения).
Поэтому же CQRS подходит только приложениям, у которых интенсивность чтения намного больше интенсивности записи.
redux очень похож на CQRS+event sourcing. Данные для чтения — это state, данные для записи — это бекэнд. В серверную архитектуру его, конечно, один в один не перенести, но подходы и проблемы очень похожи.
В реальных приложениях практически невозможно использовать "чистые" команды, которые не возвращают результата. Нужно будет обрабатывать ошибки, а также получать какие-то минимальные результаты, например, идентификаторы созданных сущностей и т.д.
Только это будет не CQRS, и не будет его преимуществ. В CQRS данные для чтения имеют структуру, оптимизированную для чтения, поэтому чтение будет быстрое за счет более медленной записи. Например, вместо расчета прибыли при создании отчета (путем суммирования всех проводок) мы рассчитываем прибыль при создании каждой проводки.
В данных для записи консистентность будет строгая (как в обычном приложении). А вот между базами для чтения и для записи будет eventual consistency, то есть они будут согласованы когда-нибудь, но не прямо сейчас. Именно поэтому в обработчиках команд обычно не используется Read DB.
В CQRS данные для чтения (для запросов) могут отличаться от данных для обработки (для команд). Поэтому дублирования (и гонки) тут не будет, скорее всего.