В приведенном вами примере из Point Free говорится о коллекциях, я говорил о случае двух отдельных модулей. Но как случай обобщения на n модулей и хранение их в коллекции пример хороший, спасибо.
По поводу namespace во вью, конечно namespace всегда можно вынести из вью, в случае Redux например в Action Creator. Но тема статьи же не про вью.
Касательно строгости функциональной архитектуры и правильных и неправильных подходов я, пожалуй, не буду комментировать :)
Спасибо за комментарий! Derived Behavior конечно же смотрели :) Если вы конкретно про этот пример, то тут скорее про переиспользование favorites: Set<Int> в двух разных модулях. Этому случаю у нас посвящен раздел "У модуля несколько родителей", а такой подход описан в блоке Computed Module State.
Конкретно использование Namespacable не считаем костылем, а просто одним из вариантов решения проблемы, никого не заставляем так писать :) Вот и вот примеры использования такого подхода в редаксе. В The Composable Architecture другой подход и у нас он упоминается в разделе "Иерархия экшенов".
Про инкапсуляцию в смысле механизма скрытия для UDF писали в одной из прошлых статей.
Отсутствие протоколов — это скорее следствие такого подхода к проектированию домена. Не нужны, потому что нет необходимости отделять один модуль от другого, они и так не знают друг о друге и общаются через общего предка. Отказываясь от протоколов мы не теряем тестируемость и масштабируемость, но и делаем тесты максимально простыми. Нам не нужно писать или генерировать множество моков, достаточно просто провалидировать результат работы редюсеров. Для нас это критически важно, так как это позволяет писать нам unit тесты очень быстро и в больших объемах.
В целом ничего не имеем против протоколов, но если их можно не использовать и глобально ничего не терять, то почему бы не использовать) Но здесь речь только про доменный слой. Там где мы используем полноценные объекты, например в UIKit, мы используем протоколы, хотя и это не обязательно. Чуть подробнее про альтернативу протоколам можно посмотреть здесь.
Что касается разбиения доменной логики, то согласен. Более того, конкретный стейт даже не знает где он будет хранится, это определяется в предке. Некоторые стейты могут быть полностью computed и просто высчитываться из существующих данных, а не физически где-то храниться.
Да, суть как раз в единственном потоке на котором обрабатывается обновления состояния. Мы под это выделяем отдельную последовательную очередь, в TCA например по умолчанию редюсеры работают на мейне. На перфоманс это критически не влияет.
В любом случае у каждого подхода есть плюсы и минусы. В нашем случае единый стейт показал себя достаточно хорошо, а его минусы не настолько критичны. Но, конечно, важно учитывать особенности проекта при выборе архитектуры.
Одна из главных причин держать весь State в одном месте — это единый источник правды. Если говорить про Redux, то у него в документации в соответствующем пункте прописано про глобальный State и один Store. Конечно, если есть какая-то часть приложения, которая никак не зависит от всего приложения, то ее без проблем можно вынести в отдельный Store(в Redux это тоже упоминается).
Проблемы начинаются когда части приложения очень активно общаются: -Если UI компоненту нужно отправить экшены сразу в два стора, то у нас не будет возможности синхронно обновить их стейты. В такой ситуации могут возникнуть недопустимые состояния приложения и их будет сложно отловить. -Если сторам нужно использовать стейты друг друга, то их придется либо копировать либо работать с ними асинхронно. Это может привести к рассинхрону между сторами, который будет очень тяжело обнаружить.
Единый State лишен таких проблем. За одну итерацию редюсера мы можем синхронно обновить любую часть приложения, написать на это тест и гарантировать отсутсвие рассинхронов. Да это влечет за собой ряд доработок, но они в результате позволяют разделить части приложения на отдельные модули и свести к минимуму взаимодействие между ними. Про наш подход к модуляризации конкретно доменного слоя можно почитать здесь.
Мы одинаково подходим к работе как с сайд эффектами в виде рендеринга UI так и к асинхронным сайд эффектам. Например в виде запросов к серверу или локальной базе данных. То есть, если нам нужно сделать запрос к серверу, то мы создаем состояние для этого запроса, создаем компонент для этого запроса, который следит за своим состоянием и как только видит что ему необходимо выполнить запрос, то переходит к его исполнению. Впервые такой подход мы встретили у Алексея Демедецкого и попробовали у себя. На данный момент такой подход решает все наши задачи. По опыту можем выделить такие плюсы и минусы:
Плюсы: - Единый подход ко всем сайд эффектам. Какой бы не был сайт эффект разработчик всегда будет создавать компонент, который этот сайд эффект реализует. - Максимальная гибкость. Мы полностью управляем временем жизни сайд эффекта и можем в нужный момент подключить его к стору и отключить. Так же такой подход позволяет синхронизировать между собой работу отдельных сайд эффектов через стейт. - В случае TCA если не нужен сайд эффект, то в редюсере необходимо вернуть .none. Это может усложнять чтение кода. В используемом нами подходе редюсеру не нужно ничего возвращать.
Минусы: -В простых случаях получается избыточно. Реализовать Thunk для простого сценария получается быстрее. -В TCA на уровне редюсера можно понять какой сайд эффект исполнится следующим. В случае сайд эффектов как отдельных компонентов необходимо смотреть кто подписывается на изменения данного стейта.
В будущих статьях планируем детальнее раскрыть тему работы с сайд эффектами.
Соответствие Action, State и Reducer 1 к 1 или 1 к многим - это тема для большой отдельной статьи. Как-нибудь доберемся до нее и расскажем какие соглашения действуют у нас. Но потенциально это, конечно, возможно, чтобы один Action обрабатывался в двух разных Reducer. Как пример приведу example-проект для The Composable Architecture. Вот тут на 50-ой строчке appReducer ловит Action успешного логина и стартует игру. Этот же Action ловится и в loginReducer из модуля LogicCore.
А что вы подразумеваете под разделением слоев в случае Action и Reducer? Мы воспринимаем и Action и Reducer как часть доменной логики приложения, поэтому всегда кладем их в доменный слой.
В приведенном вами примере из Point Free говорится о коллекциях, я говорил о случае двух отдельных модулей. Но как случай обобщения на n модулей и хранение их в коллекции пример хороший, спасибо.
По поводу namespace во вью, конечно namespace всегда можно вынести из вью, в случае Redux например в Action Creator. Но тема статьи же не про вью.
Касательно строгости функциональной архитектуры и правильных и неправильных подходов я, пожалуй, не буду комментировать :)
Спасибо за комментарий! Derived Behavior конечно же смотрели :) Если вы конкретно про этот пример, то тут скорее про переиспользование favorites: Set<Int> в двух разных модулях. Этому случаю у нас посвящен раздел "У модуля несколько родителей", а такой подход описан в блоке Computed Module State.
Конкретно использование Namespacable не считаем костылем, а просто одним из вариантов решения проблемы, никого не заставляем так писать :) Вот и вот примеры использования такого подхода в редаксе. В The Composable Architecture другой подход и у нас он упоминается в разделе "Иерархия экшенов".
Про инкапсуляцию в смысле механизма скрытия для UDF писали в одной из прошлых статей.
Отсутствие протоколов — это скорее следствие такого подхода к проектированию домена. Не нужны, потому что нет необходимости отделять один модуль от другого, они и так не знают друг о друге и общаются через общего предка. Отказываясь от протоколов мы не теряем тестируемость и масштабируемость, но и делаем тесты максимально простыми. Нам не нужно писать или генерировать множество моков, достаточно просто провалидировать результат работы редюсеров. Для нас это критически важно, так как это позволяет писать нам unit тесты очень быстро и в больших объемах.
В целом ничего не имеем против протоколов, но если их можно не использовать и глобально ничего не терять, то почему бы не использовать) Но здесь речь только про доменный слой. Там где мы используем полноценные объекты, например в UIKit, мы используем протоколы, хотя и это не обязательно. Чуть подробнее про альтернативу протоколам можно посмотреть здесь.
Что касается разбиения доменной логики, то согласен. Более того, конкретный стейт даже не знает где он будет хранится, это определяется в предке. Некоторые стейты могут быть полностью computed и просто высчитываться из существующих данных, а не физически где-то храниться.
Да, суть как раз в единственном потоке на котором обрабатывается обновления состояния. Мы под это выделяем отдельную последовательную очередь, в TCA например по умолчанию редюсеры работают на мейне. На перфоманс это критически не влияет.
В любом случае у каждого подхода есть плюсы и минусы. В нашем случае единый стейт показал себя достаточно хорошо, а его минусы не настолько критичны. Но, конечно, важно учитывать особенности проекта при выборе архитектуры.
Одна из главных причин держать весь State в одном месте — это единый источник правды. Если говорить про Redux, то у него в документации в соответствующем пункте прописано про глобальный State и один Store. Конечно, если есть какая-то часть приложения, которая никак не зависит от всего приложения, то ее без проблем можно вынести в отдельный Store(в Redux это тоже упоминается).
Проблемы начинаются когда части приложения очень активно общаются:
-Если UI компоненту нужно отправить экшены сразу в два стора, то у нас не будет возможности синхронно обновить их стейты. В такой ситуации могут возникнуть недопустимые состояния приложения и их будет сложно отловить.
-Если сторам нужно использовать стейты друг друга, то их придется либо копировать либо работать с ними асинхронно. Это может привести к рассинхрону между сторами, который будет очень тяжело обнаружить.
Единый State лишен таких проблем. За одну итерацию редюсера мы можем синхронно обновить любую часть приложения, написать на это тест и гарантировать отсутсвие рассинхронов. Да это влечет за собой ряд доработок, но они в результате позволяют разделить части приложения на отдельные модули и свести к минимуму взаимодействие между ними. Про наш подход к модуляризации конкретно доменного слоя можно почитать здесь.
Мы одинаково подходим к работе как с сайд эффектами в виде рендеринга UI так и к асинхронным сайд эффектам. Например в виде запросов к серверу или локальной базе данных. То есть, если нам нужно сделать запрос к серверу, то мы создаем состояние для этого запроса, создаем компонент для этого запроса, который следит за своим состоянием и как только видит что ему необходимо выполнить запрос, то переходит к его исполнению. Впервые такой подход мы встретили у Алексея Демедецкого и попробовали у себя. На данный момент такой подход решает все наши задачи. По опыту можем выделить такие плюсы и минусы:
Плюсы:
- Единый подход ко всем сайд эффектам. Какой бы не был сайт эффект разработчик всегда будет создавать компонент, который этот сайд эффект реализует.
- Максимальная гибкость. Мы полностью управляем временем жизни сайд эффекта и можем в нужный момент подключить его к стору и отключить. Так же такой подход позволяет синхронизировать между собой работу отдельных сайд эффектов через стейт.
- В случае TCA если не нужен сайд эффект, то в редюсере необходимо вернуть .none. Это может усложнять чтение кода. В используемом нами подходе редюсеру не нужно ничего возвращать.
Минусы:
-В простых случаях получается избыточно. Реализовать Thunk для простого сценария получается быстрее.
-В TCA на уровне редюсера можно понять какой сайд эффект исполнится следующим. В случае сайд эффектов как отдельных компонентов необходимо смотреть кто подписывается на изменения данного стейта.
В будущих статьях планируем детальнее раскрыть тему работы с сайд эффектами.
Соответствие Action, State и Reducer 1 к 1 или 1 к многим - это тема для большой отдельной статьи. Как-нибудь доберемся до нее и расскажем какие соглашения действуют у нас. Но потенциально это, конечно, возможно, чтобы один Action обрабатывался в двух разных Reducer. Как пример приведу example-проект для The Composable Architecture. Вот тут на 50-ой строчке appReducer ловит Action успешного логина и стартует игру. Этот же Action ловится и в loginReducer из модуля LogicCore.
А что вы подразумеваете под разделением слоев в случае Action и Reducer? Мы воспринимаем и Action и Reducer как часть доменной логики приложения, поэтому всегда кладем их в доменный слой.