-
-
Notifications
You must be signed in to change notification settings - Fork 509
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
Registrar/Resolver Redesign #1657
Comments
@rcdailey I'm not against the idea per se, as long as there is no breaking change in the API. We're about to ship 1.0 of Spectre.Console and Spectre.Console.Cli, and we can't really introduce breaking changes to the API at this point. |
Thanks @patriksvensson. Would the milestone still be 2.0 as opposed to a minor/feature release if I were able to maintain backward compatibility? I won't say that backward compatibility is impossible, but it will certainly be challenging. Any advice or tips you can share in this area would be valuable. I'd also like to make sure you're OK with the general approach I've chosen. I decided to introduce the DI abstractions instead of keeping your current interfaces for a few reasons:
I'll give this some more thought and share progress along the way; I definitely would like for this to be a collaborative effort. If you have a discord server or something we can chat on that'd also be great, if you wouldn't be bothered by me! Thank you for the fast response. |
@patriksvensson In addition to my previous reply, I have another question:
How important are these registrations in |
@rcdailey this will force all users to use the "Microsoft DI" and restrict everyone else who likes to use some other DI container, right? |
No, that is something I'm deliberately trying to avoid, but I'd like to share my plan below for feedback and also to make sure things really will go as I hope.
The benefits I'm aiming for:
I think achieving these goals would be much easier if we were willing to accept breaking changes. It might be possible to avoid it, but it will result in a significant amount of problem solving and effort that I do not personally believe to be worth it. My principle is always to avoid backward breaking changes, so this isn't an opinion I take lightly. However, if you all feel strongly about backward compatibility, I will do my best to find an iterative approach that allows for it. I would likely take a two step approach:
I hope my explanation is helpful. If my plan sounds too ambitious or just impossible let me know. Happy to adjust, after all, I need you all to be on board with this for PR approvals and I really don't want to waste effort if I can avoid it. |
I noticed // Create deconstructor.
var deconstructorType = parameter.PairDeconstructor?.Type ?? typeof(DefaultPairDeconstructor);
if (!(resolver.Resolve(deconstructorType) is IPairDeconstructor deconstructor))
{
if (!(Activator.CreateInstance(deconstructorType) is IPairDeconstructor activatedDeconstructor))
{
throw new InvalidOperationException($"Could not create pair deconstructor.");
}
deconstructor = activatedDeconstructor;
} This leads me to believe that there are no active users for What is the intent here? Should the configurability of a "pair deconstructor" be removed from the public interface, or should the interface simply be made |
I've read none of the above @rcdailey, but when my plate is more clear (that shouldn't be too long away), I'm happy to work alongside you with this, if it's still of interest. |
Thanks Frank. I'll try to renew interest later, but the slow turnaround makes any motivation or progress difficult. I understand everyone is spending their free time here, so I'm not upset and certainly not pointing fingers. But it is a reality, and unfortunately I barely have enough free time as it is working on my own projects. I absolutely do want to maintain a helpful relationship so if you ever get some downtime let me know. I mentioned connecting on Discord earlier, if that's an option anyone is comfortable with, that would likely make communication much easier. |
@rcdailey There is a lull in maintainer activity/free time at the moment, except for myself, which is what you are seeing from the outside. Nothing untoward, just the reality of situation. I'm currently clearing down a few PR's and preparing the command line system for version 1 of Spectre.Console. But once that's out of the way, say early Dec, I can stick around and support any development you do, so you know it will be merged. That said, if it's too ambitious or breaking, then I'll become slowed down having to square it off with the other maintainers. But other things I can merge, and/or we could look at other issues if you feel like contributing there instead. Or simply acknowledging this is the situation currently, and waiting a bit. I think you're good in terms of 'maintain a helpful relationship', so don't let that cloud the decision how we proceed here. |
Thanks again Frank. You've always been a pleasure to chat with on here so definitely will take advantage of your free time in December. I think this is a challenging effort, especially if it means not breaking backward compatibility. I've mulled over a few different approaches but it involves a LOT of hacks and shims just to avoid breaking things. And the way the resolver is exposed to users in various structures and callbacks makes it even more challenging. My inclination is to just break everything to get it the way we want, instead of tip toeing around compatibility. I understand that implies everything you mentioned, but maybe Phil's idea of bundling it with Native AOT work might make it more palatable (although I have no idea what that effort is yet, or if it's a good idea to build a bigger kitchen sink). |
I also noticed an issue with the current implementation that should be addressed if a new one is made. I use myself SimpleInjector container which lock itself after the first dependency resolution.
|
I want to understand the situation with |
nb. It's the following line that resolves the registrations, obviously deep in the command execution method:
If this was moved somewhere else @kvpt, how should it work? For example.
Is this what you had in mind? |
I think there is no need for a new method here, it only need to adhere on this two principles :
Currently rule 2 seems ok, but rule 1 is not. spectre.console/src/Spectre.Console.Cli/Internal/CommandExecutor.cs Lines 13 to 28 in aa9e5c4
For exemple the executor register IConfiguration, IAnsiConsole and CommandModel. |
Yes, I see what you mean @kvpt. I'm keen to look into whether a non-breaking refactor could separate out these different build/run concerns. I'll update the thread once I've investigated further. |
It's nearly December @rcdailey, my diary is emptying a little - how are you feeling about this issue? |
I'm going to take a look at this tonight and see if we can remove the |
@patriksvensson It would be worth keeping in mind @kvpt's comment above, when looking into this, see: #1657 (comment) |
Awesome news. I would see if you can instead get away with a dependency on just |
No, we still used a built-in DI container even if the user doesn't provide one, so would need to have an actual implementation as well. |
I guess what I'm saying is that there's a strategy you could adopt where the Spectre.Console library itself uses constructor injection normally with no DI framework dependencies. Imagine using pure DI in this case. There would be nothing special about this, you're just using dependency inversion and factories. Then via extension libraries, you can extend your configuration builder pattern to facilitate initialization of the library (i.e. provide its dependencies) using specific DI libraries, such as MS.DI, Autofac, etc. Users can also implement their own. I'm not saying you should do this; it's just an approach. If you want to hard-code to support Microsoft.DI, then I completely understand. Other popular libraries like MassTransit do this, and it seems to work fine (except when you need to support older .NET frameworks). Thanks again for acknowledging the issue and expressing support for it. If I can help at all please let me know. |
I have solved a very similar issue in https://github.com/JKamsker/Spectre.Console.Extensions.Hosting-ng |
I've never really been happy with the current type registrar/resolver model in Spectre.Console. Registration/resolution concerns are scattered across objects in the library with no clear line drawn between setup code and code with dependencies on services. This has been a complaint of mine going pretty far back.
The command interceptor logic introduced recently made the mutual exclusivity of the registrar/resolver design a little less painful, but I still often find myself in catch 22 scenarios simply because Spectre.Console steals away control over my own DI container from me.
I'd like to end this once and for all by completely rethinking the way Spectre.Console approaches DI. Instead of implementing its own nested facades, adopt a more modern approach. Something along the lines of this:
There's certainly different approaches here. There's a fluent builder pattern that might be more appropriate here as well, although I will say that when I tried it, it didn't give me that separation between registration & resolution like this approach does.
I'll provide a more comprehensive implementation of this below. Instead of diving into the Spectre.Console code base and going crazy with refactoring, I wanted to start with a small demo to mimick some of the top level objects in the library and show the approach without much concern for implementation detail right off the bat.
This also gives us a chance to discuss the idea and see if the maintainers are interested in an overhaul in this area. I understand backward compatibility will be a big concern here. I honestly don't see a way to keep things backward compatible and still be able to overhaul the aspects of this that desperately need it, IMO.
Let me know how you folks feel about this. I'm happy to help drive the work, I just want to make sure it's not a PR bound to be rejected.
Thanks in advance.
NOTE: In the demo, I'm using the nuget package
Microsoft.Extensions.DependencyInjection
, but Spectre.Console will actually only use the abstractions (i.e.Microsoft.Extensions.DependencyInjection.Abstractions
). I think going forward, the main spectre.console package should only be concerned with the IServiceProvider/IServiceCollection abstractions which allow users to adapt these just as they do today with the registrar/resolver facades. In the future we can add packages likeSpectre.Console.DependencyInjection
, where we might provide additional facilities like theAddSpectreConsole()
method.I'm open to opinion here but I'm just assuming you'd want to separate DI-specific implementations into their own packages because you might want to support other containers like Autofac separately without impacting the main library.
The text was updated successfully, but these errors were encountered: