Comments 5
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from "@angular/core";
@Component({
selector: 'app-demo',
template: '<p>User: {{ user | json }}</p>',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DemoComponent implements OnInit {
user?: User;
constructor(private postboy: AppPostboyService,
private detector: ChangeDetectorRef) {
}
ngOnInit(): void {
this.postboy.fireCallback(new GetUserQuery('exampleKey'), user => {
this.user = result; // Обновляем локальное состояние
this.detector.detectChanges();
});
}
}
Я правильно понимаю, у вас есть компонент, которому нужны некоторые данные. И вместо того, что бы обратится к сервису и потребовать у него это по 'exampleKey' и получив что-то обозреваемое забиндить это в шаблон, вам нужен сервис которому нужен класс запроса, который нужно в этот сервис закинуть, а потом самому прокинуть в шаблон?
А это решение точно соответствует решению без ненужных зависимостей в том числе и от сервисов?
Приветствую,
Ваше понимание механизма верно, но суть немного глубже. Модель взаимодействия через запросы и команды призвана минимизировать зависимости между компонентами и сервисами, а не усложнить их.
Если рассматривать подход с использованием запросов (как в примере), то основная идея заключается в том, чтобы:
Компонент запрашивал данные в абстрактной форме, а не напрямую обращался к конкретному сервису. Это позволяет компоненту не иметь привязки или зависимости от реализации сервиса.
Например, если в будущем логика получения данных изменится (например, вместо текущего API будет использоваться другой источник или появится кэширование), нам не придётся вносить изменения в сам компонент — вся логика изменения будет изолирована в обработчике запроса.
Существует единый, централизованный механизм коммуникации. Запросы и команды из библиотеки @artstesh/postboy помогают унифицировать взаимодействие между сервисами и компонентами. Это особенно ценно в сложных, модульных системах, где много областей ответственности.
Теперь о "ненужных зависимостях".
В описанной схеме компонент действительно знает о библиотеке postboy, так как он использует её для отправки запроса, но этот код (запрос через fireCallback) легко изолируется и инкапсулируется. Основная ценность этого подхода — то, что компонент вообще не знает, откуда приходят данные и какой сервис отвечает за их получение. Благодаря этому достигается гибкость архитектуры.
В обычном сценарии "компонент → сервис" любой значительный рефакторинг, изменение логики получения данных или, например, внедрение нового уровня обработки потребует изменений и в компоненте, и в вызываемой логике. Здесь же мы разделяем их полностью: поведение можно менять, не трогая компонент.
Ваш вопрос справедлив при работе с простыми приложениями, где нет необходимости вводить подобный слой абстракции (и зависимость от "посредника" действительно лишняя). Но в более сложных архитектурах, где нужно отделить доменную логику от визуальной, компоненты от конкретных сервисов, при этом сохранить читаемость и гибкость, этот подход, imho, выступает отличным решением.
Компонент запрашивал данные в абстрактной форме, а не напрямую обращался к конкретному сервису. Это позволяет компоненту не иметь привязки или зависимости от реализации сервиса.
Для этих целей как раз и придуман DI. Если сервис внедряется механизмом внедрения зависимостей, то клиент уже не имеет привязки к конкретному сервису. Эту привязку делает DI.
Существует единый, централизованный механизм коммуникации. Запросы и команды из библиотеки @artstesh/postboy помогают унифицировать взаимодействие между сервисами и компонентами. Это особенно ценно в сложных, модульных системах, где много областей ответственности.
Ну, вот просто любопытно. Из представленных примеров мне непонятно, а как, например, поддерживается актуальность кэша? Как настраивается его инвалидация?
Мне не удалось увидеть бонусов именно от конкретной реализации централизованного механизма коммуникации.
Ваш вопрос справедлив при работе с простыми приложениями, где нет необходимости вводить подобный слой абстракции (и зависимость от "посредника" действительно лишняя). Но в более сложных архитектурах, где нужно отделить доменную логику от визуальной, компоненты от конкретных сервисов, при этом сохранить читаемость и гибкость, этот подход, imho, выступает отличным решением.
Так а зачем вообще тогда нужен DI, если он не помогает разделять доменную логику от визуальной?
Вы его зачем тогда тут используете?
Из того, что вы написали складывается впечатление, что вас интересует задача, которую решает такой паттерн проектирования как мост. Который позволяет за одним интерфейсом сервиса скрывать разные реализации. Но, вместо того, что бы работать с целым сервисом, вы поделили ее на независимые операции и работаете конкретно с ними. Таким образом вы увеличиваете количество зависимостей и обслуживающего кода. С тем же успехом используя DI вы можете так же создавать сервис на каждую операцию и использовать его.
1. Использование DI для разделения зависимостей Да, внедрение зависимостей (DI) действительно решает задачу разделения компонентов и сервисов на уровне инфраструктуры. Однако важно учитывать, что DI — это только способ предоставления зависимости, то есть кто-то должен связать потребителя и его зависимость. Решение "компонент → сервис" само по себе не избавляет от логической зависимости компонента от сервиса.
С помощью @artstesh/postboy достигается более высокая степень абстракции, так как компонент запрашивает данные, не зная о существовании конкретного сервиса вообще.
DI определяет, какой конкретный сервис использовать или подставить;
подход с запросами через postboy позволяет компоненту вообще не знать, где и как будут получены данные.
Иначе говоря, DI решает проблему на уровне внедрения, а postboy — на уровне взаимодействия, убирая прямые зависимости.
2. Кэш и его инвалидация. Да, в примерах отсутствует проработка механизма управления кэшем, просто потому, что в статье и примерах акцент стоит на демонстрации работы самой библиотеки. Реализация управления кэшированием — это плавающий и кастомизируемый элемент системы, который может быть настроен под нужды проекта, и детали его реализации бесконечно далеки от абстракций, которыми оперирует библиотека.
3. Зачем такого рода абстракция, если можно обойтись DI? Вот здесь ваш комментарий о "Мосте" подходит в части философского определения о "разрыве реализации и абстракции", но это опять про попытку создать конфликт с DI... Мост действительно абстрагирует интерфейсы от их реализаций. Разница в том, что с использованием единой централизованной точки взаимодействия вы уходите от вопроса "какой именно интерфейс использовать". Поведение устанавливается на уровне обработчика сообщений, а не конфигурируется в DI. Вместо этого:
Запросы упрощают разбиение логики на независимые операции, позволяя точно концентрироваться на конкретной задаче.
Работа с центром сообщений (postboy) позволяет отделить транспорт запроса (как слать) от доставки результата (кто возвращает).
4. Обслуживающий код и его рост Конечно, накладные расходы в виде кода на recordSubject
или sub
в реальной практике могут быть заметны. Тем не менее такие механизмы предоставляют преимущества:
Инкапсуляция сложной логики в одном месте, а не в зависимости от компонентов.
Централизация процесса подписки, что снижает вероятность ошибок и упрощает масштабирование системы.
Вместо создания отдельных сервисов — каждый на своё действие — вы оперируете едиными потоками данных, которые обрабатываются специфичной логикой.
Тем самым общее количество "зависимого" кода фактически компенсируется гибкостью реализации.
P.S. Ваши замечания оправданны в контексте небольших проектов или систем с небольшим числом зависимостей. Там DI действительно справляется с задачами разграничения ответственности. Однако применение подхода через запросы и команды вводит абстракцию, позволяющую масштабировать архитектуру, минимизировать код привязки или явной зависимости между компонентами и гарантировать гибкость.
Если обобщить: это инструмент для создания архитектуры, которая стоит дополнительных затрат (наличие обработчиков, регистраций и т.д.), но выигрывает в поддержке при масштабировании. То есть это не альтернатива DI, а дополнение к нему — инструмент там, где DI уже недостаточно.
P.P.S. Боюсь, что в данном случае вы спорите не с библиотечным подходом, а с устоявшимися паттернами и подходами, я бы предложил вам ознакомиться с CQRS-подходом, Messaging pattern (pub/sub, как частная его реализация) и иже с ними. Логика брокеров сообщений вовсе не моя выдумка)
подход с запросами через postboy позволяет компоненту вообще не знать, где и как будут получены данные.
У вас получаются следующие зависимости Postboy[название операции]. Вы это противопоставляете Service[название операции]. У вас в обоих случаях компонент не знает где и как будут получены данные. Разделяя сервис на конкретные операции вы решаете другую задачу.
Решение "компонент → сервис" само по себе не избавляет от логической зависимости компонента от сервиса.
У вас как получается, вместо зависимости компонента от сервиса, вы получаете множество зависимостей компонента от всех операций, которые ему нужны. Вот если при сервисе он зависел от 2х операций, то у него была одна зависимость от сервиса и Service[название операции1] и Service[название операции1]. Вы это разделили на 3 зависимости Postboy, Query1, Query2.
Вы не решаете задачу скрытия как и где будут получены данные, т.к. за DI этого и так не видно. Вы решаете задачу распиливания сервиса на операции. Поэтому стоит объяснить зачем и когда это нужно.
Иначе говоря, DI решает проблему на уровне внедрения, а postboy — на уровне взаимодействия, убирая прямые зависимости.
Совершенно не понял, что это значит.
Да, в примерах отсутствует проработка механизма управления кэшем, просто потому, что в статье и примерах акцент стоит на демонстрации работы самой библиотеки.
Данный пример вызывает больше вопросов чем дает ответов. Если у вас все равно будет огромный комбайн сервис, который как раз и обеспечит настроенную систему кэширования. Тогда зачем нужна прокладка в виде множества query?
Вы подумайте как вам помогут эти query, когда вы начнете реализовывать инвалидацию кэша.
Запросы упрощают разбиение логики на независимые операции, позволяя точно концентрироваться на конкретной задаче.
Вот мне и любопытно, что стало проще? Вы же не убираете никуда UserService. Вы просто добавляете к нему прокладку. Иными словами, к задачам UserService добавляются еще задачи по созданию и обслуживанию Query.
Работа с центром сообщений (postboy) позволяет отделить транспорт запроса (как слать) от доставки результата (кто возвращает).
С точки зрения компонента, вы просто заменяете один сервис на другой. Был UserService[command], а стал PostboyService[command]. Вы добавили вот эти не до конца мне понятные абстракции "транспорт запроса", "доставка результата" хотя с позиции компонента ничего не понялось.
Компоненту нужен результат операции: Получить пользователя. Для этого он отправляет сервису сообщение. Он это делает в обоих случаях. Но в одном случае на каждое сообщение свой класс, а в другом сообщение - это метод класса.
Что должно случится, что бы появилась необходимость пилить один связный интерфейс на множество?
Вместо создания отдельных сервисов — каждый на своё действие — вы оперируете едиными потоками данных, которые обрабатываются специфичной логикой.
Судя по коду, вы все равно создаете отдельные сервисы каждый на свое действие. Они у вас работают под капотом Постбой-Квери. А оперируете вы ровно тем же, чем и раньше оперировали. У вас просто поменялся тип доступа к этому.
Тем самым общее количество "зависимого" кода фактически компенсируется гибкостью реализации.
Из примеров этого не видно. Т.е. гибкость то видна, а вот польза от нее нет. Наоборот видно, что гибкость ради гибкости требует огромного количества обслуживающего кода.
P.S. Ваши замечания оправданны в контексте небольших проектов или систем с небольшим числом зависимостей. Там DI действительно справляется с задачами разграничения ответственности. Однако применение подхода через запросы и команды вводит абстракцию, позволяющую масштабировать архитектуру, минимизировать код привязки или явной зависимости между компонентами и гарантировать гибкость.
Звучит, конечно, неплохо. Значит нужно показать как решаются задачи масштабирования без Запросов и с Запросами. Именно задачи масштабирования, а не просто отделения какой-то там логики от какой-то там реализации.
Если обобщить: это инструмент для создания архитектуры, которая стоит дополнительных затрат (наличие обработчиков, регистраций и т.д.), но выигрывает в поддержке при масштабировании. То есть это не альтернатива DI, а дополнение к нему — инструмент там, где DI уже недостаточно.
Вот мне и любопытно посмотреть как это выглядит. Особенно в сравнении.
P.P.S. Боюсь, что в данном случае вы спорите не с библиотечным подходом
Я вижу статью в которой по сути подменяются понятия. И вместо демонстрации плюсов масштабирования в одном случае и недостатков в другом сделан акцент на отделение логики от реализации. Хотя это тут вообще ни при чем.
Асинхронные команды и запросы c @artstesh/postboy: упрощаем архитектуру приложений