Search
Write a publication
Pull to refresh

Comments 13

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

не очень понятно, зачем делать не рутовые. какой профит

Я предлагаю в каждом модуле создавать папку "core". 

такое себе. сервисы, интерфейсы и т.д. могут вполне лежать и рядом с соответствующим компонентом, если конечно там не вагон файлов на один компонент. нет необходимости к каждому компоненту создавать core, share, helpers etc

не очень понятно, зачем делать не рутовые. какой проф

Если сервис является зависимостью только для одного lazy модуля, то зачем его создавать на старте приложения. Идея в том, чтобы соотносить все сущности с модулями, не валить все в кучу. Чем больше проект, тем больше куча.

нет необходимости к каждому компоненту создавать core

Я не предлагал создавать папку "core" для каждого компонента. Посмотрите, она есть только в модулях.

такое себе. сервисы, интерфейсы и т.д. могут вполне лежать и рядом с соответствующим компонентом, если конечно там не вагон файлов на один компонент

Это нормально работает, когда в одиночку трудишься над проектом, свой порядок в голове и все прекрасно. Проблемы начинаются, когда несколько человек трудятся над проектом. Сегодня интерфейс используется только в одном компоненте и лежит с ним в одной папке. А завтра этот интерфейс уже используется в десяти компонентах и двух сервисах. И тут есть 2 варианта:

  • разработчик забьет на это и оставит его там, где он есть;

  • перенесет в корень проекта, в котором уже сам черт ногу сломит, потому что там под сотню интерфейсов таких же;

  • создаст очередную папку "share" или "helpers" или еще что-то.

Если нет общего стандарта, то каждый делает так, как считает правильно. При этом у каждого своя правда. Так и появляется хаос.

И тут дело не в "красоте". Проблема в том, что чтобы понять в хаосе что к чему относится, что от чего зависит и что где используется, нужно открыть каждый файл и выполнить "find usages" – это все очень долго и нужно в голове все держать. А когда приходится разделить какую-то одну штуку на две отдельные штуки, так совсем становится печально.

Если сервис является зависимостью только для одного lazy модуля, то зачем его создавать на старте приложения

Если я ничего не путаю, то инстанс все же создается по требованию за счет DI, а не на старте приложения. То есть для ленивых модулей, сервис может загружаться так же лениво. Если сервис нигде не используется, он не будет включен в сборку за счет встряхивания дерева.

Ангуляровский подход по умолчанию предполагает, что сервисы являются одиночками, в рамках всего приложения. За счет как раз указания опции providedIn: 'root', вместо прямого прописывания провайдера в соответствующий модуль, становятся доступны все эти средства для оптимального распределения кода по нужным чанкам бандла.

Не корневые сервисы, зачастую поставляются с модулем, который позволяет устанавливать конфигурацию на уровне DI (вот эти все модули с .forRoot, .forChild), что требуется крайне редко.

В общем если нужен просто сервис-одиночка, без всяких хитростей с DI, без множественных инстансов и без жесткого ограничения на конкретный модуль, то предпочтительный вариант - это использовать @Injectable({ providedIn: 'root' }). Кроме того, если необходим новый инстанс сервиса на уровне каких-то конкретных компонентов, то лучше их прокидывать через провайдеры самих компонентов.

Про жесткое ограничение на конкретный модуль, я имел ввиду, что если какой то сервис попадает в тот же самый index.ts в виде экспорта, то лучше чтобы он был корневым. Это гораздо лучше позволит оптимизировать конечный бандл, чем подключение этого сервиса через еще одну прослойку в виде модуля, в котором он прописан.

Всё верно, а еще с подключением сервисов в модули можно напутать и не заметить что имеешь дело с несколькими разными инстансами.

Тем более что при уходе с модуля сервис не уничтожается. У module-scoped сервиса отличается от рутового только тем, что модульному можно скормить контролируемые зависимости.

простейший пример. вот у нас есть компонент А. у него есть сервис В. и вот мы запровайдили сервис, работает все ок. и тут выясняется, что А будет еще в другом модуле. а потом еще в одном. и вот сначала мы плодим провайдинг сервиса. а потом приходим к providedIn: root. и да. коллеги уже расписали бессмысленность провайдинга с точки зрения DI и тришейкинга

Да, прочитал комментарии выше по этому поводу. Пересмотрю свой подход. Спасибо за обратную связь, открыли мне глаза в вопросе провайдинга сервисов!

да это стандартная структура

для каждого модуля есть свой набор ангуляровских сущностей, причем некоторые сервисы могут быть внедрены на уровень компонента, а не модуля (зависит от задач). Некоторые общие сервисы уносятся в core модуль, чтобы разгрузить app модуль, а все общее делается за счет standalone компонентов в папке shared (либо по старинке один компонент - один модуль)

единственное что не понимаю смысл в алиасах и реэкспорт в index файлах. Сейчас еще кто-то вручную прописывает все импорты? Обычно в редакторах и ide этот блок скрыт, на него даже не смотришь, а импортирование автоматическое

да это стандартная структура

По факту да. Но далеко не везде она есть, поэтому решил написать. Часто в начале все складывают как придется, не хотят на это время тратить. А когда проект вырос, то уже сложно привести все в порядок. Уже не дерево, а лабиринт. Начинают на ходу переносить файлы, менять структуру зависимостей и т.д. Это все очень неприятно. Плюс в гите отследить изменения в файле если его 3 раза перенесли и 2 раза переименовали тоже тот еще квест.

единственное что не понимаю смысл в алиасах и реэкспорт в index файлах

Я и не говорю о каком-то супер смысле этого. Просто намного приятнее смотреть на алиасы, чем на забор из слэшей и точек. Когда мы импортируем что-то из node_modules, то не пишем же полный относительный путь. Так почему не делать также со своими модулями. Но и есть небольшой плюс у этого все же – мы видим из какого модуля приходит зависимость, по слэшам и точкам это сложно понять.

Как мне кажется, для импортов в рамках приложения, лучше использовать один скоуп: @app-name/auth, @app-name/system и т. д. Как минимум, чтобы можно было визуально понимать, что импорт относится именно к приложению, а не к какой-то библиотеке.

Служебные директории core, при таком подходе, очень легко могут превратится в очень большие свалки в каких-то общих модулях (app, shared и т. д.), но зависит конечно от предполагаемого объема приложения. То есть неглубокая вложенность - это конечно хорошо, но когда приходится работать с сотней подобных сервисов или интерфейсов:

import { TransactionsService, UsersService } from '@app/core/services';
import { TransactionDto, UserDto } from '@app/core/interfaces';

то такая плоская структура не всегда может быть удобной. Хочется иметь какое-то разделение подобных сервисов и интерфейсов на домены, но при этом не сильно увеличивая связность различных модулей. То есть для решения этой проблемы может понадобится введение дополнительных абстракций в структуру приложения, по типу: entities/transactions/transactions.service.ts, entities/transactions/transactions.interface.ts. В таком случае вложенность увеличивается не сильно, но нет такой проблемы, что сначала в одной директории среди большого множества файлов ищешь сервис, а потом в соседней директории, среди такого же множества файлов ищешь интерфейс данных, с которыми работает данный сервис. Иногда же бывает, что помимо сервиса и интерфейса данных, еще имеются какие-нибудь сторы и прочие инструменты, требуемые в рамках работы с одними и теми же данными. Впрочем редакторы сильно упрощают навигацию по коду, но все равно довольно сложно удерживать в голове: какие инструменты доступны для работы с теми же транзакциями, а какие нет. Разделение на основе семантики, а не по функциональному признаку, может значительно упростить восприятие отдельных частей проекта.

Кстати для контроля зависимостей между различными узлами приложения можно использовать NX. Там есть утилиты для линтера, для которых задаются правила: что, откуда и куда может импортироваться. Да и помимо этого, для ангуляра там достаточно удобный туллинг, именно в плане организации структуры репозитория.

Как мне кажется, для импортов в рамках приложения, лучше использовать один скоуп: @app-name/system@app-name/auth@app-name/system и т. д.

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

Служебные директории core, при таком подходе, очень легко могут превратится в очень большие свалки в каких-то общих модулях (app, shared и т. д.), но зависит конечно от предполагаемого объема приложения.

Согласен, предел масштабирования есть. В какой-то момент захочется добавить дополнительные разделения.

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

Я думаю, что разделение по функциональному признаку требует больше внимания и контроля, появляются дополнительные вопросы:

  • если сущность относится к нескольким функциям, то класть в какую-то общую папку или в папку первой, второй или третьей функции;

  • если сущность единственная для какой-то функции, то все равно делать папку или складывать в какую-то общую папку.

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

Кстати для контроля зависимостей между различными узлами приложения можно использовать NX.

Да, хорошая вещь. В плане структуры вообще отличная. Особенно полезна при тестировании. Тесты в большом приложении могут очень долго бежать, а в случае с NX можно каждый кусочек отдельно прогнать. NX дает классную структуру, но непростую, на первых порах может быть сложно. Особенно с обновлением зависимостей проекта/ов и самого NX.

Хотел написать про provideIn: root, но все высказали выше.( +1 за provideIn root). Хорошо, что вы упомянули про алиасы, эта тема как-то не широко используется. Но есть нюанс, некоторые IDE не сразу видят их по дефолту используют относительные пути с тысячей точек.

Я уже писал выше, это классный инструмент, не спорю. В статье речь о простой структуре. Я бы не сказал, что Nx – это простая штука, нужно уметь с ним.

Sign up to leave a comment.

Articles