Все мы понимаем для enterprise решений требуется тщательного анализа в плане проектирование и этому уделяется не мало времени, от структуры папок, описании регламента по стилям или коду, подключение микрофронтов и если эта не учитывать мы получаем в своем роде монолита, где все хаотично разбросано и в целом эта не плохо работает до определенного поры времени.
За основу своей чистой архитектуры я применил методологию проектирования DDD (Domain-Driver-Design). Что эта значит вкратце.
Основное понимание предметной области: DDD нацелено на то, чтобы разработчики и эксперты предметной области вместе работали над пониманием бизнес‑логики и задач, решаемых приложением. Это помогает создавать более эффективные и соответствующие предметной области решения.
Определение моделей предметной области: DDD ставит модель предметной области в центр разработки, помогая разработчикам создавать четкое представление о бизнес‑логике и правилах, определяющих вашу систему.
Язык моделирования: DDD ставит задачу разработчикам использовать язык, который понимают и разделяют и технические и предметные эксперты. Это упрощает коммуникацию и обеспечивает более четкое понимание между разработчиками и экспертами предметной области.
Разделение на слои: DDD рекомендует разделение приложения на слои, включающие слой предметной области, слой приложения и слой представления. Это упрощает сопровождение и масштабирование приложений.
Для примера был кейс получить данные о задачах из https://jsonplaceholder.typicode.com/ и отобразить их. Начал с того что настроил пустой React Vite проект без лишних надстроек, далее занялся со структурой папок, в принципе здесь ничего нового, как сказано выше основной концепцией DDD эта четкое представление о бизнес‑логике и правилах, определяющих вашу систему.
Внутри папки features/todos, можно будет увидеть следующее: папка с получением данных то бишь наши репозитории и модели, папка непосредственно с domain сущностью максимально абстрактная логика и папка presentation отвечающий за представление нашей логики.
Начнем с domain папки где описаны наши сущности.
Создаю интерфейс TodoEntity и описываю поля которые ожидаю получить.
Описываю интерфейс репозитория, ожидаю получить getTodos
Создаю usecase который ожидает получить ответ из getTodos
Над реализацией займусь в слой data где будут обращение апи, реализация класса Todo.repository и Todo.entity.
Реализация класса TodoRepository, как вы могли заметить в конструктор попадает todoApiService, аналогично для usecase'ов тоже, их я буду получать через DI.
Модель TodoModel реализует TodoEntity, сериализацию полей и всякого рода валидации
Работа с апи я унаследовал пакет ts-retrofit аналогично пакету retrofit, которая используется под андроид. Ее плюсами я посчитал быстро описывать интерфейсы к апи обращений через декораторы.
Связь между ними будет закладываться через конструкторы с помощью Dependency Injection, для этого я взял за основу очень удобный и подходящий пакет awilix
Ее настройка довольна проста и в использовании тоже, преимущественно эта - управление циклами жизни экземплярами класса.
Переходим к слою features/presentation
Во вьюхе будут 2 папки, эта компоненты и наш store, за основу store'а я решил использовать MobX, почему не Redux вы скажите... эта в избыточности большого шаблонного кода, и одна из плюсов mobx эта - мультисторы, ее подключение с awilix будет выигрышным вариантом чем redux, так как стор будет описываться классом, а не так как мы привыкли видеть в redux.
Ее мы также подключаем к нашей DI, чтобы получить наши usecase'ы. И остается за малым эта наша вьюха.
В заключении я бы хотел сказать, что данная архитектура возможно не самая лучшая, но ее идея в плане чистой архитектуры остается. В процессе ее можно будет расширить я думаю доп. слоями до подключение микрофронтов.
Комментарии (23)
nin-jin
29.10.2023 11:39+3Вас в этой "чистой архитектуре" не смущает, что при каждом ремаунте вьюшки происходит загрузка всех задач?
D7ILeucoH
29.10.2023 11:39-7Чистая архитектура это просто способ хранения файлов проекта. Ну и понимания какие файлы должны от каких зависеть. Не более.
К реализации бизнес логики (которая содержит упомянутую тобой проблему) это не относится
Semigradsky
29.10.2023 11:39+5Без объяснений выглядит как оверинжиниринг. Понятно, что сложно на TODO-примере объяснить зачем все эти файлы нужны, какую пользу даёт разделение на такое кол-во абстракций, но стоило попытаться.
dedmagic
29.10.2023 11:39Разделение на слои: DDD рекомендует разделение приложения на слои, включающие слой предметной области, слой приложения и слой представления.
Слой предметной области вижу (`domain`), слой представления тоже (`presentation`), а где слой приложения?
Raspy
29.10.2023 11:39+1Пришёл бизнес аналитик от заказчика и сказал что у них в ToDo айтеме есть ссылка на джиру. Как в вашей архитектуре это добавить не меняя код "продукта"?
EGO7000
29.10.2023 11:39У меня предложение к автору: а теперь эту ToDo нужно расширить и ввести кнопки массового переключения: все отмеченные становятся не отмеченными и наоборот. При этом нужно отправить запрос на сервер об этом и результат обработать выводом с проверкой, что список действительно нужно перерисовать или показать тостер с сообщением, что данные на сервере не обновились (500 ошибка пришла). Как будет выглядеть такое решение с вашей структурой?
desfar
29.10.2023 11:39Я вот читаю статью и думаю, что мне это все напоминает.
А Вы просто написали свой ангуляр на реакте.
TheArcher
29.10.2023 11:39+1Показаны структурные элементы, но тема не раскрыта абсолютно.
DDD, в первую очередь, ставит задачу изоляции смыслового ядра приложения в отдельном слое. Остальные элементы выделяются как удобная абстракция для использовании в слое предметной области. Ваше приложение работает с готовым решением, которое уже инкапсулирует всю бизнес-логику, связанную с предметной областью, для которой оно разрабатывается. Фронтенд — это лишь представление этой логики для пользователя. Вы не обосновали то, зачем вообще DDD нужен на фронтенде, как в вашем, так и в общем случае.
talented_lamp
29.10.2023 11:39При добавлении элемента в список генерируется id. Вроде он должен быть случайным, но по факту он всегда будет равен 0.
Math.random() возвращает число в диапазоне от 0 до 1. То есть, это всегда будут числа типа 0.85930859583, 0.046633 и т.п.. Если у них вызвать метод x.toFixed(0), на выходе будет строка '0'.
Если уж охота сделать случайное целое число, результат Math.random() нужно умножить на что-то большое, а потом отбросить дробную часть, например используя Math.floor(x) или (x | 0) вместо Number(x.toFixed(0)).
dopusteam
Вам не кажется, что получилась слишком большая вложенность?
Store -> useCase -> repository -> apiService?
Не хватает вообще какого то резюме, что в итоге получили, удобство поддержки, там, или простоту.
А ещё
Выглядит странно, имхо. Либо уж через конструктор, либо статический метод fromJson кажется было бы лучше
emilov Автор
Наоборот такая вложенность гарантирует нам то что каждый слой сервиса будет работать атомарно не нарушая принцип работы другого сервиса
насчет TodoModel соглашусь, ее можно имплементировать разной вариацией, сразу из под коробки можно вызывать TodoModel.fromJSON(json)
dopusteam
Непонятно, если честно. Есщи б у меня репа сразу запрос слала, то что бы я проиграл?
Не зря говорят, что любую проблему может решить добавлением слоя абстракции, кроме проблемы большого количества слоёв. На практике, при изменении придётся менять сразу все слои, что в поддержке неудобно. По моему опыту.
Так что кажется наоборот, такая вложенность гарантирует, что слои атомарными никак не будут
emilov Автор
слой репозиторий - эта обащение непосредственно к нашей апи application. тем самым не нарушая само обращение к бд. Ведь представьте что вам завтра нужно будет поменять холодильник, но привычка как брать с холодильника у вас осталось
dopusteam
А есть нормальные аргументы, а не холодильники?
Не совсем понимаю пока логики ваших слоёв и их зоны ответственности
Ещё вы упомянули DDD, а где у вас доменная логика будет?
emilov Автор
я имею что дополнительный слой абстракции нужен для того чтобы отделить работу с бд и работа с апи, ведь на уровне репозиторий наша задача просто брать данные и обрабатывать их в случае ошибка возвращать пользователю, при этом не нарушая саму концепцию data layer
dopusteam
А может быть для разделения работы с апи и с бд просто нужны разные репозитории?
Я правильно понимаю, что репа у вас - это просто фасад?
emilov Автор
да вы правильно подметили
D7ILeucoH
Чувак, ты не выкупаешь прелести чистой архитектуры. Парень всё сделал правильно. Мы на Андроиде в лучших практиках так и поступаем.
Вложенность тебе не нравится? Ну положи весь код доступа в базу данных прямо в метод который рисует текстовое поле, господи... Только никому не говори что это "чистая архитектура", а то смеяться будут.
У каждого слоя есть своё назначение, вот лично тебе какой слой не понятен? Если ты не ответишь на этот вопрос, минус объективным быть не может, лучше извинить и поставь ему плюсы.