Но мой декоратор никогда не попал!
Это верно. Чтобы понять, почему это так, лучше всего визуализировать граф объектов, который вы хотите построить:
new GetTagsForUserQuery(
new CachedCachedQueryDecorator<Task<List<Asset>>, User>(
new GetAssetsForUserQuery()))
СОВЕТ ПРО: Для многих проблем, связанных с DI, очень полезно построить требуемый граф объектов на простом C #, как показано в предыдущем фрагменте кода. Это дает вам четкую мысленную модель. Это не только полезная модель для вас, это полезный способ сообщить другим, чего вы пытаетесь достичь. Это часто гораздо труднее понять, когда просто показывают регистрации DI.
Однако, если вы попробуете это, этот код не скомпилируется. Он не скомпилируется, потому что GetTagsForUserQuery
требует GetAssetsForUserQuery
в своем конструкторе, но CachedCachedQueryDecorator<Task<List<Asset>>, User>
- это не GetTagsForUserQuery
- они оба ICachedQuery<Task<List<Asset>>, User>
, но это не то, что GetTagsForUserQuery
требует.
Из-за этого технически невозможно обернуть GetAssetsForUserQuery
CachedCachedQueryDecorator
и вставить этот декоратор в GetTagsForUserQuery
. И то же самое верно, когда вы будете разрешать GetAssetsForUserQuery
непосредственно из Simple Injector следующим образом:
GetAssetsForUserQuery query = container.GetInstance<GetAssetsForUserQuery>();
В этом случае вы запрашиваете GetAssetsForUserQuery
из контейнера, и этот тип принудительно применяется во время компиляции,Также в этом случае невозможно обернуть GetAssetsForUserQuery
декоратором при сохранении типа GetAssetsForUserQuery
.
Что бы сработало, однако, запрашивает тип по его абстракции :
ICachedQuery<Task<List<Asset>>, User> query =
container.GetInstance<ICachedQuery<Task<List<Asset>>, User>>();
В этом случае вы запрашиваете ICachedQuery<Task<List<Asset>>, User>
, и контейнер может вернуть вам любой тип, если он реализует ICachedQuery<Task<List<Asset>>, User>
.
То же самое относится к вашему GetTagsForUserQuery
. Только когда вы позволяете ему зависеть от ICachedQuery<,>
, можно украсить его. Поэтому решение состоит в том, чтобы зарегистрировать GetAssetsForUserQuery
по его абстракции:
Container.RegisterDecorator(
typeof(ICachedQuery<,>),
typeof(CachedCachedQueryDecorator<,>));
Container.Register<ICachedQuery<Task<List<Asset>>, User>, GetAssetsForUserQuery>();
Но обратите внимание на следующие вещи:
- Независимо от того, выполняются ли ваши запросы (я обычно называю их«обработчики», но то, что в названии) кешируются или нет - это деталь реализации. Вам не нужно определять другую абстракцию для кешируемых запросов, и потребители не должны знать об этом.
- Вместо предоставления отдельного
CacheStringKey
, вместо этого используйте P args
в качестве кешаключ. Это можно сделать, например, сериализацией args
в объект JSON. Это делает кеширование более прозрачным. В случае, если объект args
очень сложный, количество записей в кеше будет слишком большим, так что обычно вам нужно только кешировать результаты очень простых arg
запросов. - Независимо от того, кэшировать или нет,скорее это детали реализации, которые либо должны быть включены в Composition Root , либо в часть реализации запроса (обработчика). Обычно я делаю это, помечая эту реализацию атрибутом, но интерфейс также может работать. Затем вы можете применить декоратор условно .
- Запретить предоставление полностью разорванных сущностей как в качестве входных, так и выходных данных для вашего запроса (обработчики). Вместо этого используйте отдельные ориентированные на данные POCO (например, DTO). Что значит отправить
User
в качестве ввода? Однако гораздо понятнее, когда вы отправляете объект GetAllUserAssets
. Это GetAllUserAssets
может содержать только свойство UserId
. Это позволяет очень легко превратить этот объект в кэшируемую запись. То же самое касается выходных объектов. Сущности очень трудно надежно кэшировать. Это намного проще с POCO или DTO. Их можно сериализовать с гораздо меньшими усилиями и рисками.
Я писал о архитектурах в стиле CQRS в прошлом. Смотрите, например, эту статью . Эта статья объясняет некоторые из пунктов, изложенных выше.