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

За основу своей чистой архитектуры я применил методологию проектирования DDD (Domain-Driver-Design). Что эта значит вкратце.

  1. Основное понимание предметной области: DDD нацелено на то, чтобы разработчики и эксперты предметной области вместе работали над пониманием бизнес‑логики и задач, решаемых приложением. Это помогает создавать более эффективные и соответствующие предметной области решения.

  2. Определение моделей предметной области: DDD ставит модель предметной области в центр разработки, помогая разработчикам создавать четкое представление о бизнес‑логике и правилах, определяющих вашу систему.

  3. Язык моделирования: DDD ставит задачу разработчикам использовать язык, который понимают и разделяют и технические и предметные эксперты. Это упрощает коммуникацию и обеспечивает более четкое понимание между разработчиками и экспертами предметной области.

  4. Разделение на слои: DDD рекомендует разделение приложения на слои, включающие слой предметной области, слой приложения и слой представления. Это упрощает сопровождение и масштабирование приложений.

Для примера был кейс получить данные о задачах из https://jsonplaceholder.typicode.com/ и отобразить их. Начал с того что настроил пустой React Vite проект без лишних надстроек, далее занялся со структурой папок, в принципе здесь ничего нового, как сказано выше основной концепцией DDD эта четкое представление о бизнес‑логике и правилах, определяющих вашу систему.

Сущность todos
Сущность todos

Внутри папки features/todos, можно будет увидеть следующее: папка с получением данных то бишь наши репозитории и модели, папка непосредственно с domain сущностью максимально абстрактная логика и папка presentation отвечающий за представление нашей логики.

Начнем с domain папки где описаны наши сущности.

Создаю интерфейс TodoEntity и описываю поля которые ожидаю получить.

domain/entities/Todo.entity.ts
domain/entities/Todo.entity.ts

Описываю интерфейс репозитория, ожидаю получить getTodos

domain/repository/Todo.repository.ts
domain/repository/Todo.repository.ts

Создаю usecase который ожидает получить ответ из getTodos

domain/repository/Todo.repository.ts
domain/repository/Todo.repository.ts

Над реализацией займусь в слой data где будут обращение апи, реализация класса Todo.repository и Todo.entity.

Реализация класса TodoRepository, как вы могли заметить в конструктор попадает todoApiService, аналогично для usecase'ов тоже, их я буду получать через DI.

Todo.repository-impl.ts
Todo.repository-impl.ts

Модель TodoModel реализует TodoEntity, сериализацию полей и всякого рода валидации

Todo.model.ts
Todo.model.ts

Работа с апи я унаследовал пакет ts-retrofit аналогично пакету retrofit, которая используется под андроид. Ее плюсами я посчитал быстро описывать интерфейсы к апи обращений через декораторы.

Todo.api.ts
Todo.api.ts

Связь между ними будет закладываться через конструкторы с помощью Dependency Injection, для этого я взял за основу очень удобный и подходящий пакет awilix

src/config/di
src/config/di

Ее настройка довольна проста и в использовании тоже, преимущественно эта - управление циклами жизни экземплярами класса.

Переходим к слою features/presentation

Во вьюхе будут 2 папки, эта компоненты и наш store, за основу store'а я решил использовать MobX, почему не Redux вы скажите... эта в избыточности большого шаблонного кода, и одна из плюсов mobx эта - мультисторы, ее подключение с awilix будет выигрышным вариантом чем redux, так как стор будет описываться классом, а не так как мы привыкли видеть в redux.

presentation/store/Todo.store.ts
presentation/store/Todo.store.ts

Ее мы также подключаем к нашей DI, чтобы получить наши usecase'ы. И остается за малым эта наша вьюха.

presentation/components/Todos.tsx
presentation/components/Todos.tsx

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

Ссылка на код

Комментарии (23)


  1. dopusteam
    29.10.2023 11:39
    +3

    Вам не кажется, что получилась слишком большая вложенность?

    Store -> useCase -> repository -> apiService?

    Не хватает вообще какого то резюме, что в итоге получили, удобство поддержки, там, или простоту.

    А ещё

    new TodoModel().from(json)

    Выглядит странно, имхо. Либо уж через конструктор, либо статический метод fromJson кажется было бы лучше


    1. emilov Автор
      29.10.2023 11:39
      -6

      Наоборот такая вложенность гарантирует нам то что каждый слой сервиса будет работать атомарно не нарушая принцип работы другого сервиса

      насчет TodoModel соглашусь, ее можно имплементировать разной вариацией, сразу из под коробки можно вызывать TodoModel.fromJSON(json)


      1. dopusteam
        29.10.2023 11:39
        +5

        Наоборот такая вложенность гарантирует нам то что каждый слой сервиса будет работать атомарно не нарушая принцип работы другого сервиса

        Непонятно, если честно. Есщи б у меня репа сразу запрос слала, то что бы я проиграл?

        Не зря говорят, что любую проблему может решить добавлением слоя абстракции, кроме проблемы большого количества слоёв. На практике, при изменении придётся менять сразу все слои, что в поддержке неудобно. По моему опыту.

        Так что кажется наоборот, такая вложенность гарантирует, что слои атомарными никак не будут


        1. emilov Автор
          29.10.2023 11:39
          -4

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


          1. dopusteam
            29.10.2023 11:39
            +4

            А есть нормальные аргументы, а не холодильники?

            Не совсем понимаю пока логики ваших слоёв и их зоны ответственности

            Ещё вы упомянули DDD, а где у вас доменная логика будет?


            1. emilov Автор
              29.10.2023 11:39
              -3

              я имею что дополнительный слой абстракции нужен для того чтобы отделить работу с бд и работа с апи, ведь на уровне репозиторий наша задача просто брать данные и обрабатывать их в случае ошибка возвращать пользователю, при этом не нарушая саму концепцию data layer


              1. dopusteam
                29.10.2023 11:39
                +2

                А может быть для разделения работы с апи и с бд просто нужны разные репозитории?

                Я правильно понимаю, что репа у вас - это просто фасад?


                1. emilov Автор
                  29.10.2023 11:39

                  да вы правильно подметили


                1. D7ILeucoH
                  29.10.2023 11:39
                  -10

                  Чувак, ты не выкупаешь прелести чистой архитектуры. Парень всё сделал правильно. Мы на Андроиде в лучших практиках так и поступаем.

                  Вложенность тебе не нравится? Ну положи весь код доступа в базу данных прямо в метод который рисует текстовое поле, господи... Только никому не говори что это "чистая архитектура", а то смеяться будут.

                  У каждого слоя есть своё назначение, вот лично тебе какой слой не понятен? Если ты не ответишь на этот вопрос, минус объективным быть не может, лучше извинить и поставь ему плюсы.



  1. nin-jin
    29.10.2023 11:39
    +3

    Вас в этой "чистой архитектуре" не смущает, что при каждом ремаунте вьюшки происходит загрузка всех задач?


    1. D7ILeucoH
      29.10.2023 11:39
      -7

      Чистая архитектура это просто способ хранения файлов проекта. Ну и понимания какие файлы должны от каких зависеть. Не более.

      К реализации бизнес логики (которая содержит упомянутую тобой проблему) это не относится


      1. nin-jin
        29.10.2023 11:39
        +7

        Вы только на собеседовании это не говорите, а то выше джуна не возьмут.


      1. lair
        29.10.2023 11:39
        +6

        Чистая архитектура это просто способ хранения файлов проекта.

        "Нет".


  1. Semigradsky
    29.10.2023 11:39
    +5

    Без объяснений выглядит как оверинжиниринг. Понятно, что сложно на TODO-примере объяснить зачем все эти файлы нужны, какую пользу даёт разделение на такое кол-во абстракций, но стоило попытаться.


  1. onets
    29.10.2023 11:39
    +1

    Помнится по рукам били, когда бизнес логика была в слое презентации


  1. dedmagic
    29.10.2023 11:39

    Разделение на слои: DDD рекомендует разделение приложения на слои, включающие слой предметной области, слой приложения и слой представления.

    Слой предметной области вижу (`domain`), слой представления тоже (`presentation`), а где слой приложения?


  1. Raspy
    29.10.2023 11:39
    +1

    Пришёл бизнес аналитик от заказчика и сказал что у них в ToDo айтеме есть ссылка на джиру. Как в вашей архитектуре это добавить не меняя код "продукта"?


  1. EGO7000
    29.10.2023 11:39

    У меня предложение к автору: а теперь эту ToDo нужно расширить и ввести кнопки массового переключения: все отмеченные становятся не отмеченными и наоборот. При этом нужно отправить запрос на сервер об этом и результат обработать выводом с проверкой, что список действительно нужно перерисовать или показать тостер с сообщением, что данные на сервере не обновились (500 ошибка пришла). Как будет выглядеть такое решение с вашей структурой?


  1. alex_k777
    29.10.2023 11:39

    с FSD было бы лучше;)


  1. desfar
    29.10.2023 11:39

    Я вот читаю статью и думаю, что мне это все напоминает.

    А Вы просто написали свой ангуляр на реакте.


  1. TheArcher
    29.10.2023 11:39
    +1

    Показаны структурные элементы, но тема не раскрыта абсолютно.

    DDD, в первую очередь, ставит задачу изоляции смыслового ядра приложения в отдельном слое. Остальные элементы выделяются как удобная абстракция для использовании в слое предметной области. Ваше приложение работает с готовым решением, которое уже инкапсулирует всю бизнес-логику, связанную с предметной областью, для которой оно разрабатывается. Фронтенд — это лишь представление этой логики для пользователя. Вы не обосновали то, зачем вообще DDD нужен на фронтенде, как в вашем, так и в общем случае.


  1. 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)).