-
-
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
Support for the composite pattern. #1152
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is sweet - we've been thinking about composites for a long time. It's nice to see it living!
… nested scope didn't replace composites from the outer scope.
…n info. We don't need it in v6.
Amazing stuff @alistairjevans - I've been trying to keep up with all the pipeline changes and the details here but haven't quite got my head around it all. In general it looks great to me, although I do wonder if we're missing a trick by not fully taking advantage of the new service pipeline fundamentals for this specifically (are things like metadata actually that important for composite use-cases, if it means other aspects are out of scope?) Just generally it seems like there's still a lot of special-casing for decorators and composites, or legacy parts of the implementation which are a bit of confusing (e.g. a couple of different parts of the code which represent something like service-level sharing). I'm planning to keep looking at this but don't let me stop this progressing, because clearly it's a perfectly functional and useful extension. I'll just leave that out there though - could there be a more elegant way to pull all of this together, or would there necessarily be downsides?... |
I did consider not supporting the 'special' relationship types on composites; the reason I decided that the composites needed to support meta/func/owned, etc is that if we didn't, it would mean that:
As for utilising the service pipeline more fully, I did spend some time a few weeks back prototyping a more 'complete' service pipelines implementation, where the registration to resolve wouldn't actually be known when the pipeline starts. It would start with just the service pipeline, then select the registration, and then invoke it. It would have made it possible to do meta+composites in the way you may have imagined. The problem with that change though was that:
So I abandoned that approach, and bought it back closer to how it currently functions. I would sum up my emotional journey with pure pipelines as: 👨💻👨💻🤞👍❤🤔❓❓😒🤦♂️😢. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love that we're able to get rid of IsAdapterForIndividualComponent
and I like the flexibility of the flags for options. There's a question about the need for HasOption
right in the interface and the possibility of an extension method on RegistrationOptions
. On a larger note, outside this PR we should probably define what InstancePerMatchingLifetimeScope
actually means (or what we intend it to mean, exactly) so we can determine whether something is behaving inconsistently with that definition.
So, basically, HasOption
question and we're good to go.
…d 'HasFlag' style method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🦄
On |
No, that property is used to determine whether to copy registration sources to nested component registries when we create a new scope. A rename might be required? Not sure. |
Nah, if it's still used, probably should leave it. I don't feel strongly about the name and keeping it that way is one less breaking change. |
This PR implements support for the composite pattern in Autofac. Fixes #970.
cc @johneking
Demo
First off, define your service, and the associated composite:
You can then specify that
DemoComposite
is a composite ofIService
:The composite implementation receives the set of actual registered implementations of
IService
.Composites can consume services in exactly the same manner as a normal component, including accessing the concrete implementations in interesting ways:
Composites can have their own Metadata (separate from the backing implementations); they function entirely like a normal registration:
In fact you can apply any of the normal decorating adapters like Func, Lazy, etc to the composite.
You can also register an open generic composite:
Delegate composites are cool too:
Decorators
One choice I have made is that decorators are only applied to the individual concrete implementations, and not to the composite implementation.
My thinking is that it seems weird to decorate both the composite itself and its implementations, some duplication could occur that we don't want.
I did it that way round because not decorating the composite is easy, but only decorating the composite would be super tricky.
Please weigh in if this seems wrong.
Lifetime
Composites can have their own lifetime, including being marked
SingleInstance
. This might mean that aSingleInstance
composite ignores any new registrations added in nested lifetime scopes. This sort of makes sense, but could be confusing if someone registers a new implementation in a lifetime scope, it won't be included in the composite when you then resolve that service.I've considered raising an error if someone defines a SingleInstance composite, but figured it might be handy in certain scenarios, so didn't want to apply a blanket ban. Again, let me know if you think that we do need an error for that scenario.
How it works
The main change that powers this is that
ComponentRegistration
has a new propertyIsServiceOverride
. This property indicates that:Resolve<IService>()
, regardless of any other default, preserve-default or dynamic source registrations.Resolve<IEnumerable<IService>>()
(or any of the collection enumerations).The
IsServiceOverride
flag is passed down through any target registrations, soMeta<IOverriddenService>
hasIsServiceOverride
set because so does the original service.The
ServiceRegistrationInfo
stores the service override when it is added as an implementation, and always returns it as the registration for the service. You can only have oneIsServiceOverride
registration for a service, last one wins.The
CollectionRegistrationSource
excludes allIsServiceOverride
registrations from it's list when resolving the set of implementations, closing the loop and letting us resolve IEnumerable without getting our composite back in the set.