-
-
Notifications
You must be signed in to change notification settings - Fork 839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Decorators unable to use built-in relationships (Lazy, Func) for the decorated service #967
Comments
You're decorating all of your command handlers - wrapping them. If you don't want it decorated, don't register the decorator. |
Correction - I see what you're saying now. Don't register the decorator in the RegisterAssemblyTypes call. You'll have to register all BUT that one. |
For future questions, please ask on StackOverflow as noted in the issue template. You will get a faster and more accurate response there. |
I change my register code,
If i remove the Lazy<> in LazyCommandHandlerDecorator. like this
The Decorator work fine, |
Have you tried with the latest 4.9.2 release of Autofac? |
I update 4.9.1 to 4.9.2 from nuget, |
Turns out there is a bug here. I've added a minimal repro test to the suite. [Fact(Skip = "Issue #967")]
public void DecoratorParameterSupportsLazy()
{
var builder = new ContainerBuilder();
builder.RegisterType<ImplementorA>().As<IDecoratedService>();
builder.RegisterDecorator<DecoratorWithLazy, IDecoratedService>();
var container = builder.Build();
var instance = container.Resolve<IDecoratedService>();
Assert.IsType<DecoratorWithLazy>(instance);
Assert.IsType<ImplementorA>(instance.Decorated);
}
private interface IDecoratedService : IService
{
IDecoratedService Decorated { get; }
}
private interface IService
{
}
private class DecoratorWithLazy : IDecoratedService
{
public DecoratorWithLazy(Lazy<IDecoratedService> decorated)
{
this.Decorated = decorated.Value;
}
public IDecoratedService Decorated { get; }
} I'm not sure how to fix it off the top of my head, but @alexmg just fixed #965 which is also decorator related, unclear if maybe something might pop out at him as being how to fix this one. I'm personally a little swamped so I can't dive in at the moment. |
Turns out this happens with |
The problem here is that the when resolving What is being asked for here is that the decoration process be reversed, with the decorator class getting created before the component being decorated, and the decorated instance instead being provided to it in a late bound/lazy manner. I think the real issue is that the cycle is not being detected by the existing mechanism and that we should be checking that the decorator class actually contains a parameter that will accept the instantiated instance directly, and not via The original issue with the The entire decorator mechanism is based around resolving the implementation type first and then finding and applying any relevant decorators on top. I think supporting that and this alternative reverse decoration process is just too much. Extend that reverse decoration concept to a longer decorator chain and my head to starts to hurt thinking about decorators in the middle of the chain also being lazy or factories to other decorators. This just doesn't feel like something we can support so the appropriate error handling will need to be put in place to prevent it from happening. |
I'm not going to make any changes at this point and will think about the scenario for a while. I can see the desire to structure things like this but cannot see how the implementation would work yet. |
I've been looking at this one a bit because of the connection to a few other items, and have a few thoughts. Apologies if this is ground that's been covered or it gets off track, I'm happy to be corrected!
Generally, I'm a bit confused by the semantics here and not sure if that actually points to a deeper problem. To expand on @alexmg's comment, if a decorator for a service
where I think a "real" decorator would need to do both parts of the process above, internally duplicating what
The suggestion in #1000 is to potentially construct a single decorator as a registration source. But presumably that's why the newer decoration structure exists, because: a) it's tricky to resolve different components for However, using the old adapter syntax (with a few modifications to open it up a bit more for keys), the following works, making use of the existing built-in adaption from
Could something like that be a solution in the short-term? It's not all that neat, particularly because it changes the usage to require A final point to summarise after thinking about this for a while: overall the desired functionality seems to be "the ability to defer the resolution of a service until its members are used, but without changing its interface". That does seem like a pretty special case - some questions:
I have some other thoughts connected to how adapters, decorators and potentially composites relate to each other too but may write that up elsewhere since it's a bit more general and cuts across a few other issues. |
Suppose there will be expensive costs on instance CommandHandler, and I want to do some validation in decorator, if the command has an error, I don't need to instance the CommandHandler.
This is my idea at the time, but now I am not using. |
Yep I see, seems like a reasonable requirement - although I would still question why all the command handlers need to act like they are Also, just to back-track a bit on my statement above about only taking direct dependencies - seems like that's probably wrong. I could see an approach where the decoration process itself is what creates a new |
Indeed, not all are expensive, but I hope that object don't care if it's lazy, object are dependent on interface, it may not understand whether building objects is expensive. |
I've followed through some of the proposed approach above and got something for this which works, but there are a few things to note about the whole setup and a couple of queries. Generally the approach is to switch the decoration process from iterative to recursive and then special-case dependencies on A side-effect I think might be unavoidable here (I think @alexmg also hinted at this): decorator conditions and the decorator context in general stop making sense altogether at certain points in the decorator hierarchy if you have deferred resolution. Say we have a normal chain of dependencies like:
In either the existing or my new version, we just resolve with the order D, C, B, A, passing a context along in that order which records that same sequence of decorators being applied. Adding in a
As in the previous post, IMO the B, A - this is available immediately What does the decorator context look like when we're attempting to resolve B? There's no info about the underlying instance or other decorators - even knowing about the list of decorators which may apply and a core component registration, we don't know which decorators will eventually be applied. So in this setup I think we just need to be aware that conditions and contexts necessarily stop working across parts of the sequence which are deferred. The other main issue is the special-casing aspect. As above, I've become convinced that any @alexmg / @tillig are you able to review the working here and the assertions I've made along the way as part of this solution? Specifically:
A few provisos there but I have the draft code here with all tests passing: https://github.com/johneking/Autofac/tree/Decorators It needs tidying, more validation and some extra tests but I can create a PR if the logic generally makes sense. |
@johneking I have done some refactoring around the decorator feature to address a number of other issues that seem like more pressing things to be addressed. It would be interesting to know if this makes things easier or harder for you in your branch. There have been a lot of issues around the new decorator support compared to the original implementation which was very explicit in its nature. If some of these less common scenarios can be addressed using the more explicit key based decorator chains then that may be a perfectly reasonable solution for such cases. It seems very easy to fix one thing and break another as things currently stand. I am concerned that making the implementation even more complicated will make fixing other issues that arise in the future a lot more difficult. There have been a number of occasions in the past were we have been too accommodating of fringe scenarios and ended up paying a price for it down the road. The decorator context is a new feature specific to the new decorator support and I would like to keep that functional. It is very much based on the idea that the decorator chain is materialized as it is being applied. I also agree that the constructor of an object should not be doing anything expensive and if it is then some refactoring is likely the right answer to the problem. |
Cool, I'll get the refactored version and make a PR out of it for completeness - whether that'll be worth merging or not I'm not sure. Agree that this function superficially doesn't seem like much of an extension, but on closer inspection is both an edge-case in terms of functionality (compared to alternatives) and does add a decent amount of complexity even just on a conceptual level. I'd suggest that at least it would be worth adding something to the documentation about limitations on classes which can be used for decoration, specifically in regard to constructor requirements and adaption, since it looks like this has come up a couple of times recently. That said, the solution I'll submit is mostly just a generalisation of the existing code and when it doesn't involve deferred resolution, it results in a process which is mechanically the same. It's only when using |
Something I worry about that comes up a lot is special-casing around particular relationship types - the notion of "if we're not resolving ( Not to mention the recent "I registered my own instance of It's like there needs to be a flag on the registration that says "this fulfills a special built-in relationship" which may be covered by the I'm also sort of weirded out by what the expected behavior of a |
IMO the requirement for special-casing here is somewhat inevitable due to the nature of the decoration process, specifically that it involves that chain of ordered steps - that doesn't come up elsewhere. The particular instance we want in the middle of a chain of decorators isn't the same as if we resolved directly from a container, and it's not easy to provide component-specific adapters to handle some of the relationships listed even when adaption can be included in the sequence. It looks like with that new flag we could adapt from T -> string for example, which is great. We could also construct a The special cases for So that maybe puts a dampener on this whole feature conceptually. However, another way to look at supporting something like it would be to jump back a step to having a single setting per registration which defers the creation of the underlying component until it's used. Thinking about that, it really might not be too hard - a dynamic proxy which is basically just a wrapper around a On
I've tidied up the code but not created a PR yet after all, I won't go too far into covering all the usage permutations if conceptually this isn't a path we want to go down. It might be worth a quick review in any case, it's both a solution to this issue and also an example of the kind of pipelining outlined in #970. In regard to precedence of registrations: that does actually seem like a pretty global improvement which is somewhat independent. Is it worth spinning off a separate issue for that? |
I think there is a misunderstanding here: a thing that acts as a T but gets a Lazy injected is not a decorator but a proxy. A decorator by definition always only gets a T injected that it decorates. |
The OP was looking for something which functionally acts like decoration, essentially that for any definition of T, return a new T - but instantiate it lazily. It's a bit of a niche use-case but does seem to be a thing, even if there are some questions over the semantics. IMO it even makes some sense as a general function - specifying decorators which internally use an adapter. At any point in the decorator hierarchy you can define a "decorator" which takes a definition of T, adapts to something else, then uses that as the dependency in a class which exposes a new T. That's where the existing mechanism falls down though - the pair of adaptations from and back to the original type need to be specified as a single unit, and that's not possible with the built-in adapters. So in that sense I agree that just trying to use the existing syntax to achieve decoration using an adapter isn't really decoration, and that should potentially be made clearer in the documentation. But the general use-case might still be valid and could be achieved by extending the decorator syntax to specify a pair of adapters, or one built-in plus one custom adapter (as per the original scenario). Func<> and Lazy<> still require an even-more-special adapter though and that needs a few other changes, but it does seem possible as per my example. |
I agree that the usecase is valid - however the direction or order of dependencies is opposite here as it is with a decorator. With a decorator you first have T and then you wrap the decorator around it. With this case you don't have a T at that point which caused the issue in the first place. If nothing is being called you might even never have a T. It sounds more like a new relationship where the fact that it is lazy initialized is not leaking to the consumer as it does with injecting |
This does not appear to have been fixed, as yet, in the v6 codeline; I tried running our [currently skipped] tests for this situation against the v6 codeline and it yields a |
I'll take a look at this; my instinct is that it won't be a quick fix for the overall problem; would require substantial changes to the decorator middleware to inject lazy/func instead of the already-resolved instance. That being said, it probably shouldn't result in a |
Urgh, okay, so the tests hit a StackOverflow because they access the Lazy/Func inside the constructor of the decorator, which in turn resolves a new instance of itself, which in turn calls the decorator, etc etc. Because each resolve by the Lazy/Func happens against the lifetime scope as a fresh operation, they have their own separate circular dependency tracking, and don't object to the infinite recursion. Not a lot we can do about that, but if you are accessing the Lazy/Func in the constructor, you should just inject the service normally. I'm inclined to agree with @sglienke pretty strongly here; this relationship that is being described, where something takes a In addition , as @alexmg indicates, what is being described here is a complete reversal of the decorator process, where we would first check what the decorator wants before proceeding down the pipeline. Besides the level of change described here, it's probably mutually exclusive with the idea of decoration predicates, which needs to know the instance of an object before it decides to decorate it. Chicken and egg. We can slightly abuse the new composites functionality in v6 to get an approximation of the desired relationship; but this requires the proxy to 'pick' the instance it wants, and sort of falls down if you want to get a collection of all services while still proxying them. [Fact]
public void ProxyAClass()
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<Implementation>().As<IMyService>();
// Composite (that will choose to only expose 1 instance).
containerBuilder.RegisterComposite<Proxy, IMyService>();
var container = containerBuilder.Build();
var composite = container.Resolve<IMyService>();
composite.DoSomething();
}
public interface IMyService
{
void DoSomething();
}
public class Proxy : IMyService
{
private readonly Lazy<IMyService> _lazyRef;
public Proxy(IEnumerable<Lazy<IMyService>> allLazys)
{
// Pick the last one.
_lazyRef = allLazys.Last();
}
public void DoSomething()
{
_lazyRef.Value.DoSomething();
}
}
public class Implementation : IMyService
{
public void DoSomething()
{
}
} In summary, to support this we would need to define a new relationship type, like a Proxy, which works similar to composites in some way, but it definitely isn't an extension of the decorator feature. Personally, I'm in favour of closing this issue, and opening a new feature request issue to define what a 'Proxy' looks like (or whichever name we think is appropriate). |
I'll buy the argument that this isn't a decorator and could be classified as a proxy or something else. I think a new feature request with some discussion to flesh out the concept would be fine, and it shouldn't hold up v6 getting out the door. I'll get something written up shortly. |
Sorry, my english is poor.
So just show my code.
And I use this to register:
When LazyCommandHandlerDecorator is create,
the parameter in constructor is LazyCommandHandlerDecorator,
i wish it is CreateInvoiceCommandHandler,
what mistake did i make?
The text was updated successfully, but these errors were encountered: